1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright 2020-2022 Peter Williams <peter@newton.cx> and collaborators
// Licensed under the MIT License.
//! The Cranko configuration file.
//!
//! Given the same input repository, Cranko should give reproducible results no
//! matter who’s running it. So we really want all configuration to be at the
//! per-repository level.
use anyhow::Context;
use std::{collections::HashMap, fs::File, io::Read, path::Path};
use crate::{
atry,
errors::{Error, Result},
};
/// The configuration file structures as explicitly serialized into the TOML
/// format.
mod syntax {
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// The toplevel (per-repo) configuration structure.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SerializedConfiguration {
/// General per-repository configuration.
pub repo: RepoConfiguration,
/// Centralized per-project configuration.
#[serde(default)]
pub projects: HashMap<String, ProjectConfiguration>,
}
/// Configuration relating to the backing repository. This is applied
/// directly to the runtime Repository instance.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct RepoConfiguration {
/// Git URLs that the upstream remote might be using.
pub upstream_urls: Vec<String>,
/// The name of the `rc`-like branch.
pub rc_name: Option<String>,
/// The name of the `release`-like branch.
pub release_name: Option<String>,
/// The format for release tag names.
pub release_tag_name_format: Option<String>,
}
/// Configuration relating to individual projects.
///
/// Whenever possible, this configuration should be specified in per-project
/// metadata files to preserve locality. But some pieces of configuration
/// need to be centralized.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ProjectConfiguration {
/// Ignore this project if/when it is automatically detected.
pub ignore: bool,
}
}
// The rest of this module normalizes the on-disk format into forms more useful
// at runtime.
pub use syntax::{ProjectConfiguration, RepoConfiguration};
#[derive(Clone, Debug)]
pub struct ConfigurationFile {
pub repo: RepoConfiguration,
pub projects: HashMap<String, ProjectConfiguration>,
}
impl Default for ConfigurationFile {
fn default() -> Self {
let repo = RepoConfiguration::default();
let projects = Default::default();
ConfigurationFile { repo, projects }
}
}
impl ConfigurationFile {
pub fn get<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut f = match File::open(&path) {
Ok(f) => f,
Err(e) => {
return if e.kind() == std::io::ErrorKind::NotFound {
Ok(Self::default())
} else {
Err(Error::new(e).context(format!(
"failed to open config file `{}`",
path.as_ref().display()
)))
}
}
};
let mut text = String::new();
f.read_to_string(&mut text)
.with_context(|| format!("failed to read config file `{}`", path.as_ref().display()))?;
let sercfg: syntax::SerializedConfiguration = toml::from_str(&text).with_context(|| {
format!(
"could not parse config file `{}` as TOML",
path.as_ref().display()
)
})?;
Ok(ConfigurationFile {
repo: sercfg.repo,
projects: sercfg.projects,
})
}
pub fn into_toml(self) -> Result<String> {
let syn_cfg = syntax::SerializedConfiguration {
repo: self.repo,
projects: self.projects,
};
Ok(atry!(
toml::to_string_pretty(&syn_cfg);
["could not serialize configuration into TOML format"]
))
}
}