configure_semantic_release_assets/
lib.rs1#![forbid(unsafe_code)]
2#![deny(warnings)]
3
4use std::{
5 collections::HashSet,
6 io::{self, BufWriter, Write},
7 path::PathBuf,
8 str::FromStr,
9};
10use std::{fs::File, io::Read, path::Path};
11
12use indexmap::{map::Entry, IndexMap};
13use log::debug;
14
15mod error;
16
17use crate::error::Error;
18
19#[derive(Debug)]
20pub enum WriteTo {
21 Stdout,
22 InPlace,
23}
24
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub enum ModifiedFlag {
27 Unmodified,
28 Modified,
29}
30
31#[derive(Debug)]
32pub struct SemanticReleaseManifest {
33 inner: IndexMap<String, serde_json::Value>,
34}
35
36impl FromStr for SemanticReleaseManifest {
37 type Err = serde_json::Error;
38
39 fn from_str(s: &str) -> Result<Self, Self::Err> {
40 Ok(Self {
41 inner: serde_json::from_str(s)?,
42 })
43 }
44}
45
46pub struct SemanticReleaseConfiguration {
47 manifest: SemanticReleaseManifest,
48 manifest_path: PathBuf,
49 dirty: ModifiedFlag,
50}
51
52fn plugin_name(plugin: &serde_json::Value) -> Option<&str> {
53 match plugin {
54 serde_json::Value::String(name) => Some(name.as_str()),
55 serde_json::Value::Array(array) => array.get(0).and_then(|value| value.as_str()),
56 _ => None,
57 }
58}
59
60fn plugin_configuration(
61 plugin: &mut serde_json::Value,
62) -> Option<&mut serde_json::Map<String, serde_json::Value>> {
63 match plugin {
64 serde_json::Value::Array(array) => array.get_mut(1).and_then(|value| value.as_object_mut()),
65 _ => None,
66 }
67}
68
69impl SemanticReleaseManifest {
70 pub fn apply_whitelist(&mut self, whitelist: HashSet<String>) -> ModifiedFlag {
71 let mut dirty = ModifiedFlag::Unmodified;
72
73 if let Entry::Occupied(mut entry) = self.inner.entry("plugins".to_owned()) {
74 if let Some(plugins) = entry.get_mut().as_array_mut() {
75 for plugin in plugins {
76 if plugin_name(plugin) != Some("@semantic-release/github") {
77 continue;
78 }
79
80 if let Some(assets) = plugin_configuration(plugin)
81 .and_then(|settings| settings.get_mut("assets"))
82 .and_then(|assets| assets.as_array_mut())
83 {
84 assets.retain(|asset| {
85 let label = asset
86 .as_object()
87 .and_then(|asset| asset.get("label"))
88 .and_then(|label| label.as_str());
89 match label {
90 Some(label) => {
91 let keep = whitelist.contains(label);
92 if !keep {
93 dirty = ModifiedFlag::Modified;
94 }
95 keep
96 }
97 None => true,
99 }
100 });
101 };
102 }
103 }
104 };
105
106 dirty
107 }
108}
109
110impl std::fmt::Display for SemanticReleaseManifest {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(f, "{}", serde_json::to_string_pretty(&self.inner).unwrap())
113 }
114}
115
116impl SemanticReleaseConfiguration {
117 pub fn read_from_file(semantic_release_manifest_path: &Path) -> Result<Self, Error> {
120 debug!(
121 "Reading semantic-release configuration from file {:?}",
122 semantic_release_manifest_path
123 );
124
125 if !semantic_release_manifest_path.exists() {
126 return Err(Error::configuration_file_not_found_error(
127 semantic_release_manifest_path,
128 ));
129 }
130
131 let mut string = String::new();
135 File::open(semantic_release_manifest_path)
136 .map_err(|err| Error::file_open_error(err, semantic_release_manifest_path))?
137 .read_to_string(&mut string)
138 .map_err(|err| Error::file_read_error(err, semantic_release_manifest_path))?;
139
140 Ok(Self {
141 manifest: SemanticReleaseManifest::from_str(&string)
142 .map_err(|err| Error::file_parse_error(err, semantic_release_manifest_path))?,
143 manifest_path: semantic_release_manifest_path.to_owned(),
144 dirty: ModifiedFlag::Unmodified,
145 })
146 }
147
148 fn write(&mut self, mut w: impl Write) -> Result<(), Error> {
149 debug!(
150 "Writing semantic-release configuration to file {:?}",
151 self.manifest_path
152 );
153 serde_json::to_writer_pretty(&mut w, &self.manifest.inner)
154 .map_err(Error::file_serialize_error)?;
155 w.write_all(b"\n")
156 .map_err(|err| Error::file_write_error(err, &self.manifest_path))?;
157 w.flush()
158 .map_err(|err| Error::file_write_error(err, &self.manifest_path))?;
159
160 Ok(())
161 }
162
163 pub fn write_if_modified(&mut self, write_to: WriteTo) -> Result<(), Error> {
164 match self.dirty {
165 ModifiedFlag::Unmodified => Ok(()),
166 ModifiedFlag::Modified => match write_to {
167 WriteTo::Stdout => self.write(io::stdout()),
168 WriteTo::InPlace => {
169 let file = File::create(&self.manifest_path)
170 .map_err(|err| Error::file_open_error(err, &self.manifest_path))?;
171 self.write(BufWriter::new(file))
172 }
173 },
174 }
175 }
176
177 pub fn apply_whitelist(&mut self, to_remove: HashSet<String>) {
178 let modified = self.manifest.apply_whitelist(to_remove);
179 if modified == ModifiedFlag::Modified {
180 self.dirty = ModifiedFlag::Modified;
181 }
182 }
183}