sheesy_tools/process/
mod.rs

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}