Skip to main content

systemprompt_cli/commands/web/templates/
list.rs

1//! `web templates list` command.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use anyhow::{Context, Result};
7use clap::Args;
8use std::fs;
9use std::path::Path;
10
11use crate::CliConfig;
12use crate::shared::CommandOutput;
13
14use super::super::paths::WebPaths;
15use super::super::types::{TemplateSummary, TemplatesConfig};
16
17#[derive(Debug, Clone, Copy, Args)]
18pub struct ListArgs {
19    #[arg(long, help = "Show only templates with missing files")]
20    pub missing: bool,
21}
22
23pub(super) fn execute(args: ListArgs, config: &CliConfig) -> Result<CommandOutput> {
24    execute_in_dir(args, config, &WebPaths::resolve()?.templates)
25}
26
27pub fn execute_in_dir(
28    args: ListArgs,
29    _config: &CliConfig,
30    templates_dir: &Path,
31) -> Result<CommandOutput> {
32    let templates_yaml_path = templates_dir.join("templates.yaml");
33
34    if !templates_yaml_path.exists() {
35        let empty: Vec<TemplateSummary> = vec![];
36        return Ok(CommandOutput::table_of(
37            vec!["name", "content_types", "file_exists", "file_path"],
38            &empty,
39        )
40        .with_title("Templates"));
41    }
42
43    let content = fs::read_to_string(&templates_yaml_path).with_context(|| {
44        format!(
45            "Failed to read templates config at {}",
46            templates_yaml_path.display()
47        )
48    })?;
49
50    let config: TemplatesConfig = serde_yaml::from_str(&content).with_context(|| {
51        format!(
52            "Failed to parse templates config at {}",
53            templates_yaml_path.display()
54        )
55    })?;
56
57    let mut templates: Vec<TemplateSummary> = config
58        .templates
59        .iter()
60        .map(|(name, entry)| {
61            let file_path = templates_dir.join(format!("{}.html", name));
62            let file_exists = file_path.exists();
63
64            TemplateSummary {
65                name: name.clone(),
66                content_types: entry.content_types.clone(),
67                file_exists,
68                file_path: file_path.to_string_lossy().to_string(),
69            }
70        })
71        .filter(|t| !args.missing || !t.file_exists)
72        .collect();
73
74    templates.sort_by(|a, b| a.name.cmp(&b.name));
75
76    Ok(CommandOutput::table_of(
77        vec!["name", "content_types", "file_exists", "file_path"],
78        &templates,
79    )
80    .with_title("Templates"))
81}