diff --git a/Cargo.lock b/Cargo.lock index ebfbeffd..e48fefc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "open 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "prettytable-rs 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "rustty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -74,6 +75,11 @@ dependencies = [ "nom 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "getopts" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "glob" version = "0.2.10" @@ -156,6 +162,14 @@ dependencies = [ "unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pulldown-cmark" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.11" diff --git a/Cargo.toml b/Cargo.toml index ceb9bda9..d58d0158 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,5 @@ term = "0.2.12" term_grid = "0.1.2" prettytable-rs = "0.4.0" open = "1.1.0" +pulldown-cmark = "0.0.3" diff --git a/etc/cli.yml b/etc/cli.yml index 21b38e0f..08fa8293 100644 --- a/etc/cli.yml +++ b/etc/cli.yml @@ -258,3 +258,110 @@ subcommands: version: 0.1 author: Matthias Beyer + - notes: + about: Notes module + version: 0.1 + author: Matthias Beyer + subcommands: + - add: + about: Add Note + version: 0.1 + author: Matthias Beyer + args: + - name: + short: n + long: name + help: Add a name to the note + required: false + takes_value: true + + - tags: + short: t + long: tags + help: Add these tags to the note + required: false + takes_value: true + + - edit: + about: Edit Note + version: 0.1 + author: Matthias Beyer + args: + - id: + long: id + help: Edit Note with this ID + required: false + takes_value: true + + - namegrep: + short: n + long: name + help: Edit notes where the name matches this regex + required: false + takes_value: true + + - grep: + short: g + long: grep + help: Edit notes where the content matches this regex + required: false + takes_value: true + + - tags: + short: t + long: tags + help: Edit notes with these tags + required: false + takes_value: true + + - list: + about: List notes + version: 0.1 + author: Matthias Beyer + args: + - namegrep: + short: n + long: name + help: Filter for name which matches this regex + required: false + takes_value: true + + - grep: + short: g + long: grep + help: grep with regex + required: false + takes_value: true + + - tags: + short: t + long: tags + help: Filter for these tags + required: false + takes_value: true + + - remove: + about: Remove note(s) + version: 0.1 + author: Matthias Beyer + args: + - id: + long: id + help: Delete Note by ID + required: false + takes_value: true + + - match: + short: m + long: match + help: Match for regex + required: false + takes_value: true + + - tags: + short: t + long: tags + help: Filter for these tags + required: false + takes_value: true + diff --git a/src/configuration.rs b/src/configuration.rs index 7671bef7..b783c734 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -65,8 +65,8 @@ impl Configuration { self.editor.clone() } - pub fn editor_opts(&self) -> &String { - &self.editor_opts + pub fn editor_opts(&self) -> String { + self.editor_opts.clone() } } diff --git a/src/main.rs b/src/main.rs index 504be825..37ef4914 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ pub mod ui; pub mod util; pub use module::bm::BM; +pub use module::notes::Notes; fn main() { let yaml = load_yaml!("../etc/cli.yml"); @@ -43,10 +44,11 @@ fn main() { debug!("Runtime : {:?}", &rt); - if let Some(matches) = rt.config.cli_matches.subcommand_matches("bm") { - let res = BM::new(&rt).exec(matches); - info!("BM exited with {}", res); - } else { - info!("No commandline call...") - } + let res = match rt.config.cli_matches.subcommand_name() { + Some("bm") => BM::new(&rt).exec(rt.config.cli_matches.subcommand_matches("bm").unwrap()), + Some("notes") => Notes::new(&rt).exec(rt.config.cli_matches.subcommand_matches("notes").unwrap()), + _ => false, + }; + + info!("Module execution ended with {}", res); } diff --git a/src/module/bm/mod.rs b/src/module/bm/mod.rs index 008df23d..251645f0 100644 --- a/src/module/bm/mod.rs +++ b/src/module/bm/mod.rs @@ -243,94 +243,44 @@ impl<'a> BM<'a> { * Subcommand: add_tags */ fn command_add_tags(&self, matches: &ArgMatches) -> bool { - self.alter_tags_in_files(matches, |old_tags, cli_tags| { + use module::helpers::header::tags::data::alter_tags_in_files; + use self::header::rebuild_header_with_tags; + + let parser = Parser::new(JsonHeaderParser::new(None)); + alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| { let mut new_tags = old_tags.clone(); new_tags.append(&mut cli_tags.clone()); new_tags - }) + }, rebuild_header_with_tags) } /** * Subcommand: rm_tags */ fn command_rm_tags(&self, matches: &ArgMatches) -> bool { - self.alter_tags_in_files(matches, |old_tags, cli_tags| { + use module::helpers::header::tags::data::alter_tags_in_files; + use self::header::rebuild_header_with_tags; + + let parser = Parser::new(JsonHeaderParser::new(None)); + alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| { old_tags.clone() .into_iter() .filter(|tag| !cli_tags.contains(tag)) .collect() - }) + }, rebuild_header_with_tags) } /** * Subcommand: set_tags */ fn command_set_tags(&self, matches: &ArgMatches) -> bool { - self.alter_tags_in_files(matches, |old_tags, cli_tags| { - cli_tags.clone() - }) - } - - /** - * Helper function to alter the tags in a file - */ - fn alter_tags_in_files(&self, matches: &ArgMatches, generate_new_tags: F) -> bool - where F: Fn(Vec, &Vec) -> Vec - { + use module::helpers::header::tags::data::alter_tags_in_files; use self::header::rebuild_header_with_tags; - let cli_tags = matches.value_of("tags") - .map(|ts| { - ts.split(",") - .map(String::from) - .collect::>() - }) - .unwrap_or(vec![]); - let parser = Parser::new(JsonHeaderParser::new(None)); - - let filter = { - let hash_filter = create_hash_filter(matches, "with:id", false); - let text_filter = create_text_header_field_grep_filter(matches, "with_match", "URL", false); - let tags_filter = create_tag_filter(matches, "with_tags", false); - hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter)) - }; - - self.rt - .store() - .load_for_module(self, &parser) - .into_iter() - .filter(|file| filter.filter_file(file)) - .map(|file| { - debug!("Remove tags from file: {:?}", file); - - let hdr = { - let f = file.deref().borrow(); - f.header().clone() - }; - - debug!("Tags:..."); - let old_tags = get_tags_from_header(&hdr); - debug!(" old_tags = {:?}", &old_tags); - debug!(" cli_tags = {:?}", &cli_tags); - - let new_tags = generate_new_tags(old_tags, &cli_tags); - debug!(" new_tags = {:?}", &new_tags); - - let new_header = rebuild_header_with_tags(&hdr, new_tags) - .unwrap_or_else(|| { - error!("Could not rebuild header for file"); - exit(1); - }); - { - let mut f_mut = file.deref().borrow_mut(); - f_mut.set_header(new_header); - } - - self.rt.store().persist(&parser, file); - true - }) - .all(|x| x) + alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| { + cli_tags.clone() + }, rebuild_header_with_tags) } } @@ -380,6 +330,10 @@ impl<'a> Module<'a> for BM<'a> { fn name(&self) -> &'static str { "bookmark" } + + fn runtime(&self) -> &Runtime { + self.rt + } } impl<'a> Debug for BM<'a> { diff --git a/src/module/helpers/header/tags.rs b/src/module/helpers/header/tags.rs index e2e390e0..2fd13363 100644 --- a/src/module/helpers/header/tags.rs +++ b/src/module/helpers/header/tags.rs @@ -35,6 +35,10 @@ pub mod spec { pub mod data { use std::ops::Deref; use storage::file::header::data::FileHeaderData as FHD; + use module::Module; + use clap::ArgMatches; + use storage::parser::Parser; + use storage::parser::FileHeaderParser; /** * Use a Vec to build a Tag-Array: @@ -94,5 +98,77 @@ pub mod data { tags } + /** + * Helper function to alter the tags in a file + */ + pub fn alter_tags_in_files(m: &Module, + matches: &ArgMatches, + parser: &Parser, + generate_new_tags: F, + rebuild_header: R) -> bool + where HP: FileHeaderParser, + F: Fn(Vec, &Vec) -> Vec, + R: Fn(&FHD, Vec) -> Option + { + use std::process::exit; + use module::helpers::cli::create_tag_filter; + use module::helpers::cli::create_hash_filter; + use module::helpers::cli::create_text_header_field_grep_filter; + use module::helpers::cli::create_content_grep_filter; + use module::helpers::cli::CliFileFilter; + + let cli_tags = matches.value_of("tags") + .map(|ts| { + ts.split(",") + .map(String::from) + .collect::>() + }) + .unwrap_or(vec![]); + + let filter = { + let hash_filter = create_hash_filter(matches, "with:id", false); + let text_filter = create_text_header_field_grep_filter(matches, "with_match", "URL", false); + let tags_filter = create_tag_filter(matches, "with_tags", false); + hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter)) + }; + + m.runtime() + .store() + .load_for_module(m, &parser) + .into_iter() + .filter(|file| filter.filter_file(file)) + .map(|file| { + debug!("Alter tags in file: {:?}", file); + + let hdr = { + let f = file.deref().borrow(); + f.header().clone() + }; + + debug!("Tags:..."); + let old_tags = get_tags_from_header(&hdr); + debug!(" old_tags = {:?}", &old_tags); + debug!(" cli_tags = {:?}", &cli_tags); + + let new_tags = generate_new_tags(old_tags, &cli_tags); + debug!(" new_tags = {:?}", &new_tags); + + let new_header = rebuild_header(&hdr, new_tags) + .unwrap_or_else(|| { + error!("Could not rebuild header for file"); + exit(1); + }); + { + let mut f_mut = file.deref().borrow_mut(); + f_mut.set_header(new_header); + } + + m.runtime().store().persist(&parser, file); + true + }) + .all(|x| x) + } + + } diff --git a/src/module/mod.rs b/src/module/mod.rs index 24fdc9a5..910995cb 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -2,8 +2,11 @@ use std::fmt::Debug; use clap::ArgMatches; +use runtime::Runtime; + pub mod bm; pub mod helpers; +pub mod notes; /** * Module interface, each module has to implement this. @@ -11,5 +14,7 @@ pub mod helpers; pub trait Module<'a> : Debug { fn exec(&self, matches: &ArgMatches) -> bool; fn name(&self) -> &'static str; + + fn runtime(&self) -> &Runtime; } diff --git a/src/module/notes/header.rs b/src/module/notes/header.rs new file mode 100644 index 00000000..76fc1880 --- /dev/null +++ b/src/module/notes/header.rs @@ -0,0 +1,64 @@ +/* + * Lets talk about header data first. + * We need: + * + * - tags + * - name (not unique) + * + * So an header could look like this: + * + * ```json + * { + * 'name': "kittennotes", + * 'tags': ['foo', 'bar', 'baz'], + * } + * ``` + * + * Nothing more is required for the header, I guess + * + */ + +use module::helpers; +use module::helpers::header as headerhelpers; +use storage::file::header::data::FileHeaderData as FHD; +use storage::file::header::spec::FileHeaderSpec as FHS; + +pub fn get_spec() -> FHS { + FHS::Map { keys: vec![ helpers::spec::named_text("NAME"), + headerhelpers::tags::spec::tags_key() ] } +} + + +pub fn build_header(name: String, tags: Vec) -> FHD { + FHD::Map { + keys: vec![ + FHD::Key { + name: String::from("NAME"), + value: Box::new(FHD::Text(name.clone())) + }, + FHD::Key { + name: String::from("TAGS"), + value: Box::new(headerhelpers::tags::data::build_tag_array(tags)) + } + ] + } +} + +pub fn get_tags_from_header(header: &FHD) -> Vec { + headerhelpers::tags::data::get_tags_from_header(header) +} + +/** + * Get the name from the Header + * + * Returns empty string if there is no NAME field + */ +pub fn get_name_from_header(header: &FHD) -> String { + headerhelpers::data::get_name_from_header(header).unwrap_or(String::from("")) +} + +pub fn rebuild_header_with_tags(header: &FHD, tags: Vec) -> Option { + let name = get_name_from_header(header); + Some(build_header(name, tags)) +} + diff --git a/src/module/notes/mod.rs b/src/module/notes/mod.rs new file mode 100644 index 00000000..f6736d40 --- /dev/null +++ b/src/module/notes/mod.rs @@ -0,0 +1,294 @@ +use std::fmt::{Debug, Formatter}; +use std::fmt::Result as FMTResult; +use std::rc::Rc; +use std::cell::RefCell; +use std::ops::Deref; + +use clap::ArgMatches; +use regex::Regex; + +mod header; + +use module::Module; +use runtime::Runtime; +use storage::file::File; +use storage::parser::Parser; +use storage::json::parser::JsonHeaderParser; +use module::helpers::cli::create_tag_filter; +use module::helpers::cli::create_hash_filter; +use module::helpers::cli::create_text_header_field_grep_filter; +use module::helpers::cli::create_content_grep_filter; +use module::helpers::cli::CliFileFilter; + +pub struct Notes<'a> { + rt: &'a Runtime<'a>, +} + +impl<'a> Notes<'a> { + + pub fn new(rt: &'a Runtime<'a>) -> Notes<'a> { + Notes { + rt: rt, + } + } + + fn command_add(&self, matches: &ArgMatches) -> bool { + use std::process::exit; + use self::header::build_header; + use ui::external::editor::let_user_provide_content; + + let parser = Parser::new(JsonHeaderParser::new(None)); + let name = matches.value_of("name") + .map(String::from) + .unwrap_or(String::from("")); + let tags = matches.value_of("tags") + .and_then(|s| Some(s.split(",").map(String::from).collect())) + .unwrap_or(vec![]); + + debug!("Building header with"); + debug!(" name = '{:?}'", name); + debug!(" tags = '{:?}'", tags); + let header = build_header(name, tags); + + let content = let_user_provide_content(self.runtime()).unwrap_or(String::from("")); + + let fileid = self.rt.store().new_file_with_content(self, header, content); + self.rt + .store() + .load(self, &parser, &fileid) + .and_then(|file| { + info!("Created file in memory: {}", fileid); + Some(self.rt.store().persist(&parser, file)) + }) + .unwrap_or(false) + } + + fn command_edit(&self, matches: &ArgMatches) -> bool { + use ui::external::editor::edit_content; + + let parser = Parser::new(JsonHeaderParser::new(None)); + + let filter = { + let hash_filter = create_hash_filter(matches, "id", false); + let head_filter = create_text_header_field_grep_filter(matches, "namematch", "NAME", false); + let text_filter = create_content_grep_filter(matches, "match", false); + let tags_filter = create_tag_filter(matches, "tags", false); + hash_filter.or(Box::new(head_filter)).or(Box::new(text_filter)).or(Box::new(tags_filter)) + }; + + let result = self.rt + .store() + .load_for_module(self, &parser) + .into_iter() + .filter(|f| filter.filter_file(f)) + .map(|file| { + debug!("File loaded, can edit now: {:?}", file); + + let old_content = { + let f = file.deref().borrow(); + f.data().clone() + }; + + debug!("Editing content now..."); + let (new_content, editing_worked) = edit_content(self.runtime(), old_content); + debug!("... ready with editing"); + + if editing_worked { + debug!("Editing worked"); + { + let mut f = file.deref().borrow_mut(); + f.set_data(new_content); + } + self.runtime().store().persist(&parser, file) + } else { + debug!("Editing didn't work"); + false + } + }) + .fold((0, 0), |acc, succeeded| { + let (worked, failed) = acc; + if succeeded { + (worked + 1, failed) + } else { + (worked, failed + 1) + } + }); + + let (worked, failed) = result; + + info!("Editing succeeded for {} files", worked); + info!("Editing failed for {} files", failed); + + return failed == 0; + } + + fn command_list(&self, matches: &ArgMatches) -> bool { + use ui::file::{FilePrinter, TablePrinter}; + use self::header::get_name_from_header; + use self::header::get_tags_from_header; + use std::process::exit; + use module::helpers::cli::CliFileFilter; + + let parser = Parser::new(JsonHeaderParser::new(None)); + + let filter = { + let hash_filter = create_hash_filter(matches, "id", true); + let head_filter = create_text_header_field_grep_filter(matches, "match", "NAME", true); + let text_filter = create_content_grep_filter(matches, "match", true); + let tags_filter = create_tag_filter(matches, "tags", true); + hash_filter.or(Box::new(head_filter)).and(Box::new(text_filter)).and(Box::new(tags_filter)) + }; + + let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging()); + + printer.print_files_custom( + self.rt.store() + .load_for_module(self, &parser) + .into_iter() + .filter(|f| filter.filter_file(f)), + &|file| { + let fl = file.deref().borrow(); + let hdr = fl.header(); + let name = get_name_from_header(hdr); + let tags = get_tags_from_header(hdr); + + debug!("Custom printer field: name = '{:?}'", name); + debug!("Custom printer field: tags = '{:?}'", tags); + + vec![name, tags.join(", ")] + } + ); + true + } + + fn command_remove(&self, matches: &ArgMatches) -> bool { + let parser = Parser::new(JsonHeaderParser::new(None)); + + let filter = { + let hash_filter = create_hash_filter(matches, "id", false); + let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false); + let tags_filter = create_tag_filter(matches, "tags", false); + hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter)) + }; + + let result = self.rt + .store() + .load_for_module(self, &parser) + .iter() + .filter(|file| filter.filter_file(file)) + .map(|file| { + debug!("File loaded, can remove now: {:?}", file); + let f = file.deref().borrow(); + self.rt.store().remove(f.id().clone()) + }) + .fold((0, 0), |acc, succeeded| { + let (worked, failed) = acc; + if succeeded { + (worked + 1, failed) + } else { + (worked, failed + 1) + } + }); + + let (worked, failed) = result; + + info!("Removing succeeded for {} files", worked); + info!("Removing failed for {} files", failed); + + return failed == 0; + } + + fn command_add_tags(&self, matches: &ArgMatches) -> bool { + use module::helpers::header::tags::data::alter_tags_in_files; + use self::header::rebuild_header_with_tags; + + let parser = Parser::new(JsonHeaderParser::new(None)); + alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| { + let mut new_tags = old_tags.clone(); + new_tags.append(&mut cli_tags.clone()); + new_tags + }, rebuild_header_with_tags) + } + + fn command_rm_tags(&self, matches: &ArgMatches) -> bool { + use module::helpers::header::tags::data::alter_tags_in_files; + use self::header::rebuild_header_with_tags; + + let parser = Parser::new(JsonHeaderParser::new(None)); + alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| { + old_tags.clone() + .into_iter() + .filter(|tag| !cli_tags.contains(tag)) + .collect() + }, rebuild_header_with_tags) + } + + fn command_set_tags(&self, matches: &ArgMatches) -> bool { + use module::helpers::header::tags::data::alter_tags_in_files; + use self::header::rebuild_header_with_tags; + + let parser = Parser::new(JsonHeaderParser::new(None)); + alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| { + cli_tags.clone() + }, rebuild_header_with_tags) + } + +} + +impl<'a> Module<'a> for Notes<'a> { + + fn exec(&self, matches: &ArgMatches) -> bool { + match matches.subcommand_name() { + Some("add") => { + self.command_add(matches.subcommand_matches("add").unwrap()) + }, + + Some("edit") => { + self.command_edit(matches.subcommand_matches("edit").unwrap()) + }, + + Some("list") => { + self.command_list(matches.subcommand_matches("list").unwrap()) + }, + + Some("remove") => { + self.command_remove(matches.subcommand_matches("remove").unwrap()) + }, + + Some("add_tags") => { + self.command_add_tags(matches.subcommand_matches("add_tags").unwrap()) + }, + + Some("rm_tags") => { + self.command_rm_tags(matches.subcommand_matches("rm_tags").unwrap()) + }, + + Some("set_tags") => { + self.command_set_tags(matches.subcommand_matches("set_tags").unwrap()) + }, + + Some(_) | None => { + info!("No command given, doing nothing"); + false + }, + } + } + + fn name(&self) -> &'static str{ + "notes" + } + + fn runtime(&self) -> &Runtime { + self.rt + } + +} + +impl<'a> Debug for Notes<'a> { + + fn fmt(&self, fmt: &mut Formatter) -> FMTResult { + write!(fmt, "[Module][Notes]"); + Ok(()) + } + +} diff --git a/src/runtime.rs b/src/runtime.rs index 9ccce2f2..029e68a8 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,4 +1,5 @@ use std::fmt::{Debug, Formatter, Error}; +use std::process::Command; extern crate log; use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata, SetLoggerError}; @@ -112,18 +113,26 @@ impl<'a> Runtime<'a> { } } - pub fn editor(&self) -> String { + pub fn editor(&self) -> Command { use std::env::var; - if let Some(editor) = self.config.editor() { - editor + &self.config.editor_opts()[..] - } else if let Some(editor) = self.configuration.editor() { - editor + &self.configuration.editor_opts()[..] - } else if let Ok(editor) = var("EDITOR") { - editor - } else { - String::from("vim") + let (editor, args) : (String, String) = { + if let Some(editor) = self.config.editor() { + (editor, self.config.editor_opts()) + } else if let Some(editor) = self.configuration.editor() { + (editor, self.configuration.editor_opts()) + } else if let Ok(editor) = var("EDITOR") { + (editor, String::from("")) + } else { + (String::from("vim"), String::from("")) + } + }; + + let mut e = Command::new(editor); + for arg in args.split(" ") { + e.arg(arg); } + e } } diff --git a/src/storage/file/mod.rs b/src/storage/file/mod.rs index 4dde562a..19866a81 100644 --- a/src/storage/file/mod.rs +++ b/src/storage/file/mod.rs @@ -59,6 +59,13 @@ impl File { &self.data } + /** + * Set the data + */ + pub fn set_data(&mut self, new_data: String) { + self.data = new_data; + } + /** * Set the (header, data) of the file */ diff --git a/src/ui/external/editor.rs b/src/ui/external/editor.rs index d6030a5e..fb43895c 100644 --- a/src/ui/external/editor.rs +++ b/src/ui/external/editor.rs @@ -20,7 +20,7 @@ pub fn let_user_provide_content(rt: &Runtime) -> Option { } let output = { - let mut cmd = Command::new(rt.editor()); + let mut cmd = rt.editor(); cmd.arg(filepath); debug!("cmd = {:?}", cmd); cmd.spawn() @@ -49,3 +49,60 @@ pub fn let_user_provide_content(rt: &Runtime) -> Option { Some(contents) }).unwrap_or(None) } + +/** + * Edit some content in a temporary file. If anything failes within this routine, it returns the + * old content and false. + * If the editing succeeded, it returns the new content and true + */ +pub fn edit_content(rt: &Runtime, old_content: String) -> (String, bool) { + use std::io::Read; + use std::io::Write; + use std::fs::File; + use std::process::Command; + use std::process::exit; + + let filepath = "/tmp/imag-tmp.md"; + { + let mut file = match File::create(filepath) { + Ok(f) => f, + Err(e) => { + error!("Error creating file {}", filepath); + debug!("Error creating file at '{}', error = {}", filepath, e); + exit(1); + } + }; + + file.write(old_content.as_ref()); + } + debug!("Ready with putting old content into the file"); + + let output = { + let mut cmd = rt.editor(); + cmd.arg(filepath); + debug!("cmd = {:?}", cmd); + cmd.spawn() + .and_then(|child| child.wait_with_output()) + }; + + let process_out = output.map_err(|e| { + error!("Editor call failed"); + debug!("Editor call failed: {:?}", e); + return None as Option; + }).unwrap(); + + if !process_out.status.success() { + error!("Editor call failed"); + debug!("status = {:?}", process_out.status); + debug!("stdout = {:?}", String::from_utf8(process_out.stdout)); + debug!("stderr = {:?}", String::from_utf8(process_out.stderr)); + return (old_content, false); + } + + let mut contents = String::new(); + File::open(filepath).map(|mut file| { + file.read_to_string(&mut contents); + (contents, true) + }).unwrap_or((old_content, false)) +} +