cargo_version_sync/
runner.rs

1use std::collections::HashMap;
2use std::fs;
3use std::io;
4use std::path::{Path, PathBuf};
5
6use failure::{format_err, Fallible};
7
8use crate::changeset::{Changeset, Diff};
9use crate::manifest::{Config, Manifest};
10use crate::replacer::{Replacer, ReplacerContext};
11
12#[derive(Debug)]
13pub struct Runner {
14    manifest: Manifest,
15    manifest_dir: PathBuf,
16    replacements: HashMap<PathBuf, Vec<Replacer>>,
17}
18
19impl Runner {
20    pub fn init() -> Fallible<Self> {
21        let manifest_dir = cargo_manifest_dir()?;
22        let manifest_path = manifest_dir.join("Cargo.toml");
23        let manifest = Manifest::from_file(manifest_path)?
24            .ok_or_else(|| format_err!("missing Cargo manifest file"))?;
25
26        let mut runner = Self {
27            manifest,
28            manifest_dir,
29            replacements: HashMap::new(),
30        };
31
32        if runner.config().map_or(false, |config| config.use_preset) {
33            println!("[cargo-version-sync] use preset");
34            runner.build_preset()?;
35        } else {
36            runner.collect_replacements()?;
37        }
38
39        Ok(runner)
40    }
41
42    pub fn manifest_dir(&self) -> &Path {
43        &self.manifest_dir
44    }
45
46    fn config(&self) -> Option<&Config> {
47        self.manifest
48            .package
49            .metadata
50            .as_ref()
51            .and_then(|metadata| metadata.version_sync.as_ref())
52    }
53
54    fn resolve_path(&self, path: impl AsRef<Path>) -> io::Result<Option<PathBuf>> {
55        match self.manifest_dir.join(path).canonicalize() {
56            Ok(path) => Ok(Some(path)),
57            Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
58            Err(e) => Err(e),
59        }
60    }
61
62    fn build_preset(&mut self) -> Fallible<()> {
63        if let Some(file) = self.resolve_path("README.md")? {
64            self.replacements
65                .entry(file.clone())
66                .or_insert_with(Default::default)
67                .extend(vec![
68                    Replacer::builtin("markdown")?,
69                    Replacer::regex(
70                        "https://deps.rs/crate/{{name}}/[0-9a-z\\.-]+",
71                        "https://deps.rs/crate/{{name}}/{{version}}",
72                    ),
73                ]);
74        }
75        if let Some(file) = self.resolve_path("src/lib.rs")? {
76            self.replacements
77                .entry(file.clone())
78                .or_insert_with(Default::default)
79                .push(Replacer::builtin("html-root-url")?);
80        }
81        Ok(())
82    }
83
84    fn collect_replacements(&mut self) -> Fallible<()> {
85        if let Some(ref parsed_replacements) = self
86            .manifest
87            .package
88            .metadata
89            .as_ref()
90            .and_then(|meta| meta.version_sync.as_ref())
91            .map(|version_sync| &version_sync.replacements)
92        {
93            for replace in parsed_replacements.iter() {
94                if let Some(file) = self.resolve_path(&replace.file)? {
95                    self.replacements
96                        .entry(file.clone())
97                        .or_insert_with(Default::default)
98                        .extend(replace.replacers.clone());
99                }
100            }
101        }
102
103        Ok(())
104    }
105
106    pub fn collect_changeset(&self) -> Fallible<Changeset> {
107        let mut diffs = Vec::new();
108
109        for (path, replacers) in &self.replacements {
110            if !path.is_file() {
111                continue;
112            }
113
114            let content = fs::read_to_string(&path)?;
115
116            let replaced = {
117                let mut cx = ReplacerContext::new(content.as_str(), &self.manifest);
118                for replacer in replacers {
119                    replacer.replace(&mut cx)?;
120                }
121                cx.finish().into_owned()
122            };
123
124            if content != replaced {
125                diffs.push(Diff {
126                    file: path.clone(),
127                    content,
128                    replaced,
129                });
130            }
131        }
132
133        Ok(Changeset {
134            diffs,
135            manifest_dir: &self.manifest_dir,
136        })
137    }
138}
139
140fn cargo_manifest_dir() -> Fallible<PathBuf> {
141    match std::env::var_os("CARGO_MANIFEST_DIR") {
142        Some(dir) => Ok(PathBuf::from(dir)),
143        None => {
144            let current_dir = std::env::current_dir()?;
145            let mut current_dir: &Path = &current_dir;
146            loop {
147                if current_dir.join("Cargo.toml").is_file() {
148                    return Ok(current_dir.to_owned());
149                }
150                current_dir = match current_dir.parent() {
151                    Some(parent) => parent,
152                    None => {
153                        return Err(failure::format_err!("The cargo manifest file is not found"))
154                    }
155                }
156            }
157        }
158    }
159}