Auto merge of #33 - matthiasbeyer:add-notes, r=matthiasbeyer

Add module: notes (journal)

Module: Notes/journal

Closes #14

Tracking branch for the module implementation.
This commit is contained in:
Homu 2016-01-03 04:07:48 +09:00
commit 670f0e16e9
13 changed files with 674 additions and 84 deletions

14
Cargo.lock generated
View file

@ -9,6 +9,7 @@ dependencies = [
"log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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)", "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)", "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]] [[package]]
name = "glob" name = "glob"
version = "0.2.10" version = "0.2.10"
@ -156,6 +162,14 @@ dependencies = [
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "rand" name = "rand"
version = "0.3.11" version = "0.3.11"

View file

@ -27,4 +27,5 @@ term = "0.2.12"
term_grid = "0.1.2" term_grid = "0.1.2"
prettytable-rs = "0.4.0" prettytable-rs = "0.4.0"
open = "1.1.0" open = "1.1.0"
pulldown-cmark = "0.0.3"

View file

@ -258,3 +258,110 @@ subcommands:
version: 0.1 version: 0.1
author: Matthias Beyer <mail@beyermatthias.de> author: Matthias Beyer <mail@beyermatthias.de>
- notes:
about: Notes module
version: 0.1
author: Matthias Beyer <mail@beyermatthias.de>
subcommands:
- add:
about: Add Note
version: 0.1
author: Matthias Beyer <mail@beyermatthias.de>
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 <mail@beyermatthias.de>
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 <mail@beyermatthias.de>
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 <mail@beyermatthias.de>
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

View file

@ -65,8 +65,8 @@ impl Configuration {
self.editor.clone() self.editor.clone()
} }
pub fn editor_opts(&self) -> &String { pub fn editor_opts(&self) -> String {
&self.editor_opts self.editor_opts.clone()
} }
} }

View file

@ -25,6 +25,7 @@ pub mod ui;
pub mod util; pub mod util;
pub use module::bm::BM; pub use module::bm::BM;
pub use module::notes::Notes;
fn main() { fn main() {
let yaml = load_yaml!("../etc/cli.yml"); let yaml = load_yaml!("../etc/cli.yml");
@ -43,10 +44,11 @@ fn main() {
debug!("Runtime : {:?}", &rt); debug!("Runtime : {:?}", &rt);
if let Some(matches) = rt.config.cli_matches.subcommand_matches("bm") { let res = match rt.config.cli_matches.subcommand_name() {
let res = BM::new(&rt).exec(matches); Some("bm") => BM::new(&rt).exec(rt.config.cli_matches.subcommand_matches("bm").unwrap()),
info!("BM exited with {}", res); Some("notes") => Notes::new(&rt).exec(rt.config.cli_matches.subcommand_matches("notes").unwrap()),
} else { _ => false,
info!("No commandline call...") };
}
info!("Module execution ended with {}", res);
} }

View file

@ -243,94 +243,44 @@ impl<'a> BM<'a> {
* Subcommand: add_tags * Subcommand: add_tags
*/ */
fn command_add_tags(&self, matches: &ArgMatches) -> bool { 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(); let mut new_tags = old_tags.clone();
new_tags.append(&mut cli_tags.clone()); new_tags.append(&mut cli_tags.clone());
new_tags new_tags
}) }, rebuild_header_with_tags)
} }
/** /**
* Subcommand: rm_tags * Subcommand: rm_tags
*/ */
fn command_rm_tags(&self, matches: &ArgMatches) -> bool { 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() old_tags.clone()
.into_iter() .into_iter()
.filter(|tag| !cli_tags.contains(tag)) .filter(|tag| !cli_tags.contains(tag))
.collect() .collect()
}) }, rebuild_header_with_tags)
} }
/** /**
* Subcommand: set_tags * Subcommand: set_tags
*/ */
fn command_set_tags(&self, matches: &ArgMatches) -> bool { fn command_set_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;
cli_tags.clone()
})
}
/**
* Helper function to alter the tags in a file
*/
fn alter_tags_in_files<F>(&self, matches: &ArgMatches, generate_new_tags: F) -> bool
where F: Fn(Vec<String>, &Vec<String>) -> Vec<String>
{
use self::header::rebuild_header_with_tags; use self::header::rebuild_header_with_tags;
let cli_tags = matches.value_of("tags")
.map(|ts| {
ts.split(",")
.map(String::from)
.collect::<Vec<String>>()
})
.unwrap_or(vec![]);
let parser = Parser::new(JsonHeaderParser::new(None)); let parser = Parser::new(JsonHeaderParser::new(None));
alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
let filter = { cli_tags.clone()
let hash_filter = create_hash_filter(matches, "with:id", false); }, rebuild_header_with_tags)
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)
} }
} }
@ -380,6 +330,10 @@ impl<'a> Module<'a> for BM<'a> {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"bookmark" "bookmark"
} }
fn runtime(&self) -> &Runtime {
self.rt
}
} }
impl<'a> Debug for BM<'a> { impl<'a> Debug for BM<'a> {

View file

@ -35,6 +35,10 @@ pub mod spec {
pub mod data { pub mod data {
use std::ops::Deref; use std::ops::Deref;
use storage::file::header::data::FileHeaderData as FHD; 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<String> to build a Tag-Array: * Use a Vec<String> to build a Tag-Array:
@ -94,5 +98,77 @@ pub mod data {
tags tags
} }
/**
* Helper function to alter the tags in a file
*/
pub fn alter_tags_in_files<HP, F, R>(m: &Module,
matches: &ArgMatches,
parser: &Parser<HP>,
generate_new_tags: F,
rebuild_header: R) -> bool
where HP: FileHeaderParser,
F: Fn(Vec<String>, &Vec<String>) -> Vec<String>,
R: Fn(&FHD, Vec<String>) -> Option<FHD>
{
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::<Vec<String>>()
})
.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)
}
} }

