libimaglogfrontend/
lib.rs1#![forbid(unsafe_code)]
21
22#![deny(
23 non_camel_case_types,
24 non_snake_case,
25 path_statements,
26 trivial_numeric_casts,
27 unstable_features,
28 unused_allocation,
29 unused_import_braces,
30 unused_imports,
31 unused_must_use,
32 unused_mut,
33 unused_qualifications,
34 while_true,
35)]
36
37extern crate clap;
38#[macro_use] extern crate is_match;
39#[macro_use] extern crate log;
40extern crate toml;
41extern crate toml_query;
42extern crate itertools;
43extern crate failure;
44extern crate textwrap;
45extern crate resiter;
46
47extern crate libimaglog;
48extern crate libimagrt;
49extern crate libimagstore;
50extern crate libimagerror;
51extern crate libimagdiary;
52
53use std::io::Write;
54use std::io::Cursor;
55use std::str::FromStr;
56
57use failure::Error;
58use failure::err_msg;
59use failure::Fallible as Result;
60use resiter::Map;
61use resiter::AndThen;
62use resiter::IterInnerOkOrElse;
63use resiter::Filter;
64
65use libimagrt::application::ImagApplication;
66use libimagrt::runtime::Runtime;
67use libimagdiary::diary::Diary;
68use libimagdiary::diaryid::DiaryId;
69use libimaglog::log::Log;
70use libimagstore::iter::get::StoreIdGetIteratorExtension;
71use libimagstore::store::FileLockEntry;
72
73use clap::App;
74
75mod ui;
76
77use toml::Value;
78use itertools::Itertools;
79
80pub enum ImagLog {}
85impl ImagApplication for ImagLog {
86 fn run(rt: Runtime) -> Result<()> {
87 if let Some(scmd) = rt.cli().subcommand_name() {
88 match scmd {
89 "show" => show(&rt),
90 other => {
91 debug!("Unknown command");
92 if rt.handle_unknown_subcommand("imag-bookmark", other, rt.cli())?.success() {
93 Ok(())
94 } else {
95 Err(err_msg("Failed to handle unknown subcommand"))
96 }
97 },
98 }
99 } else {
100 let text = get_log_text(&rt);
101 let diary_name = match rt.cli().value_of("diaryname").map(String::from) {
102 Some(s) => s,
103 None => get_diary_name(&rt)?,
104 };
105
106 debug!("Writing to '{}': {}", diary_name, text);
107
108 rt.store()
109 .new_entry_now(&diary_name)
110 .and_then(|mut fle| {
111 fle.make_log_entry()?;
112 *fle.get_content_mut() = text;
113 Ok(fle)
114 })
115 .and_then(|fle| rt.report_touched(fle.get_location()).map_err(Error::from))
116 }
117 }
118
119 fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
120 ui::build_ui(app)
121 }
122
123 fn name() -> &'static str {
124 env!("CARGO_PKG_NAME")
125 }
126
127 fn description() -> &'static str {
128 "Overlay to imag-diary to 'log' single lines of text"
129 }
130
131 fn version() -> &'static str {
132 env!("CARGO_PKG_VERSION")
133 }
134}
135
136fn show(rt: &Runtime) -> Result<()> {
137 use std::borrow::Cow;
138
139 use libimagdiary::iter::DiaryEntryIterator;
140 use libimagdiary::entry::DiaryEntry;
141
142 let scmd = rt.cli().subcommand_matches("show").unwrap(); let iters : Vec<DiaryEntryIterator> = match scmd.values_of("show-name") {
144 Some(values) => values
145 .map(|diary_name| Diary::entries(rt.store(), diary_name))
146 .collect::<Result<Vec<DiaryEntryIterator>>>(),
147
148 None => if scmd.is_present("show-all") {
149 debug!("Showing for all diaries");
150 let iter = rt.store()
151 .diary_names()?
152 .map(|diary_name| {
153 let diary_name = diary_name?;
154 debug!("Getting entries for Diary: {}", diary_name);
155 let entries = Diary::entries(rt.store(), &diary_name)?;
156 let diary_name = Cow::from(diary_name);
157 Ok((entries, diary_name))
158 })
159 .collect::<Result<Vec<(DiaryEntryIterator, Cow<str>)>>>()?;
160
161 let iter = iter.into_iter()
162 .unique_by(|tpl| tpl.1.clone())
163 .map(|tpl| tpl.0)
164 .collect::<Vec<DiaryEntryIterator>>();
165
166 Ok(iter)
167 } else {
168 get_diary_name(rt).and_then(|dname| Diary::entries(rt.store(), &dname)).map(|e| vec![e])
170 }
171 }?;
172
173 let mut do_wrap = if scmd.is_present("show-wrap") {
174 Some(80)
175 } else {
176 None
177 };
178 let do_remove_newlines = scmd.is_present("show-skipnewlines");
179
180 if let Some(wrap_value) = scmd.value_of("show-wrap") {
181 do_wrap = Some(usize::from_str(wrap_value).map_err(Error::from)?);
182 }
183
184 let mut output = rt.stdout();
185
186 let v = iters.into_iter()
187 .flatten()
188 .into_get_iter(rt.store())
189 .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
190 .and_then_ok(|e| e.is_log().map(|b| (b, e)))
191 .filter_ok(|tpl| tpl.0)
192 .map_ok(|tpl| tpl.1)
193 .and_then_ok(|entry| entry.diary_id().map(|did| (did.get_date_representation(), did, entry)))
194 .collect::<Result<Vec<_>>>()?;
195
196 v.into_iter()
197 .sorted_by_key(|tpl| tpl.0)
198 .map(|tpl| (tpl.1, tpl.2))
199 .inspect(|tpl| debug!("Found entry: {:?}", tpl.1))
200 .map(|(id, entry)| {
201 if let Some(wrap_limit) = do_wrap {
202 let mut buffer = Cursor::new(Vec::with_capacity(256));
207 do_write_to(&mut buffer, id, &entry, do_remove_newlines)?;
208 let buffer = String::from_utf8(buffer.into_inner())?;
209
210 ::textwrap::wrap(&buffer, wrap_limit)
212 .iter()
213 .map(|line| writeln!(&mut output, "{}", line).map_err(Error::from))
214 .collect::<Result<Vec<_>>>()?;
215 } else {
216 do_write_to(&mut output, id, &entry, do_remove_newlines)?;
217 }
218
219 rt.report_touched(entry.get_location()).map_err(Error::from)
220 })
221 .collect::<Result<Vec<_>>>()
222 .map(|_| ())
223}
224
225fn get_diary_name(rt: &Runtime) -> Result<String> {
226 use toml_query::read::TomlValueReadExt;
227 use toml_query::read::TomlValueReadTypeExt;
228
229 let cfg = rt
230 .config()
231 .ok_or_else(|| err_msg("Configuration not present, cannot continue"))?;
232
233 let current_log = cfg
234 .read_string("log.default")?
235 .ok_or_else(|| err_msg("Configuration missing: 'log.default'"))?;
236
237 if cfg
238 .read("log.logs")?
239 .ok_or_else(|| err_msg("Configuration missing: 'log.logs'"))?
240 .as_array()
241 .ok_or_else(|| err_msg("Configuration 'log.logs' is not an Array"))?
242 .iter()
243 .map(|e| if !is_match!(e, &Value::String(_)) {
244 Err(err_msg("Configuration 'log.logs' is not an Array<String>!"))
245 } else {
246 Ok(e)
247 })
248 .map_ok(|value| value.as_str().unwrap())
249 .map_ok(String::from)
250 .collect::<Result<Vec<_>>>()?
251 .iter()
252 .find(|log| *log == ¤t_log)
253 .is_none()
254 {
255 Err(err_msg("'log.logs' does not contain 'log.default'"))
256 } else {
257 Ok(current_log)
258 }
259}
260
261fn get_log_text(rt: &Runtime) -> String {
262 rt.cli()
263 .values_of("text")
264 .unwrap() .enumerate()
266 .fold(String::with_capacity(500), |mut acc, (n, e)| {
267 if n != 0 {
268 acc.push_str(" ");
269 }
270 acc.push_str(e);
271 acc
272 })
273}
274
275fn do_write_to<'a>(sink: &mut dyn Write, id: DiaryId, entry: &FileLockEntry<'a>, do_remove_newlines: bool) -> Result<()> {
276 let text = if do_remove_newlines {
277 entry.get_content().trim_end().replace("\n", "")
278 } else {
279 entry.get_content().trim_end().to_string()
280 };
281
282 writeln!(sink,
283 "{dname: >10} - {y: >4}-{m:0>2}-{d:0>2}T{H:0>2}:{M:0>2} - {text}",
284 dname = id.diary_name(),
285 y = id.year(),
286 m = id.month(),
287 d = id.day(),
288 H = id.hour(),
289 M = id.minute(),
290 text = text)
291 .map_err(Error::from)
292}
293