ironflow_cli/commands/
template.rs1use std::path::{Path, PathBuf};
4
5use anyhow::Result;
6use clap::{Args, Subcommand};
7
8use ironflow_templates::fetch::fetch_repo;
9use ironflow_templates::install::install_template;
10use ironflow_templates::registry::{discover_templates, find_template};
11
12#[derive(Debug, Args)]
14pub struct TemplateArgs {
15 #[command(subcommand)]
17 pub command: TemplateCommands,
18}
19
20#[derive(Debug, Subcommand)]
22pub enum TemplateCommands {
23 List {
25 source: String,
27 },
28 Info {
30 source: String,
32 name: String,
34 },
35 Add {
37 source: String,
39 name: String,
41 #[arg(long, short)]
43 output: Option<PathBuf>,
44 },
45}
46
47pub fn execute(args: &TemplateArgs) -> Result<()> {
53 match &args.command {
54 TemplateCommands::List { source } => cmd_list(source),
55 TemplateCommands::Info { source, name } => cmd_info(source, name),
56 TemplateCommands::Add {
57 source,
58 name,
59 output,
60 } => cmd_add(source, name, output.as_deref()),
61 }
62}
63
64fn resolve_source(source: &str) -> Result<ResolvedSource> {
70 if source.contains("://") {
71 let tmp = fetch_repo(source)?;
72 Ok(ResolvedSource::Cloned(tmp))
73 } else {
74 let path = PathBuf::from(source);
75 if !path.exists() {
76 anyhow::bail!("path does not exist: {source}");
77 }
78 Ok(ResolvedSource::Local(path))
79 }
80}
81
82enum ResolvedSource {
83 Local(PathBuf),
84 Cloned(tempfile::TempDir),
85}
86
87impl ResolvedSource {
88 fn path(&self) -> &Path {
89 match self {
90 ResolvedSource::Local(p) => p,
91 ResolvedSource::Cloned(tmp) => tmp.path(),
92 }
93 }
94}
95
96fn cmd_list(source: &str) -> Result<()> {
97 let resolved = resolve_source(source)?;
98 let templates = discover_templates(resolved.path())?;
99
100 if templates.is_empty() {
101 println!("No templates found in {source}");
102 return Ok(());
103 }
104
105 println!("Available templates:");
106 println!();
107 for (name, (manifest, _dir)) in &templates {
108 println!(" {name} (v{})", manifest.template.version);
109 println!(" {}", manifest.template.description);
110 if let Some(cat) = &manifest.template.category {
111 println!(" category: {cat}");
112 }
113 println!();
114 }
115
116 Ok(())
117}
118
119fn cmd_info(source: &str, name: &str) -> Result<()> {
120 let resolved = resolve_source(source)?;
121 let (manifest, _dir) = find_template(resolved.path(), name)?;
122
123 println!("Template: {}", manifest.template.name);
124 println!("Version: {}", manifest.template.version);
125 println!("Description: {}", manifest.template.description);
126
127 if !manifest.template.authors.is_empty() {
128 println!("Authors: {}", manifest.template.authors.join(", "));
129 }
130 if let Some(license) = &manifest.template.license {
131 println!("License: {license}");
132 }
133 if let Some(cat) = &manifest.template.category {
134 println!("Category: {cat}");
135 }
136
137 if !manifest.dependencies.is_empty() {
138 println!();
139 println!("Dependencies:");
140 let mut deps: Vec<_> = manifest.dependencies.keys().collect();
141 deps.sort();
142 for dep in deps {
143 println!(" - {dep}");
144 }
145 }
146
147 Ok(())
148}
149
150fn cmd_add(source: &str, name: &str, output: Option<&Path>) -> Result<()> {
151 let resolved = resolve_source(source)?;
152 let (manifest, template_dir) = find_template(resolved.path(), name)?;
153
154 let destination = match output {
155 Some(path) => path.to_path_buf(),
156 None => PathBuf::from(format!("src/workflows/{name}")),
157 };
158
159 let result = install_template(&manifest, &template_dir, &destination)?;
160
161 println!(
162 "Template '{name}' installed to {}",
163 result.destination.display()
164 );
165
166 if !result.dependencies.is_empty() {
167 println!();
168 println!("Add these dependencies to your Cargo.toml:");
169 println!();
170 for dep in &result.dependencies {
171 println!(" {dep}");
172 }
173 }
174
175 Ok(())
176}