Skip to main content

systemprompt_cli/commands/web/sitemap/
show.rs

1//! `web sitemap show` command with XML preview.
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;
9
10use crate::CliConfig;
11use crate::shared::CommandOutput;
12use systemprompt_config::ProfileBootstrap;
13use systemprompt_logging::CliService;
14use systemprompt_models::content_config::ContentConfigRaw;
15
16use super::super::types::SitemapRoute;
17
18#[derive(Debug, Clone, Copy, Args)]
19pub struct ShowArgs {
20    #[arg(long, help = "Show XML preview")]
21    pub preview: bool,
22}
23
24pub(super) fn execute(args: ShowArgs, config: &CliConfig) -> Result<CommandOutput> {
25    let profile = ProfileBootstrap::get().context("Failed to get profile")?;
26    execute_with_config_path(args, config, profile.paths.content_config().as_str())
27}
28
29pub fn execute_with_config_path(
30    args: ShowArgs,
31    _config: &CliConfig,
32    content_config_path: &str,
33) -> Result<CommandOutput> {
34    let content = fs::read_to_string(content_config_path)
35        .with_context(|| format!("Failed to read content config at {}", content_config_path))?;
36
37    let config: ContentConfigRaw = serde_yaml::from_str(&content)
38        .with_context(|| format!("Failed to parse content config at {}", content_config_path))?;
39
40    let mut routes: Vec<SitemapRoute> = Vec::new();
41
42    for (name, source) in &config.content_sources {
43        if !source.enabled {
44            continue;
45        }
46
47        if let Some(sitemap) = &source.sitemap {
48            if !sitemap.enabled {
49                continue;
50            }
51
52            if let Some(parent) = &sitemap.parent_route
53                && parent.enabled
54            {
55                routes.push(SitemapRoute {
56                    url: parent.url.clone(),
57                    priority: parent.priority,
58                    changefreq: parent.changefreq.clone(),
59                    source: format!("{} (parent)", name),
60                });
61            }
62
63            routes.push(SitemapRoute {
64                url: sitemap.url_pattern.clone(),
65                priority: sitemap.priority,
66                changefreq: sitemap.changefreq.clone(),
67                source: name.clone(),
68            });
69        }
70    }
71
72    routes.sort_by(|a, b| {
73        b.priority
74            .partial_cmp(&a.priority)
75            .unwrap_or(std::cmp::Ordering::Equal)
76    });
77
78    if args.preview {
79        let xml = generate_xml_preview(&routes);
80        CliService::output(&xml);
81    }
82
83    Ok(
84        CommandOutput::table_of(vec!["url", "priority", "changefreq", "source"], &routes)
85            .with_title("Sitemap Configuration"),
86    )
87}
88
89fn generate_xml_preview(routes: &[SitemapRoute]) -> String {
90    let mut xml = String::from(concat!(
91        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
92        "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"
93    ));
94
95    for route in routes {
96        xml.push_str(&format!(
97            "  <url>\n    <loc>{}</loc>\n    <priority>{:.1}</priority>\n    \
98             <changefreq>{}</changefreq>\n  </url>\n",
99            route.url, route.priority, route.changefreq
100        ));
101    }
102
103    xml.push_str("</urlset>\n");
104    xml
105}