Skip to main content

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::fixture_manager_fixtures().map(|fixture| (fixture.name(), fixture)),
138            )
139        } else {
140            let fixtures = self
141                .fixtures
142                .iter()
143                .map(|name| {
144                    let fixture = JsonFixture::by_name(name)
145                        .ok_or_else(|| anyhow!("unknown fixture: {}", name))?;
146                    Ok((name.as_str(), fixture))
147                })
148                .collect::<Result<Vec<_>>>()?;
149            Box::new(fixtures.into_iter())
150        };
151
152        let mut num_changed = 0;
153
154        for (name, fixture) in fixtures {
155            println!("generating outputs for {name}...");
156
157            let context: GenerateContext<'_, T> =
158                GenerateContext::new(fixture, &args, self.mode == GenerateMode::Force)?;
159            for item in context {
160                let item = item?;
161                let is_changed = item.is_changed();
162
163                if is_changed {
164                    num_changed += 1;
165                }
166
167                if self.mode == GenerateMode::Check {
168                    if is_changed {
169                        println!("** {}:\n{}", item.path(), item.diff());
170                    }
171
172                    continue;
173                }
174
175                if is_changed || self.mode == GenerateMode::Force {
176                    item.write_to_path()?;
177                }
178            }
179        }
180
181        if self.mode == GenerateMode::Check && num_changed > 0 {
182            bail!("{} outputs changed", num_changed);
183        }
184
185        println!("{num_changed} outputs changed");
186
187        Ok(())
188    }
189}