1use failure::{Error, ResultExt};
2use json;
3use serde::Serialize;
4use yaml;
5
6mod types;
7pub use self::types::*;
8use std::env::vars;
9use std::fs::File;
10use std::io::Cursor;
11use std::io::{self, stdin};
12use treediff::{diff, tools};
13
14mod util;
15
16fn validate(cmds: &[Command]) -> Result<(), Error> {
17 let num_merge_stdin_cmds = cmds
18 .iter()
19 .filter(|c| if let Command::MergeStdin = **c { true } else { false })
20 .count();
21 if num_merge_stdin_cmds > 1 {
22 bail!(
23 "Cannot read from stdin more than once, found {} invocations",
24 num_merge_stdin_cmds
25 );
26 }
27 Ok(())
28}
29
30fn to_json(s: String, state: &State) -> json::Value {
31 let mut reader = io::Cursor::new(s);
32 util::de_json_or_yaml_document_support(&mut reader, state)
33 .unwrap_or_else(|_| json::Value::from(reader.into_inner()))
34}
35
36pub fn reduce(
37 cmds: Vec<Command>,
38 initial_state: Option<State>,
39 mut output: &mut dyn io::Write,
40) -> Result<State, Error> {
41 validate(&cmds)?;
42
43 use self::Command::*;
44 let mut state = initial_state.unwrap_or_else(State::default);
45
46 for cmd in cmds {
47 match cmd {
48 SelectToBuffer(pointer) => {
49 let json_pointer = into_pointer(&pointer);
50 match state.value {
51 Some(ref value) => state.buffer.push(
52 value
53 .pointer(&json_pointer)
54 .ok_or_else(|| format_err!("There is no value at '{}'", pointer))?
55 .clone(),
56 ),
57 None => bail!("There is no value to fetch from yet"),
58 }
59 }
60 SerializeBuffer => show_buffer(state.output_mode.as_ref(), &state.buffer, &mut output)?,
61 SelectNextMergeAt(at) => {
62 state.select_next_at = Some(at);
63 }
64 InsertNextMergeAt(at) => {
65 state.insert_next_at = Some(at);
66 }
67 SetMergeMode(mode) => {
68 state.merge_mode = mode;
69 }
70 MergeValue(pointer, value) => {
71 let value_to_merge = to_json(value, &state);
72 let prev_insert_next_at = state.insert_next_at;
73 state.insert_next_at = Some(pointer);
74
75 state = merge(value_to_merge, state)?;
76
77 state.insert_next_at = prev_insert_next_at;
78 }
79 MergeStdin => {
80 if let Some(input) = probe_and_read_from_stdin()? {
81 let value_to_merge = util::de_json_or_yaml_document_support(input, &state)?;
82 state = merge(value_to_merge, state)?;
83 }
84 }
85 MergeEnvironment(pattern) => {
86 let map = vars().filter(|&(ref var, _)| pattern.matches(var)).fold(
87 json::Map::new(),
88 |mut m, (var, value)| {
89 m.insert(var, to_json(value, &state));
90 m
91 },
92 );
93 state = merge(json::Value::from(map), state)?;
94 }
95 MergePath(path) => {
96 let reader =
97 File::open(&path).context(format!("Failed to open file at '{}' for reading", path.display()))?;
98 let value_to_merge = util::de_json_or_yaml_document_support(reader, &state)?;
99 state = merge(value_to_merge, state)?;
100 }
101 SetOutputMode(mode) => {
102 state.output_mode = Some(mode);
103 }
104 Serialize => {
105 state.value = match state.value {
106 Some(value) => Some(apply_transforms(
107 value,
108 state.insert_next_at.take(),
109 state.select_next_at.take(),
110 )?),
111 None => None,
112 };
113
114 show(state.output_mode.as_ref(), &state.value, &mut output)?
115 }
116 }
117 }
118
119 Ok(state)
120}
121
122fn probe_and_read_from_stdin() -> Result<Option<Cursor<Vec<u8>>>, Error> {
123 use std::io::Read;
124
125 let s = stdin();
126 let mut stdin = s.lock();
127 let mut buf = Vec::new();
128 stdin
129 .read_to_end(&mut buf)
130 .context("Failed to read everything from standard input")?;
131 Ok(if buf.is_empty() { None } else { Some(Cursor::new(buf)) })
132}
133
134fn show_buffer<W>(output_mode: Option<&OutputMode>, value: &[json::Value], mut ostream: W) -> Result<(), Error>
135where
136 W: io::Write,
137{
138 let has_complex_value = value.iter().any(|v| match *v {
139 json::Value::Array(_) | json::Value::Object(_) => true,
140 _ => false,
141 });
142
143 let output_mode = match output_mode {
144 None => {
145 if has_complex_value {
146 Some(&OutputMode::Json)
147 } else {
148 None
149 }
150 }
151 Some(mode) => Some(mode),
152 };
153
154 match output_mode {
155 None => {
156 for v in value {
157 match *v {
158 json::Value::Bool(ref v) => writeln!(ostream, "{}", v),
159 json::Value::Number(ref v) => writeln!(ostream, "{}", v),
160 json::Value::String(ref v) => writeln!(ostream, "{}", v),
161 json::Value::Null => continue,
162 json::Value::Object(_) | json::Value::Array(_) => {
163 unreachable!("We should never try to print complex values here - this is a bug.")
164 }
165 }?;
166 }
167 Ok(())
168 }
169 mode @ Some(_) => show(mode, value, ostream),
170 }
171}
172
173fn show<V, W>(output_mode: Option<&OutputMode>, value: V, ostream: W) -> Result<(), Error>
174where
175 V: Serialize,
176 W: io::Write,
177{
178 match output_mode {
179 Some(&OutputMode::Json) | None => json::to_writer_pretty(ostream, &value).map_err(Into::into),
180 Some(&OutputMode::Yaml) => yaml::to_writer(ostream, &value).map_err(Into::into),
181 }
182}
183
184fn into_pointer(p: &str) -> String {
185 let mut p = if p.find('/').is_none() {
186 p.replace('.', "/")
187 } else {
188 p.to_owned()
189 };
190 if !p.starts_with('/') {
191 p.insert(0, '/');
192 }
193 p
194}
195
196fn select_json_at(pointer: Option<String>, v: json::Value) -> Result<json::Value, Error> {
197 match pointer {
198 Some(pointer) => {
199 let json_pointer = into_pointer(&pointer);
200 v.pointer(&json_pointer)
201 .map(|v| v.to_owned())
202 .ok_or_else(|| format_err!("No value at pointer '{}'", pointer))
203 }
204 None => Ok(v),
205 }
206}
207
208fn insert_json_at(pointer: Option<String>, v: json::Value) -> Result<json::Value, Error> {
209 Ok(match pointer {
210 Some(mut pointer) => {
211 pointer = into_pointer(&pointer);
212 let mut current = v;
213 for elm in pointer.rsplit('/').filter(|s| !s.is_empty()) {
214 let index: Result<usize, _> = elm.parse();
215 match index {
216 Ok(index) => {
217 let mut a = vec![json::Value::Null; index + 1];
218 a[index] = current;
219 current = json::Value::from(a);
220 }
221 Err(_) => {
222 let mut map = json::Map::new();
223 map.insert(elm.to_owned(), current);
224 current = json::Value::from(map)
225 }
226 }
227 }
228 current
229 }
230 None => v,
231 })
232}
233
234fn apply_transforms(
235 src: json::Value,
236 insert_at: Option<String>,
237 select_at: Option<String>,
238) -> Result<json::Value, Error> {
239 select_json_at(select_at, src).and_then(|src| insert_json_at(insert_at, src))
240}
241
242fn merge(src: json::Value, mut state: State) -> Result<State, Error> {
243 let src = apply_transforms(src, state.insert_next_at.take(), state.select_next_at.take())?;
244
245 match state.value {
246 None => {
247 state.value = Some(src);
248 Ok(state)
249 }
250 Some(existing_value) => {
251 let mut m = tools::Merger::with_filter(existing_value.clone(), NeverDrop::with_mode(&state.merge_mode));
252 diff(&existing_value, &src, &mut m);
253
254 if !m.filter().clashed_keys.is_empty() {
255 Err(format_err!("{}", m.filter())
256 .context("The merge failed due to conflicts")
257 .into())
258 } else {
259 state.value = Some(m.into_inner());
260 Ok(state)
261 }
262 }
263 }
264}