1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//! Loading failures own acquisition context, never query-view errors.
use mant_protocol::ScopeTextError;
use std::{error::Error, fmt, path::PathBuf};
/// Invalid loading input or failure to acquire readable local content.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LoadError {
/// Native input was selected but this build does not enable the roff backend.
NativeBackendUnavailable {
/// Optional cached quick reference available through explicit tldr policy.
tldr_topic: Option<String>,
},
/// A document selector was empty after trimming.
EmptyName,
/// A native manual category was empty or malformed.
InvalidManualSection,
/// A tldr command query was qualified by a non-command manual section.
TldrManualSection {
/// Incompatible native manual section.
section: String,
},
/// An explicit Markdown source name was empty.
InvalidSource,
/// Markdown-source and native-manual selectors were combined.
ConflictingSourceSelectors,
/// A direct Markdown input path was empty.
EmptyMarkdownPath,
/// Automatic format inference did not recognize a direct input.
UnsupportedInputFormat {
/// Caller-facing input path.
path: String,
},
/// A document selector or source violated its bounded loading contract.
InvalidSelector {
/// Loading input field name.
field: &'static str,
/// Precise bound or character violation.
error: ScopeTextError,
},
/// Markdown input could not be read or parsed.
Markdown {
/// Caller-facing source path.
path: String,
/// Stable failure detail.
detail: String,
},
/// Markdown parsing produced neither document nor tldr content.
EmptyMarkdown {
/// Selected-document label.
label: String,
},
/// Registered-document discovery failed.
Registry {
/// Stable source-configuration or discovery detail.
detail: String,
},
/// Native manual loading failed.
Manual(ManualLoadError),
/// No full document was found, but an optional tldr entry is available.
ManualWithTldr {
/// Native-manual failure retained as the authoritative lookup error.
error: ManualLoadError,
/// Topic that can be queried explicitly with `--tldr`.
topic: String,
},
/// An explicit tldr query found no quick-reference candidate.
TldrNotFound {
/// Requested tldr topic.
topic: String,
},
/// An explicit tldr candidate could not be read or parsed.
Tldr {
/// Requested tldr topic.
topic: String,
/// Stable cache or Markdown failure detail.
detail: String,
},
/// No Markdown, manual, or quick-reference content could be resolved.
NoReadableContent {
/// Requested document name.
name: String,
},
}
/// Native-manual resolution or lowering failed after candidate selection.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ManualLoadError {
/// No indexed native manual matched the request.
NotFound {
/// Requested manual name.
name: String,
/// Search-path and candidate detail.
detail: String,
},
/// A selected manual could not be parsed or lowered.
Parse {
/// Requested manual name.
name: String,
/// Stable parser or source-policy detail.
detail: String,
},
/// Parsing succeeded but produced no readable semantic content.
Empty {
/// Requested manual name.
name: String,
/// Physical selected manual path.
path: PathBuf,
/// Non-fatal parser findings explaining the empty result.
diagnostics: Vec<String>,
},
}
impl fmt::Display for LoadError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NativeBackendUnavailable { tldr_topic } => {
formatter.write_str("native manual loading requires the 'roff' feature")?;
if let Some(topic) = tldr_topic {
write!(
formatter,
"\nhint: a tldr entry is available; run `mant {topic} --tldr`"
)?;
}
Ok(())
}
Self::EmptyName => formatter.write_str("name must not be empty"),
Self::InvalidManualSection => formatter.write_str(
"manual section must be a conventional number or the single letter 'l' or 'n'",
),
Self::TldrManualSection { section } => write!(
formatter,
"manual section '{section}' does not identify a command quick reference; tldr supports section families 1 and 8"
),
Self::InvalidSource => formatter.write_str("document source must not be empty"),
Self::ConflictingSourceSelectors => formatter.write_str(
"document source cannot be combined with a manual section or manual-only policy",
),
Self::EmptyMarkdownPath => formatter.write_str("Markdown path must not be empty"),
Self::UnsupportedInputFormat { path } => write!(
formatter,
"could not infer the input format for '{path}'; use --input-format markdown or roff"
),
Self::InvalidSelector { field, error } => {
write!(formatter, "{field} {}", selector_error_message(*error))
}
Self::Markdown { path, detail } => {
write!(
formatter,
"could not load Markdown document '{path}': {detail}"
)
}
Self::EmptyMarkdown { label } => {
write!(
formatter,
"Markdown document '{label}' has no readable content"
)
}
Self::Registry { detail } => formatter.write_str(detail),
Self::Manual(error) => error.fmt(formatter),
Self::ManualWithTldr { error, topic } => {
error.fmt(formatter)?;
write!(
formatter,
"\nhint: a tldr entry is available; run `mant {topic} --tldr`"
)
}
Self::TldrNotFound { topic } => {
write!(formatter, "no tldr quick reference was found for '{topic}'")
}
Self::Tldr { topic, detail } => {
write!(formatter, "could not load tldr entry '{topic}': {detail}")
}
Self::NoReadableContent { name } => {
write!(
formatter,
"no readable document content was found for '{name}'"
)
}
}
}
}
impl Error for LoadError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Manual(error) | Self::ManualWithTldr { error, .. } => Some(error),
_ => None,
}
}
}
fn selector_error_message(error: ScopeTextError) -> String {
match error {
ScopeTextError::Empty => "must not be empty".to_owned(),
ScopeTextError::ControlCharacter => "must not contain control characters".to_owned(),
ScopeTextError::TooLong { maximum } => {
format!("must not exceed {maximum} Unicode scalar values")
}
}
}
impl fmt::Display for ManualLoadError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound { name, detail } => {
write!(formatter, "could not load manual '{name}': {detail}")
}
Self::Parse { name, detail } => write!(
formatter,
"could not load manual '{name}': manual source: {detail}"
),
Self::Empty {
name,
path,
diagnostics,
} => {
write!(
formatter,
"could not load manual '{name}': libmandoc parsed {} but produced no readable sections",
path.display()
)?;
if !diagnostics.is_empty() {
write!(formatter, "; diagnostics: {}", diagnostics.join("; "))?;
}
Ok(())
}
}
}
}
impl Error for ManualLoadError {}