Skip to main content

aperture_cli/cli/commands/
docs.rs

1//! Handlers for `aperture docs`, `aperture overview`, and `aperture list-commands`.
2
3use crate::cache::models::CachedSpec;
4use crate::config::manager::{get_config_dir, ConfigManager};
5use crate::constants;
6use crate::docs::{DocumentationGenerator, HelpFormatter};
7use crate::engine::loader;
8use crate::error::Error;
9use crate::fs::OsFileSystem;
10use crate::output::Output;
11use std::path::PathBuf;
12
13pub fn list_commands(context: &str, output: &Output) -> Result<(), Error> {
14    let config_dir = if let Ok(dir) = std::env::var(constants::ENV_APERTURE_CONFIG_DIR) {
15        PathBuf::from(dir)
16    } else {
17        get_config_dir()?
18    };
19    let cache_dir = config_dir.join(constants::DIR_CACHE);
20    let spec = loader::load_cached_spec(&cache_dir, context).map_err(|e| match e {
21        Error::Io(_) => Error::spec_not_found(context),
22        _ => e,
23    })?;
24    let formatted_output = HelpFormatter::format_command_list(&spec);
25    // ast-grep-ignore: no-println
26    println!("{formatted_output}");
27    output.tip(format!(
28        "Use 'aperture docs {context}' for detailed API documentation"
29    ));
30    output.tip(format!(
31        "Use 'aperture search <term> --api {context}' to find specific operations"
32    ));
33    output.tip("Use shortcuts: 'aperture exec <operation-id> --help'");
34    Ok(())
35}
36
37/// Execute help command with enhanced documentation
38pub fn execute_help_command(
39    manager: &ConfigManager<OsFileSystem>,
40    api_name: Option<&str>,
41    tag: Option<&str>,
42    operation: Option<&str>,
43    enhanced: bool,
44    output: &Output,
45) -> Result<(), Error> {
46    match (api_name, tag, operation) {
47        (None, None, None) => {
48            let specs = load_all_specs(manager)?;
49            let doc_gen = DocumentationGenerator::new(specs);
50            // ast-grep-ignore: no-println
51            println!("{}", doc_gen.generate_interactive_menu());
52        }
53        (Some(api), tag_opt, operation_opt) => {
54            let specs = load_all_specs(manager)?;
55            let doc_gen = DocumentationGenerator::new(specs);
56            match (tag_opt, operation_opt) {
57                (None, None) => {
58                    let overview = doc_gen.generate_api_overview(api)?;
59                    // ast-grep-ignore: no-println
60                    println!("{overview}");
61                }
62                (Some(tag), Some(op)) => {
63                    let help = doc_gen.generate_command_help(api, tag, op)?;
64                    if enhanced {
65                        // ast-grep-ignore: no-println
66                        println!("{help}");
67                    } else {
68                        // ast-grep-ignore: no-println
69                        println!("{}", help.lines().take(20).collect::<Vec<_>>().join("\n"));
70                        output.tip("Use --enhanced for full documentation with examples");
71                    }
72                }
73                _ => {
74                    // Must appear regardless of APERTURE_LOG; tracing may suppress at low levels.
75                    // ast-grep-ignore: no-println
76                    eprintln!("Invalid docs command. Usage:");
77                    // ast-grep-ignore: no-println
78                    eprintln!("  aperture docs                        # Interactive menu");
79                    // ast-grep-ignore: no-println
80                    eprintln!("  aperture docs <api>                  # API overview");
81                    // ast-grep-ignore: no-println
82                    eprintln!("  aperture docs <api> <tag> <operation> # Command help");
83                    std::process::exit(1);
84                }
85            }
86        }
87        _ => {
88            // Must appear regardless of APERTURE_LOG; tracing may suppress at low levels.
89            // ast-grep-ignore: no-println
90            eprintln!("Invalid help command arguments");
91            std::process::exit(1);
92        }
93    }
94    Ok(())
95}
96
97/// Execute overview command
98#[allow(clippy::too_many_lines)]
99pub fn execute_overview_command(
100    manager: &ConfigManager<OsFileSystem>,
101    api_name: Option<&str>,
102    all: bool,
103    output: &Output,
104) -> Result<(), Error> {
105    if !all {
106        let Some(api) = api_name else {
107            // Must appear regardless of APERTURE_LOG; tracing may suppress at low levels.
108            // ast-grep-ignore: no-println
109            eprintln!("Error: Must specify API name or use --all flag");
110            // ast-grep-ignore: no-println
111            eprintln!("Usage:");
112            // ast-grep-ignore: no-println
113            eprintln!("  aperture overview <api>");
114            // ast-grep-ignore: no-println
115            eprintln!("  aperture overview --all");
116            std::process::exit(1);
117        };
118        let specs = load_all_specs(manager)?;
119        let doc_gen = DocumentationGenerator::new(specs);
120        let overview = doc_gen.generate_api_overview(api)?;
121        // ast-grep-ignore: no-println
122        println!("{overview}");
123        return Ok(());
124    }
125
126    let specs = load_all_specs(manager)?;
127    if specs.is_empty() {
128        output.info("No API specifications configured.");
129        output.info("Use 'aperture config add <name> <spec-file>' to get started.");
130        return Ok(());
131    }
132
133    // ast-grep-ignore: no-println
134    println!("All APIs Overview\n");
135    // ast-grep-ignore: no-println
136    println!("{}", "=".repeat(60));
137    for (api_name, spec) in &specs {
138        // ast-grep-ignore: no-println
139        println!("\n** {} ** (v{})", spec.name, spec.version);
140        if let Some(ref base_url) = spec.base_url {
141            // ast-grep-ignore: no-println
142            println!("   Base URL: {base_url}");
143        }
144        let operation_count = spec.commands.len();
145        // ast-grep-ignore: no-println
146        println!("   Operations: {operation_count}");
147        let mut method_counts = std::collections::BTreeMap::new();
148        for command in &spec.commands {
149            *method_counts.entry(command.method.clone()).or_insert(0) += 1;
150        }
151        let method_summary: Vec<String> = method_counts
152            .iter()
153            .map(|(method, count)| format!("{method}: {count}"))
154            .collect();
155        // ast-grep-ignore: no-println
156        println!("   Methods: {}", method_summary.join(", "));
157        // ast-grep-ignore: no-println
158        println!("   Quick start: aperture list-commands {api_name}");
159    }
160    // ast-grep-ignore: no-println
161    println!("\n{}", "=".repeat(60));
162    output.tip("Use 'aperture overview <api>' for detailed information about a specific API");
163    Ok(())
164}
165
166/// Load all cached specs from the manager
167pub fn load_all_specs(
168    manager: &ConfigManager<OsFileSystem>,
169) -> Result<std::collections::BTreeMap<String, CachedSpec>, Error> {
170    let specs = manager.list_specs()?;
171    let cache_dir = manager.config_dir().join(constants::DIR_CACHE);
172    let mut all_specs = std::collections::BTreeMap::new();
173    for spec_name in &specs {
174        match loader::load_cached_spec(&cache_dir, spec_name) {
175            Ok(spec) => {
176                all_specs.insert(spec_name.clone(), spec);
177            }
178            Err(e) => tracing::warn!(spec = spec_name, error = %e, "could not load spec"),
179        }
180    }
181    Ok(all_specs)
182}