Skip to main content

fixture_manager/
hakari_toml.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::context::ContextImpl;
5use anyhow::Result;
6use camino::{Utf8Path, Utf8PathBuf};
7use fixtures::json::*;
8use hakari::{Hakari, HakariBuilder, HakariCargoToml, HakariOutputOptions, diffy::PatchFormatter};
9use once_cell::sync::Lazy;
10use proptest::prelude::*;
11use proptest_ext::ValueGenerator;
12
13pub struct HakariTomlContext;
14
15impl<'g> ContextImpl<'g> for HakariTomlContext {
16    type IterArgs = usize;
17    type IterItem = (usize, HakariTomlItem<'g>);
18    type Existing = HakariCargoToml;
19
20    fn dir_name(fixture: &'g JsonFixture) -> Utf8PathBuf {
21        fixture
22            .abs_path()
23            .parent()
24            .expect("up to dirname of summary")
25            .join("hakari")
26    }
27
28    fn file_name(fixture: &'g JsonFixture, &(count, _): &Self::IterItem) -> String {
29        format!("{}-{}.toml", fixture.name(), count)
30    }
31
32    fn iter(
33        fixture: &'g JsonFixture,
34        &count: &Self::IterArgs,
35    ) -> Box<dyn Iterator<Item = Self::IterItem> + 'g> {
36        // Make a fresh generator for each output so that filtering by --fixtures continues to
37        // produce deterministic results.
38        let mut generator = ValueGenerator::from_seed(fixture.name());
39
40        let graph = fixture.graph();
41        let hakari_builder_strategy =
42            HakariBuilder::proptest1_strategy(graph, Just(fixture.details().hakari_package()));
43
44        let iter = (0..count).map(move |idx| {
45            // The partial clones mean that a change to the algorithm in part of the strategy won't
46            // affect the rest of it.
47            let mut iter_generator = generator.partial_clone();
48            let mut builder = iter_generator
49                .partial_clone()
50                .generate(&hakari_builder_strategy);
51
52            // The alternate fixture uses this registry.
53            if fixture.name() == "metadata_alternate_registries" {
54                builder.add_registries([("my-registry", METADATA_ALTERNATE_REGISTRY_URL)]);
55            }
56
57            let hakari = builder.compute();
58            let mut output_options = HakariOutputOptions::default();
59            output_options
60                .set_builder_summary(true)
61                .set_absolute_paths(true);
62            let toml = hakari
63                .to_toml_string(&output_options)
64                .expect("to_toml_string worked");
65
66            (idx, HakariTomlItem { hakari, toml })
67        });
68        Box::new(iter)
69    }
70
71    fn parse_existing(path: &Utf8Path, contents: String) -> Result<Self::Existing> {
72        Ok(HakariCargoToml::new_in_memory(path, contents)?)
73    }
74
75    fn is_changed(
76        _fixture: &'g JsonFixture,
77        (_, item): &Self::IterItem,
78        existing: &Self::Existing,
79    ) -> Result<bool> {
80        Ok(existing.is_changed(&item.toml))
81    }
82
83    fn diff(
84        _fixture: &'g JsonFixture,
85        (_, item): &Self::IterItem,
86        existing: Option<&Self::Existing>,
87    ) -> String {
88        static DEFAULT_EXISTING: Lazy<HakariCargoToml> = Lazy::new(|| {
89            let contents = format!(
90                "{}{}",
91                HakariCargoToml::BEGIN_SECTION,
92                HakariCargoToml::END_SECTION
93            );
94            HakariCargoToml::new_in_memory("default", contents)
95                .expect("contents are in correct format")
96        });
97
98        let existing = existing.unwrap_or(&*DEFAULT_EXISTING);
99
100        let diff = existing.diff_toml(&item.toml);
101        let formatter = PatchFormatter::new();
102
103        format!("{}", formatter.fmt_patch(&diff))
104
105        // let package_id = guppy::PackageId::new(
106        //     "curl-sys 0.4.36+curl-7.71.1 (registry+https://github.com/rust-lang/crates.io-index)",
107        // );
108        // let explain = item.hakari.explain(&package_id);
109        // let explain = if let Ok(explain) = explain {
110        //     format!("{}", explain.display())
111        // } else {
112        //     "".to_owned()
113        // };
114        // format!("{}\n\n{}", formatter.fmt_patch(&diff), explain)
115    }
116
117    fn write_to_string(
118        fixture: &'g JsonFixture,
119        (_, item): &Self::IterItem,
120        out: &mut String,
121    ) -> Result<()> {
122        // XXX this should be unified with `DEFAULT_EXISTING` somehow, bleh
123        let out_contents = format!(
124            "# This file is @generated. To regenerate, run:\n\
125             #    cargo run -p fixture-manager -- generate-hakari --fixture {}\n\
126             \n\
127             ### BEGIN HAKARI SECTION\n\
128             \n\
129             ### END HAKARI SECTION\n\
130             \n\
131             # This part of the file should be preserved at the end.\n",
132            fixture.name()
133        );
134
135        let new_toml = HakariCargoToml::new_in_memory("bogus", out_contents)?;
136        Ok(new_toml.write_to_fmt(&item.toml, out)?)
137    }
138}
139
140pub struct HakariTomlItem<'g> {
141    #[allow(dead_code)]
142    hakari: Hakari<'g>,
143    toml: String,
144}