Skip to main content

systemprompt_cli/commands/web/assets/
list.rs

1use anyhow::{Context, Result};
2use chrono::{DateTime, Utc};
3use clap::{Args, ValueEnum};
4use walkdir::WalkDir;
5
6use crate::CliConfig;
7use crate::shared::CommandOutput;
8
9use super::super::paths::WebPaths;
10use super::super::types::{AssetSummary, AssetType};
11use super::asset_type::determine_asset_type;
12
13#[derive(Debug, Clone, Copy, ValueEnum, Default)]
14pub enum AssetTypeFilter {
15    #[default]
16    All,
17    Css,
18    Logo,
19    Favicon,
20    Font,
21    Image,
22}
23
24#[derive(Debug, Clone, Copy, Args)]
25pub struct ListArgs {
26    #[arg(long, value_enum, default_value = "all", help = "Filter by asset type")]
27    pub asset_type: AssetTypeFilter,
28}
29
30pub(super) fn execute(args: ListArgs, config: &CliConfig) -> Result<CommandOutput> {
31    execute_in_dir(args, config, &WebPaths::resolve()?.assets)
32}
33
34pub fn execute_in_dir(
35    args: ListArgs,
36    _config: &CliConfig,
37    assets_dir: &std::path::Path,
38) -> Result<CommandOutput> {
39    if !assets_dir.exists() {
40        let empty: Vec<AssetSummary> = vec![];
41        return Ok(CommandOutput::table_of(
42            vec!["path", "asset_type", "size_bytes", "modified"],
43            &empty,
44        )
45        .with_title("Assets"));
46    }
47
48    let mut assets: Vec<AssetSummary> = Vec::new();
49
50    for entry in WalkDir::new(assets_dir)
51        .follow_links(true)
52        .into_iter()
53        .filter_map(Result::ok)
54    {
55        let path = entry.path();
56
57        if !path.is_file() {
58            continue;
59        }
60
61        let relative_path = path
62            .strip_prefix(assets_dir)
63            .unwrap_or(path)
64            .to_string_lossy()
65            .to_string();
66
67        let asset_type = determine_asset_type(path, &relative_path);
68
69        if !matches_filter(asset_type, args.asset_type) {
70            continue;
71        }
72
73        let metadata = path.metadata().context("Failed to get file metadata")?;
74        let size_bytes = metadata.len();
75        let modified = metadata.modified().ok().map_or_else(
76            || "unknown".to_owned(),
77            |t| {
78                let datetime: DateTime<Utc> = t.into();
79                datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string()
80            },
81        );
82
83        assets.push(AssetSummary {
84            path: relative_path,
85            asset_type,
86            size_bytes,
87            modified,
88        });
89    }
90
91    assets.sort_by(|a, b| a.path.cmp(&b.path));
92
93    Ok(CommandOutput::table_of(
94        vec!["path", "asset_type", "size_bytes", "modified"],
95        &assets,
96    )
97    .with_title("Assets"))
98}
99
100fn matches_filter(asset_type: AssetType, filter: AssetTypeFilter) -> bool {
101    match filter {
102        AssetTypeFilter::All => true,
103        AssetTypeFilter::Css => asset_type == AssetType::Css,
104        AssetTypeFilter::Logo => asset_type == AssetType::Logo,
105        AssetTypeFilter::Favicon => asset_type == AssetType::Favicon,
106        AssetTypeFilter::Font => asset_type == AssetType::Font,
107        AssetTypeFilter::Image => {
108            asset_type == AssetType::Image
109                || asset_type == AssetType::Logo
110                || asset_type == AssetType::Favicon
111        },
112    }
113}