1use std::path::{Path, PathBuf};
17
18use anyhow::{Context, Result};
19use gize_core::Manifest;
20
21use crate::plan::Plan;
22use crate::writer::{Options, Report, Writer};
23
24#[derive(Debug, Clone)]
26pub struct GenContext {
27 pub manifest: Manifest,
29 pub root: PathBuf,
31}
32
33impl GenContext {
34 pub fn from_root(root: impl Into<PathBuf>) -> Result<Self> {
36 let root = root.into();
37 let manifest_path = root.join("gize.toml");
38 let text = std::fs::read_to_string(&manifest_path)
39 .with_context(|| format!("reading {}", manifest_path.display()))?;
40 let manifest = Manifest::from_toml(&text)?;
41 Ok(Self { manifest, root })
42 }
43
44 pub fn from_current_dir() -> Result<Self> {
46 Self::from_root(Path::new("."))
47 }
48}
49
50pub trait Generator {
52 fn name(&self) -> &str;
54
55 fn plan(&self, ctx: &GenContext, args: &[String]) -> Result<Plan>;
58}
59
60pub fn run(generator: &dyn Generator, args: &[String], opts: Options) -> Result<Report> {
64 let ctx = GenContext::from_current_dir().context("not a gize project here (no gize.toml)")?;
65 let plan = generator
66 .plan(&ctx, args)
67 .with_context(|| format!("plugin `{}` failed to build its plan", generator.name()))?;
68 Writer::new(opts).apply(&ctx.root, &plan)
69}
70
71pub fn run_in(
73 ctx: &GenContext,
74 generator: &dyn Generator,
75 args: &[String],
76 opts: Options,
77) -> Result<Report> {
78 let plan = generator.plan(ctx, args)?;
79 Writer::new(opts).apply(&ctx.root, &plan)
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 struct Dummy;
87 impl Generator for Dummy {
88 fn name(&self) -> &str {
89 "dummy"
90 }
91 fn plan(&self, _ctx: &GenContext, _args: &[String]) -> Result<Plan> {
92 Ok(Plan::new().create("generated/by_plugin.txt", "hello from a plugin\n"))
93 }
94 }
95
96 #[test]
97 fn a_plugin_generates_through_the_safe_writer() {
98 use std::sync::atomic::{AtomicUsize, Ordering};
99 static COUNTER: AtomicUsize = AtomicUsize::new(0);
100 let root = std::env::temp_dir().join(format!(
101 "gize-plugin-{}-{}",
102 std::process::id(),
103 COUNTER.fetch_add(1, Ordering::Relaxed)
104 ));
105 std::fs::create_dir_all(&root).unwrap();
106 std::fs::write(root.join("gize.toml"), "[project]\nname = \"demo\"\n").unwrap();
107
108 let ctx = GenContext::from_root(&root).unwrap();
109 let report = run_in(&ctx, &Dummy, &[], Options::default()).unwrap();
110 assert_eq!(report.created, vec!["generated/by_plugin.txt".to_string()]);
111 assert!(root.join("generated/by_plugin.txt").is_file());
112
113 let again = run_in(&ctx, &Dummy, &[], Options::default()).unwrap();
115 assert_eq!(again.skipped, vec!["generated/by_plugin.txt".to_string()]);
116 }
117}