Skip to main content

lexicon_docx/
lib.rs

1pub mod error;
2pub mod frontmatter;
3pub mod model;
4pub mod parser;
5pub mod render;
6pub mod resolve;
7pub mod signatures;
8pub mod style;
9
10use std::path::{Path, PathBuf};
11
12use error::{Diagnostic, Result};
13use model::{Document, Status};
14use signatures::SignatureBlock;
15use style::StyleConfig;
16
17pub fn parse(input: &str) -> Result<Document> {
18    parser::parse(input)
19}
20
21pub fn resolve(doc: &mut Document) {
22    resolve::resolve(doc);
23}
24
25pub fn render_docx(
26    doc: &Document,
27    style: &StyleConfig,
28    input_dir: Option<&Path>,
29    signature_blocks: &[SignatureBlock],
30) -> Result<Vec<u8>> {
31    render::docx::render_docx(doc, style, input_dir, signature_blocks)
32}
33
34/// Resolve a config file path by searching:
35/// 1. The input document's directory
36/// 2. $XDG_CONFIG_HOME/lexicon/ (defaults to ~/.config/lexicon/)
37///
38/// Returns the first path that exists, or None.
39pub fn resolve_config_path(filename: &str, input_dir: Option<&Path>) -> Option<PathBuf> {
40    // 1. Same directory as the input document
41    if let Some(dir) = input_dir {
42        let local = dir.join(filename);
43        if local.exists() {
44            return Some(local);
45        }
46    }
47
48    // 2. $XDG_CONFIG_HOME/lexicon/ or ~/.config/lexicon/
49    let config_dir = std::env::var("XDG_CONFIG_HOME")
50        .map(PathBuf::from)
51        .unwrap_or_else(|_| {
52            let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
53            PathBuf::from(home).join(".config")
54        })
55        .join("lexicon");
56
57    let global = config_dir.join(filename);
58    if global.exists() {
59        return Some(global);
60    }
61
62    None
63}
64
65pub fn process(
66    input: &str,
67    style: &StyleConfig,
68    input_dir: Option<&Path>,
69    signatures_path: Option<&Path>,
70) -> Result<(Vec<u8>, Vec<Diagnostic>)> {
71    let mut doc = parse(input)?;
72    resolve(&mut doc);
73
74    // Resolve signature blocks if enabled
75    let mut sig_diagnostics = Vec::new();
76    let signature_blocks = if style.signatures.enabled {
77        let definitions = match signatures_path {
78            Some(path) => signatures::load_definitions(path, &mut sig_diagnostics),
79            None => {
80                sig_diagnostics.push(Diagnostic {
81                    level: error::DiagLevel::Warning,
82                    message: "Signatures enabled but no definitions file found (searched input directory and $XDG_CONFIG_HOME/lexicon/)".to_string(),
83                    location: None,
84                });
85                None
86            }
87        };
88
89        signatures::resolve_signature_blocks(
90            &doc.meta.parties,
91            doc.meta.doc_type.as_deref(),
92            style,
93            &definitions,
94            &mut sig_diagnostics,
95        )
96    } else {
97        vec![]
98    };
99
100    doc.diagnostics.extend(sig_diagnostics);
101
102    let mut bytes = render_docx(&doc, style, input_dir, &signature_blocks)?;
103    if doc.meta.status == Some(Status::Draft) {
104        bytes = render::watermark::inject_watermark(bytes, "DRAFT")?;
105    }
106    Ok((bytes, doc.diagnostics))
107}