mant_loader/
manual_input.rs1mod error;
4mod source;
5
6use crate::ManualPage;
7use libmandoc_rs::ParseReport;
8use mant_codec::{parse_roff_bytes, parse_roff_bytes_with_report, redirect_target};
9use mant_ir::Document;
10use source::{load_manual_source, resolve_manual_redirects};
11use std::path::Path;
12
13pub use error::{ManualError, ManualErrorKind};
14pub use source::MAX_MANUAL_BYTES;
15
16pub fn parse_manual_source(path: &Path) -> Result<Document, ManualError> {
26 parse_manual_source_with_report(path).map(|(document, _)| document)
27}
28
29pub fn parse_manual_source_with_report(
40 path: &Path,
41) -> Result<(Document, ParseReport), ManualError> {
42 let loaded = load_manual_source(path)?;
43 reject_standalone_redirect(path, &loaded.source)?;
44 parse_roff_bytes_with_report(path, &loaded.source).map_err(ManualError::from)
45}
46
47pub fn parse_manual_bytes(path: &Path, source: &[u8]) -> Result<Document, ManualError> {
56 reject_standalone_redirect(path, source)?;
57 parse_roff_bytes(path, source).map_err(ManualError::from)
58}
59
60fn reject_standalone_redirect(path: &Path, source: &[u8]) -> Result<(), ManualError> {
61 if redirect_target(source)
62 .map_err(|error| ManualError::redirect(path, error.to_string()))?
63 .is_some()
64 {
65 return Err(ManualError::redirect(
66 path,
67 "standalone .so redirects require MANPATH discovery and cannot be followed by --input",
68 ));
69 }
70 Ok(())
71}
72
73pub fn parse_manual_page(page: &ManualPage) -> Result<Document, ManualError> {
80 let resolved = resolve_manual_redirects(page)?;
81 let mut document = parse_roff_bytes(&page.path, &resolved.source)?;
82 if let Some(alias_target) = resolved.alias_target {
83 document.meta.alias_target = Some(alias_target);
84 }
85 Ok(document)
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn standalone_inputs_reject_redirect_only_so_pages() {
94 let path = Path::new("stdin");
95 let error = parse_manual_bytes(path, b".so man1/target.1\n")
96 .expect_err("standalone input must not follow another file");
97 assert_eq!(error.kind(), ManualErrorKind::Redirect);
98 assert_eq!(error.path(), path);
99 assert!(error.to_string().contains("require MANPATH discovery"));
100 }
101
102 #[test]
103 fn standalone_malformed_redirect_keeps_original_path_and_failure_kind() {
104 for source in [
105 b".so\n".as_slice(),
106 b".so first second\n",
107 b".so bad\0name\n",
108 ] {
109 let path = Path::new("original display name.1");
110 let error = parse_manual_bytes(path, source).expect_err("reject malformed alias");
111 assert_eq!(error.kind(), ManualErrorKind::Redirect);
112 assert_eq!(error.path(), path);
113 assert_eq!(
114 error.message(),
115 "manual .so redirect must contain exactly one target path"
116 );
117 }
118 }
119}