1use std::path::PathBuf;
4
5use mant_sources::{
6 RegisteredDocument, RegisteredDocumentOrigin, SourceConfigError, list_registered_documents,
7};
8
9use crate::{ManualIndex, discover_manual_roots};
10
11#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
13pub enum AvailableDocumentKind {
14 Markdown,
15 Manual,
16}
17
18#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
20pub enum AvailableDocumentOrigin {
21 Documents,
22 Source(String),
23 ManualPath,
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct AvailableDocument {
29 pub name: String,
30 pub kind: AvailableDocumentKind,
31 pub section: Option<String>,
32 pub path: PathBuf,
33 pub origin: AvailableDocumentOrigin,
34}
35
36pub fn list_available_documents() -> Result<Vec<AvailableDocument>, SourceConfigError> {
43 let manuals = ManualIndex::from_roots(discover_manual_roots());
44 Ok(list_available_documents_from(
45 list_registered_documents()?,
46 manuals.pages(),
47 ))
48}
49
50fn list_available_documents_from(
51 registered: Vec<RegisteredDocument>,
52 manuals: &[crate::ManualPage],
53) -> Vec<AvailableDocument> {
54 let mut documents = registered
55 .into_iter()
56 .map(|document| AvailableDocument {
57 name: document.name,
58 kind: AvailableDocumentKind::Markdown,
59 section: None,
60 path: document.path,
61 origin: match document.origin {
62 RegisteredDocumentOrigin::Documents => AvailableDocumentOrigin::Documents,
63 RegisteredDocumentOrigin::Source(source) => AvailableDocumentOrigin::Source(source),
64 },
65 })
66 .chain(manuals.iter().map(|page| AvailableDocument {
67 name: page.name.clone(),
68 kind: AvailableDocumentKind::Manual,
69 section: Some(page.section.clone()),
70 path: page.path.clone(),
71 origin: AvailableDocumentOrigin::ManualPath,
72 }))
73 .collect::<Vec<_>>();
74 documents.sort_by(|left, right| {
75 (&left.name, left.kind, &left.section).cmp(&(&right.name, right.kind, &right.section))
76 });
77 documents
78}
79
80#[cfg(test)]
81mod tests {
82 use std::path::PathBuf;
83
84 use mant_sources::{RegisteredDocument, RegisteredDocumentOrigin};
85
86 use crate::ManualPage;
87
88 use super::{AvailableDocumentKind, AvailableDocumentOrigin, list_available_documents_from};
89
90 #[test]
91 fn merges_both_namespaces_without_hiding_manual_sections() {
92 let documents = list_available_documents_from(
93 vec![RegisteredDocument {
94 name: "printf".to_owned(),
95 path: PathBuf::from("/home/demo/.local/share/mant/documents/printf.md"),
96 origin: RegisteredDocumentOrigin::Documents,
97 }],
98 &[
99 ManualPage {
100 name: "printf".to_owned(),
101 section: "1".to_owned(),
102 path: PathBuf::from("/usr/share/man/man1/printf.1.gz"),
103 manual_root: PathBuf::from("/usr/share/man"),
104 },
105 ManualPage {
106 name: "printf".to_owned(),
107 section: "3".to_owned(),
108 path: PathBuf::from("/usr/share/man/man3/printf.3.gz"),
109 manual_root: PathBuf::from("/usr/share/man"),
110 },
111 ],
112 );
113
114 assert_eq!(documents.len(), 3);
115 assert_eq!(documents[0].kind, AvailableDocumentKind::Markdown);
116 assert_eq!(documents[0].origin, AvailableDocumentOrigin::Documents);
117 assert_eq!(documents[1].section.as_deref(), Some("1"));
118 assert_eq!(documents[2].section.as_deref(), Some("3"));
119 }
120
121 #[test]
122 fn keeps_shadowed_markdown_candidates_in_fallback_order() {
123 let documents = list_available_documents_from(
124 vec![
125 RegisteredDocument {
126 name: "tool".to_owned(),
127 path: PathBuf::from("/data/mant/documents/tool.md"),
128 origin: RegisteredDocumentOrigin::Documents,
129 },
130 RegisteredDocument {
131 name: "tool".to_owned(),
132 path: PathBuf::from("/data/mant/documents/sources/alpha/tool.md"),
133 origin: RegisteredDocumentOrigin::Source("alpha".to_owned()),
134 },
135 ],
136 &[],
137 );
138 assert_eq!(documents.len(), 2);
139 assert_eq!(documents[0].origin, AvailableDocumentOrigin::Documents);
140 assert_eq!(
141 documents[1].origin,
142 AvailableDocumentOrigin::Source("alpha".to_owned())
143 );
144 }
145}