Skip to main content

automd_rs/
handler.rs

1//! Block dispatcher: parses tag options, routes to generators, fills block body.
2
3use crate::error::Result;
4use crate::generators::badges::{self as badges_gen, BadgesConfig};
5use crate::generators::cargo_add::{self as cargo_add_gen};
6use crate::generators::cargo_install::{self as cargo_install_gen};
7use crate::generators::contributors::{self as contributors_gen, ContributorsConfig};
8use crate::generators::description::{self as description_gen};
9use crate::generators::file::{self as file_gen};
10use crate::generators::with_automdrs::{self as with_automdrs_gen, WithAutomdrsConfig};
11use crate::parser::cargo::ParsedManifest;
12use crate::parser::tag_options::{option_bool, parse_tag_options};
13use urlogger::{LogLevel, log};
14
15/// Context passed to block handlers (parsed Cargo.toml).
16#[derive(Debug, Clone)]
17pub struct UpdateContext {
18    pub config: ParsedManifest,
19    /// Crate root directory for resolving relative paths (e.g. in `file` block).
20    pub manifest_dir: std::path::PathBuf,
21}
22
23impl UpdateContext {
24    pub fn new(config: ParsedManifest, manifest_dir: std::path::PathBuf) -> Self {
25        Self {
26            config,
27            manifest_dir,
28        }
29    }
30}
31
32/// Trait for generating block content by block name.
33pub trait BlockHandler: Send + Sync {
34    fn generate(
35        &self,
36        block_name: &str,
37        open_tag_line: &str,
38        context: &UpdateContext,
39    ) -> Result<Vec<String>>;
40}
41
42fn parse_badges_config(open_tag: &str) -> BadgesConfig {
43    let opts = parse_tag_options(open_tag, "badges");
44    log!(LogLevel::Trace, "badges config: {:?}", opts);
45    BadgesConfig {
46        version: option_bool(&opts, &["showCrateVersion", "version"]),
47        downloads: option_bool(&opts, &["showCrateDownloads", "downloads"]),
48        docs: option_bool(&opts, &["showCrateDocs", "docs"]),
49        commit_activity: option_bool(&opts, &["showCommitActivity", "commit_activity"]),
50        repo_stars: option_bool(&opts, &["showRepoStars", "repo_stars"]),
51    }
52}
53
54fn parse_contributors_config(open_tag: &str) -> ContributorsConfig {
55    let opts = parse_tag_options(open_tag, "contributors");
56    log!(LogLevel::Trace, "contributors config: {:?}", opts);
57    ContributorsConfig {
58        author: opts.get("author").cloned().unwrap_or_default(),
59        license: opts.get("license").cloned().unwrap_or_default(),
60    }
61}
62
63fn parse_with_automdrs_config(open_tag: &str) -> WithAutomdrsConfig {
64    let opts = parse_tag_options(open_tag, "with-automdrs");
65    log!(LogLevel::Trace, "with-automdrs config: {:?}", opts);
66    WithAutomdrsConfig {
67        message: opts.get("message").cloned().unwrap_or_default(),
68    }
69}
70
71/// Default handler for built-in blocks: badges, contributors, with-automdrs, cargo-install, cargo-add.
72#[derive(Debug, Default)]
73pub struct DefaultHandler;
74
75impl BlockHandler for DefaultHandler {
76    fn generate(
77        &self,
78        block_name: &str,
79        open_tag_line: &str,
80        context: &UpdateContext,
81    ) -> Result<Vec<String>> {
82        match block_name {
83            "badges" => {
84                let config = parse_badges_config(open_tag_line);
85                Ok(badges_gen::generate(&config, &context.config))
86            }
87            "contributors" => {
88                let config = parse_contributors_config(open_tag_line);
89                Ok(contributors_gen::generate(&config, &context.config))
90            }
91            "with-automdrs" => {
92                let config = parse_with_automdrs_config(open_tag_line);
93                Ok(with_automdrs_gen::generate(&config))
94            }
95            "cargo-install" => Ok(cargo_install_gen::generate(&context.config)),
96            "cargo-add" => Ok(cargo_add_gen::generate(&context.config)),
97            "description" => Ok(description_gen::generate(&context.config)),
98            "file" => {
99                let opts = parse_tag_options(open_tag_line, "file");
100                let src = opts.get("src").cloned().unwrap_or_default();
101                Ok(file_gen::generate(&context.manifest_dir, &src)?)
102            }
103            _ => Ok(vec![]),
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::parser::cargo::ParsedManifest;
112
113    fn context() -> UpdateContext {
114        UpdateContext::new(
115            ParsedManifest {
116                name: "test-crate".to_string(),
117                description: "d".to_string(),
118                username: "u".to_string(),
119                repository_name: "r".to_string(),
120            },
121            std::path::PathBuf::from("."),
122        )
123    }
124
125    #[test]
126    fn test_update_context_new() {
127        let ctx = context();
128        assert_eq!(ctx.config.name, "test-crate");
129    }
130
131    #[test]
132    fn test_generate_badges() {
133        let h = DefaultHandler::default();
134        let out = h
135            .generate(
136                "badges",
137                "<!-- automdrs:badges version docs -->",
138                &context(),
139            )
140            .unwrap();
141        assert!(!out.is_empty());
142        assert!(out[0].contains("crates/v/test-crate"));
143    }
144
145    #[test]
146    fn test_generate_contributors() {
147        let h = DefaultHandler::default();
148        let out = h
149            .generate(
150                "contributors",
151                "<!-- automdrs:contributors author=\"A\" license=\"MIT\" -->",
152                &context(),
153            )
154            .unwrap();
155        assert_eq!(out.len(), 1);
156        assert!(out[0].contains("A"));
157        assert!(out[0].contains("MIT"));
158    }
159
160    #[test]
161    fn test_generate_with_automdrs() {
162        let h = DefaultHandler::default();
163        let out = h
164            .generate(
165                "with-automdrs",
166                "<!-- automdrs:with-automdrs -->",
167                &context(),
168            )
169            .unwrap();
170        assert_eq!(out.len(), 1);
171        assert!(out[0].contains("automd-rs"));
172    }
173
174    #[test]
175    fn test_generate_unknown_block() {
176        let h = DefaultHandler::default();
177        let out = h
178            .generate("unknown", "<!-- automdrs:unknown -->", &context())
179            .unwrap();
180        assert_eq!(out, Vec::<String>::new());
181    }
182
183    #[test]
184    fn test_generate_file() {
185        let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
186        let ctx = UpdateContext::new(
187            ParsedManifest {
188                name: "test".to_string(),
189                description: "d".to_string(),
190                username: "u".to_string(),
191                repository_name: "r".to_string(),
192            },
193            manifest_dir.to_path_buf(),
194        );
195        let h = DefaultHandler::default();
196        let out = h
197            .generate("file", "<!-- automdrs:file src=\"./src/main.rs\" -->", &ctx)
198            .unwrap();
199        assert!(!out.is_empty());
200        assert_eq!(out[0], "```rust");
201    }
202}