cargo_version_sync/
runner.rs1use 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 = ¤t_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}