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