View file

@ -2,8 +2,11 @@ use std::fmt::Debug;
use clap::ArgMatches; use clap::ArgMatches;
use runtime::Runtime;
pub mod bm; pub mod bm;
pub mod helpers; pub mod helpers;
pub mod notes;
/** /**
* Module interface, each module has to implement this. * Module interface, each module has to implement this.
@ -11,5 +14,7 @@ pub mod helpers;
pub trait Module<'a> : Debug { pub trait Module<'a> : Debug {
fn exec(&self, matches: &ArgMatches) -> bool; fn exec(&self, matches: &ArgMatches) -> bool;
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
fn runtime(&self) -> &Runtime;
} }

View file

@ -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<String>) -> 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<String> {
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<String>) -> Option<FHD> {
let name = get_name_from_header(header);
Some(build_header(name, tags))
}

294
src/module/notes/mod.rs Normal file
View file

@ -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(())
}
}

View file

@ -1,4 +1,5 @@
use std::fmt::{Debug, Formatter, Error}; use std::fmt::{Debug, Formatter, Error};
use std::process::Command;
extern crate log; extern crate log;
use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata, SetLoggerError}; 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; use std::env::var;
if let Some(editor) = self.config.editor() { let (editor, args) : (String, String) = {
editor + &self.config.editor_opts()[..] if let Some(editor) = self.config.editor() {
} else if let Some(editor) = self.configuration.editor() { (editor, self.config.editor_opts())
editor + &self.configuration.editor_opts()[..] } else if let Some(editor) = self.configuration.editor() {
} else if let Ok(editor) = var("EDITOR") { (editor, self.configuration.editor_opts())
editor } else if let Ok(editor) = var("EDITOR") {
} else { (editor, String::from(""))
String::from("vim") } else {
(String::from("vim"), String::from(""))
}
};
let mut e = Command::new(editor);
for arg in args.split(" ") {
e.arg(arg);
} }
e
} }
} }

View file

@ -59,6 +59,13 @@ impl File {
&self.data &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 * Set the (header, data) of the file
*/ */

View file

@ -20,7 +20,7 @@ pub fn let_user_provide_content(rt: &Runtime) -> Option<String> {
} }
let output = { let output = {
let mut cmd = Command::new(rt.editor()); let mut cmd = rt.editor();
cmd.arg(filepath); cmd.arg(filepath);
debug!("cmd = {:?}", cmd); debug!("cmd = {:?}", cmd);
cmd.spawn() cmd.spawn()
@ -49,3 +49,60 @@ pub fn let_user_provide_content(rt: &Runtime) -> Option<String> {
Some(contents) Some(contents)
}).unwrap_or(None) }).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<String>;
}).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))
}