fixture_manager/
lib.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4pub mod context;
5pub mod hakari_toml;
6pub mod summaries;
7
8use crate::{
9    context::{ContextImpl, GenerateContext},
10    hakari_toml::HakariTomlContext,
11    summaries::*,
12};
13use anyhow::{Result, anyhow, bail};
14use clap::{Parser, ValueEnum};
15use fixtures::json::JsonFixture;
16
17#[derive(Debug, Parser)]
18pub struct FixtureManager {
19    // TODO: add global options
20    #[clap(subcommand)]
21    cmd: Command,
22}
23
24impl FixtureManager {
25    pub fn exec(self) -> Result<()> {
26        match self.cmd {
27            Command::List => list(),
28            Command::GenerateSummaries(opts) => opts.exec(),
29            Command::GenerateHakari(opts) => opts.exec(),
30        }
31    }
32}
33
34#[derive(Debug, Parser)]
35enum Command {
36    #[clap(name = "list")]
37    /// List fixtures
38    List,
39    /// Generate summaries
40    GenerateSummaries(GenerateSummariesOpts),
41    /// Generate Hakari outputs
42    GenerateHakari(GenerateHakariOpts),
43}
44
45pub fn list() -> Result<()> {
46    for (name, fixture) in JsonFixture::all_fixtures().iter() {
47        println!("{}: {}", name, fixture.workspace_path());
48    }
49
50    Ok(())
51}
52
53#[derive(Debug, Parser)]
54pub struct GenerateSummariesOpts {
55    /// Number of summaries to generate
56    #[clap(long, default_value = Self::DEFAULT_COUNT_STR)]
57    pub count: usize,
58
59    #[clap(flatten)]
60    pub generate_opts: GenerateOpts,
61}
62
63impl GenerateSummariesOpts {
64    /// The default value of the `count` field, as a string.
65    pub const DEFAULT_COUNT_STR: &'static str = "8";
66
67    /// The default value of the `count` field.
68    pub fn default_count() -> usize {
69        Self::DEFAULT_COUNT_STR
70            .parse()
71            .expect("DEFAULT_COUNT_STR should parse as a usize")
72    }
73}
74
75#[derive(Debug, Parser)]
76pub struct GenerateHakariOpts {
77    /// Number of options to generate
78    #[clap(long, default_value = Self::DEFAULT_COUNT_STR)]
79    pub count: usize,
80
81    #[clap(flatten)]
82    pub generate_opts: GenerateOpts,
83}
84
85impl GenerateHakariOpts {
86    /// The default value of the `count` field, as a string.
87    pub const DEFAULT_COUNT_STR: &'static str = "4";
88
89    /// The default value of the `count` field.
90    pub fn default_count() -> usize {
91        Self::DEFAULT_COUNT_STR
92            .parse()
93            .expect("DEFAULT_COUNT_STR should parse as a usize")
94    }
95}
96
97#[derive(Debug, Parser)]
98pub struct GenerateOpts {
99    /// Execution mode (check, force or generate)
100    #[clap(
101        long,
102        short,
103        value_enum,
104        ignore_case = true,
105        default_value = "generate"
106    )]
107    pub mode: GenerateMode,
108
109    /// Only generate outputs for these fixtures
110    #[clap(long)]
111    pub fixtures: Vec<String>,
112}
113
114#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, ValueEnum)]
115pub enum GenerateMode {
116    Generate,
117    Check,
118    Force,
119}
120
121impl GenerateSummariesOpts {
122    pub fn exec(self) -> Result<()> {
123        self.generate_opts.exec::<SummaryContext>(self.count)
124    }
125}
126
127impl GenerateHakariOpts {
128    pub fn exec(self) -> Result<()> {
129        self.generate_opts.exec::<HakariTomlContext>(self.count)
130    }
131}
132
133impl GenerateOpts {
134    pub fn exec<'g, T: ContextImpl<'g>>(self, args: T::IterArgs) -> Result<()> {
135        let fixtures: Box<dyn Iterator<Item = (&str, &JsonFixture)>> = if self.fixtures.is_empty() {
136            Box::new(
137                JsonFixture::all_fixtures()
138                    .iter()
139                    .map(|(name, fixture)| (*name, fixture)),
140            )
141        } else {
142            let fixtures = self
143                .fixtures
144                .iter()
145                .map(|name| {
146                    let fixture = JsonFixture::by_name(name)
147                        .ok_or_else(|| anyhow!("unknown fixture: {}", name))?;
148                    Ok((name.as_str(), fixture))
149                })
150                .collect::<Result<Vec<_>>>()?;
151            Box::new(fixtures.into_iter())
152        };
153
154        let mut num_changed = 0;
155
156        for (name, fixture) in fixtures {
157            println!("generating outputs for {name}...");
158
159            let context: GenerateContext<'_, T> =
160                GenerateContext::new(fixture, &args, self.mode == GenerateMode::Force)?;
161            for item in context {
162                let item = item?;
163                let is_changed = item.is_changed();
164
165                if is_changed {
166                    num_changed += 1;
167                }
168
169                if self.mode == GenerateMode::Check {
170                    if is_changed {
171                        println!("** {}:\n{}", item.path(), item.diff());
172                    }
173
174                    continue;
175                }
176
177                if is_changed || self.mode == GenerateMode::Force {
178                    item.write_to_path()?;
179                }
180            }
181        }
182
183        if self.mode == GenerateMode::Check && num_changed > 0 {
184            bail!("{} outputs changed", num_changed);
185        }
186
187        println!("{num_changed} outputs changed");
188
189        Ok(())
190    }
191}