Skip to main content

asimov_cli/commands/source/
fetch.rs

1// This is free and unencumbered software released into the public domain.
2
3use crate::{BoxError, StandardOptions, SysexitsError::*, shared};
4use asimov_module::{ModuleName, normalization::normalize_url, resolve::Resolver};
5use asimov_runner::{FetcherOptions, GraphOutput};
6use clientele::crates::clap::Args;
7use color_print::ceprintln;
8use miette::Result;
9
10#[derive(Args, Clone, Debug, Default)]
11pub struct SourceFetchArgs {
12    /// Optionally choose the module instead of using module resolution.
13    /// The module's manifest must declare support for the URL for the
14    /// module to be used.
15    #[clap(long, short = 'M')]
16    module: Option<ModuleName>,
17
18    /// The output format.
19    #[arg(value_name = "FORMAT", short = 'o', long)]
20    output: Option<String>,
21
22    urls: Vec<String>,
23}
24
25pub async fn fetch(args: SourceFetchArgs, flags: &StandardOptions) -> Result<(), BoxError> {
26    let registry = asimov_registry::Registry::default();
27
28    let installed_modules = shared::installed_modules(&registry, Some("fetcher")).await?;
29
30    let resolver = Resolver::try_from_iter(installed_modules.iter()).map_err(|e| {
31        ceprintln!("<s,r>error:</> failed to build resolver: {e}");
32        EX_UNAVAILABLE
33    })?;
34
35    for input_url in args.urls {
36        if flags.verbose > 1 {
37            ceprintln!("<s,c>»</> Fetching <s>{}</>...", input_url);
38        }
39
40        let input_url = normalize_url(&input_url).unwrap_or_else(|e| {
41            if flags.verbose > 1 {
42                ceprintln!(
43                    "<s,y>warning:</> using given unmodified URL, normalization failed: {e}"
44                );
45            }
46            input_url.clone()
47        });
48
49        let modules = resolver.resolve(&input_url).map_err(|e| {
50            ceprintln!("<s,r>error:</> unable to handle URL <s>{input_url}</>: {e}");
51            EX_USAGE
52        })?;
53
54        let module = shared::pick_module(
55            &registry,
56            &input_url,
57            modules.as_slice(),
58            args.module.as_deref(),
59        )
60        .await?;
61
62        let mut fetcher = asimov_runner::Fetcher::new(
63            format!("asimov-{}-fetcher", module.name),
64            &input_url,
65            GraphOutput::Inherited,
66            FetcherOptions::builder()
67                .maybe_output(args.output.as_deref())
68                .maybe_other(flags.debug.then_some("--debug"))
69                .build(),
70        );
71
72        let _ = fetcher.execute().await.map_err(|e| {
73            ceprintln!("<s,r>error:</> fetcher execution failed: {e}");
74            EX_UNAVAILABLE
75        })?;
76
77        if flags.verbose > 0 {
78            ceprintln!("<s,g>✓</> Fetched <s>{}</>.", input_url);
79        }
80    }
81
82    Ok(())
83}