summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--bin/core/imag-header/Cargo.toml49
l---------bin/core/imag-header/README.md1
-rw-r--r--bin/core/imag-header/src/main.rs363
-rw-r--r--bin/core/imag-header/src/ui.rs244
-rw-r--r--doc/src/04020-module-header.md4
-rw-r--r--scripts/release.sh1
7 files changed, 663 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 742f533f..20f7d34f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ members = [
"bin/core/imag-git",
"bin/core/imag-gps",
"bin/core/imag-grep",
+ "bin/core/imag-header",
"bin/core/imag-ids",
"bin/core/imag-init",
"bin/core/imag-link",
diff --git a/bin/core/imag-header/Cargo.toml b/bin/core/imag-header/Cargo.toml
new file mode 100644
index 00000000..9287f3b6
--- /dev/null
+++ b/bin/core/imag-header/Cargo.toml
@@ -0,0 +1,49 @@
+[package]
+name = "imag-header"
+version = "0.10.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-entry command"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../../../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://imag-pim.org/doc/"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+build = "../../../build.rs"
+
+[badges]
+travis-ci = { repository = "matthiasbeyer/imag" }
+is-it-maintained-issue-resolution = { repository = "matthiasbeyer/imag" }
+is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+log = "0.4"
+version = "2.0.1"
+toml = "0.4"
+toml-query = "0.8"
+filters = "0.3"
+failure = "0.1"
+
+libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
+libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" }
+libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
+libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" }
+libimagentryedit = { version = "0.10.0", path = "../../../lib/entry/libimagentryedit" }
+libimagentryview = { version = "0.10.0", path = "../../../lib/entry/libimagentryview" }
+
+[dependencies.clap]
+version = "^2.29"
+default-features = false
+features = ["color", "suggestions", "wrap_help"]
+
+[dev-dependencies.libimagrt]
+version = "0.10.0"
+path = "../../../lib/core/libimagrt"
+default-features = false
+features = ["testing"]
+
diff --git a/bin/core/imag-header/README.md b/bin/core/imag-header/README.md
new file mode 120000
index 00000000..490d7257
--- /dev/null
+++ b/bin/core/imag-header/README.md
@@ -0,0 +1 @@
+../../../doc/src/04020-module-header.md \ No newline at end of file
diff --git a/bin/core/imag-header/src/main.rs b/bin/core/imag-header/src/main.rs
new file mode 100644
index 00000000..1ee8440c
--- /dev/null
+++ b/bin/core/imag-header/src/main.rs
@@ -0,0 +1,363 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate clap;
+#[macro_use] extern crate log;
+extern crate toml;
+extern crate toml_query;
+extern crate filters;
+extern crate failure;
+
+extern crate libimagentryedit;
+extern crate libimagerror;
+#[macro_use] extern crate libimagrt;
+extern crate libimagstore;
+extern crate libimagutil;
+
+use std::io::Write;
+use std::str::FromStr;
+
+use clap::ArgMatches;
+use filters::filter::Filter;
+use failure::Error;
+
+use libimagerror::exit::ExitCode;
+use libimagerror::io::ToExitCode;
+use libimagerror::iter::TraceIterator;
+use libimagerror::trace::MapErrTrace;
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+use libimagstore::iter::get::StoreIdGetIteratorExtension;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::StoreIdIterator;
+
+use toml_query::read::TomlValueReadExt;
+
+mod ui;
+
+fn main() {
+ let version = make_imag_version!();
+ let rt = generate_runtime_setup("imag-header",
+ &version,
+ "Plumbing tool for reading/writing structured data in entries",
+ ui::build_ui);
+
+ let list_output_with_ids = rt.cli().is_present("list-id");
+ let list_output_with_ids_fmt = rt.cli().value_of("list-id-format");
+
+ trace!("list_output_with_ids = {:?}", list_output_with_ids );
+ trace!("list_output_with_ids_fmt = {:?}", list_output_with_ids_fmt);
+
+ let sids = rt.ids::<::ui::PathProvider>().map_err_trace_exit_unwrap();
+
+ let iter = StoreIdIterator::new(Box::new(sids.into_iter().map(Ok)))
+ .into_get_iter(rt.store())
+ .trace_unwrap_exit()
+ .filter_map(|x| x);
+
+ let exit_code = match rt.cli().subcommand() {
+ ("read", Some(mtch)) => read(&rt, mtch, iter),
+ ("has", Some(mtch)) => has(&rt, mtch, iter),
+ ("hasnt", Some(mtch)) => hasnt(&rt, mtch, iter),
+ ("int", Some(mtch)) => int(&rt, mtch, iter),
+ ("float", Some(mtch)) => float(&rt, mtch, iter),
+ ("string", Some(mtch)) => string(&rt, mtch, iter),
+ ("bool", Some(mtch)) => boolean(&rt, mtch, iter),
+ (other, _mtchs) => {
+ debug!("Unknown command");
+ rt.handle_unknown_subcommand("imag-header", other, rt.cli())
+ .map_err_trace_exit_unwrap()
+ .code()
+ .unwrap_or(1)
+ },
+ };
+
+ ::std::process::exit(exit_code)
+}
+
+fn read<'a, 'e, I>(rt: &Runtime, mtch: &ArgMatches<'a>, iter: I) -> i32
+ where I: Iterator<Item = FileLockEntry<'e>>
+{
+ debug!("Processing headers: reading value");
+ let header_path = get_header_path(mtch, "header-value-path");
+ let mut output = rt.stdout();
+
+ iter.fold(0, |accu, entry| {
+ trace!("Processing headers: working on {:?}", entry.get_location());
+ entry.get_header()
+ .read(header_path)
+ .map_err(Error::from)
+ .map_err_trace_exit_unwrap()
+ .map(|value| {
+ trace!("Processing headers: Got value {:?}", value);
+ writeln!(output, "{}", value)
+ .to_exit_code()
+ .map(|_| accu)
+ .unwrap_or_else(ExitCode::code)
+ })
+ .unwrap_or_else(|| {
+ // if value not present and configured
+ error!("Value not present for entry {} at {}", entry.get_location(), header_path);
+ 1
+ })
+ })
+}
+
+fn has<'a, 'e, I>(rt: &Runtime, mtch: &ArgMatches<'a>, iter: I) -> i32
+ where I: Iterator<Item = FileLockEntry<'e>>
+{
+ debug!("Processing headers: has value");
+ let header_path = get_header_path(mtch, "header-value-path");
+ let mut output = rt.stdout();
+
+ iter.fold(0, |accu, entry| {
+ trace!("Processing headers: working on {:?}", entry.get_location());
+ let value = entry.get_header()
+ .read(header_path)
+ .map_err(Error::from)
+ .map_err_trace_exit_unwrap()
+ .is_some();
+
+ writeln!(output, "{} - {}", entry.get_location(), value)
+ .to_exit_code()
+ .map(|_| if value { accu } else { 1 })
+ .unwrap_or_else(ExitCode::code)
+ })
+}
+
+fn hasnt<'a, 'e, I>(rt: &Runtime, mtch: &ArgMatches<'a>, iter: I) -> i32
+ where I: Iterator<Item = FileLockEntry<'e>>
+{
+ debug!("Processing headers: hasnt value");
+ let header_path = get_header_path(mtch, "header-value-path");
+ let mut output = rt.stdout();
+
+ iter.fold(0, |accu, entry| {
+ trace!("Processing headers: working on {:?}", entry.get_location());
+ let value = entry.get_header()
+ .read(header_path)
+ .map_err(Error::from)
+ .map_err_trace_exit_unwrap()
+ .is_none();
+
+ writeln!(output, "{} - {}", entry.get_location(), value)
+ .to_exit_code()
+ .map(|_| if value { accu } else { 1 })
+ .unwrap_or_else(ExitCode::code)
+ })
+}
+
+macro_rules! implement_compare {
+ { $mtch: ident, $path: expr, $t: ty, $compare: expr } => {{
+ trace!("Getting value at {}, comparing as {}", $path, stringify!($t));
+ if let Some(cmp) = $mtch.value_of($path).map(FromStr::from_str) {
+ let cmp : $t = cmp.unwrap(); // safe by clap
+ trace!("Getting value at {} = {}", $path, cmp);
+ $compare(cmp)
+ } else {
+ true
+ }
+ }}
+}
+
+fn int<'a, 'e, I>(rt: &Runtime, mtch: &ArgMatches<'a>, iter: I) -> i32
+ where I: Iterator<Item = FileLockEntry<'e>>
+{
+ debug!("Processing headers: int value");
+ let header_path = get_header_path(mtch, "header-value-path");
+ let mut output = rt.stdout();
+
+ let filter = ::filters::ops::bool::Bool::new(true)
+ .and(|i: &i64| -> bool {
+ implement_compare!(mtch, "header-int-eq", i64, |cmp| *i == cmp)
+ })
+ .and(|i: &i64| -> bool {
+ implement_compare!(mtch, "header-int-neq", i64, |cmp| *i != cmp)
+ })
+ .and(|i: &i64| -> bool {
+ implement_compare!(mtch, "header-int-lt", i64, |cmp| *i < cmp)
+ })
+ .and(|i: &i64| -> bool {
+ implement_compare!(mtch, "header-int-gt", i64, |cmp| *i > cmp)
+ })
+ .and(|i: &i64| -> bool {
+ implement_compare!(mtch, "header-int-lte", i64, |cmp| *i <= cmp)
+ })
+ .and(|i: &i64| -> bool {
+ implement_compare!(mtch, "header-int-gte", i64, |cmp| *i >= cmp)
+ });
+
+ iter.fold(0, |accu, entry| {
+ trace!("Processing headers: working on {:?}", entry.get_location());
+ if let Some(v) = entry.get_header()
+ .read(header_path)
+ .map_err(Error::from)
+ .map_err_trace_exit_unwrap()
+ {
+ match v {
+ ::toml::Value::Integer(i) => if filter.filter(&i) {
+ writeln!(output, "{} - {}", entry.get_location(), i)
+ .to_exit_code()
+ .map(|_| accu)
+ .unwrap_or_else(ExitCode::code)
+ } else { 1 },
+ _ => 1
+ }
+ } else { 1 }
+ })
+}
+
+fn float<'a, 'e, I>(rt: &Runtime, mtch: &ArgMatches<'a>, iter: I) -> i32
+ where I: Iterator<Item = FileLockEntry<'e>>
+{
+ debug!("Processing headers: float value");
+ let header_path = get_header_path(mtch, "header-value-path");
+ let mut output = rt.stdout();
+
+ let filter = ::filters::ops::bool::Bool::new(true)
+ .and(|i: &f64| -> bool {
+ implement_compare!(mtch, "header-float-eq", f64, |cmp| *i == cmp)
+ })
+ .and(|i: &f64| -> bool {
+ implement_compare!(mtch, "header-float-neq", f64, |cmp| *i != cmp)
+ })
+ .and(|i: &f64| -> bool {
+ implement_compare!(mtch, "header-float-lt", f64, |cmp| *i < cmp)
+ })
+ .and(|i: &f64| -> bool {
+ implement_compare!(mtch, "header-float-gt", f64, |cmp| *i > cmp)
+ })
+ .and(|i: &f64| -> bool {
+ implement_compare!(mtch, "header-float-lte", f64, |cmp| *i <= cmp)
+ })
+ .and(|i: &f64| -> bool {
+ implement_compare!(mtch, "header-float-gte", f64, |cmp| *i >= cmp)
+ });
+
+ iter.fold(0, |accu, entry| {
+ trace!("Processing headers: working on {:?}", entry.get_location());
+ if let Some(v) = entry.get_header()
+ .read(header_path)
+ .map_err(Error::from)
+ .map_err_trace_exit_unwrap()
+ {
+ match v {
+ ::toml::Value::Float(i) => if filter.filter(&i) {
+ writeln!(output, "{} - {}", entry.get_location(), i)
+ .to_exit_code()
+ .map(|_| accu)
+ .unwrap_or_else(ExitCode::code)
+ } else { 1 },
+ _ => 1
+ }
+ } else { 1 }
+ })
+}
+
+fn string<'a, 'e, I>(rt: &Runtime, mtch: &ArgMatches<'a>, iter: I) -> i32
+ where I: Iterator<Item = FileLockEntry<'e>>
+{
+ debug!("Processing headers: string value");
+ let header_path = get_header_path(mtch, "header-value-path");
+ let mut output = rt.stdout();
+
+ let filter = ::filters::ops::bool::Bool::new(true)
+ .and(|i: &String| -> bool {
+ implement_compare!(mtch, "header-string-eq", String, |cmp| *i == cmp)
+ })
+ .and(|i: &String| -> bool {
+ implement_compare!(mtch, "header-string-neq", String, |cmp| *i != cmp)
+ });
+
+ iter.fold(0, |accu, entry| {
+ trace!("Processing headers: working on {:?}", entry.get_location());
+ if let Some(v) = entry.get_header()
+ .read(header_path)
+ .map_err(Error::from)
+ .map_err_trace_exit_unwrap()
+ {
+ match v {
+ ::toml::Value::String(s) => if filter.filter(&s) {
+ writeln!(output, "{} - {}", entry.get_location(), s)
+ .to_exit_code()
+ .map(|_| accu)
+ .unwrap_or_else(ExitCode::code)
+ } else { 1 },
+ _ => 1
+ }
+ } else { 1 }
+ })
+}
+
+fn boolean<'a, 'e, I>(rt: &Runtime, mtch: &ArgMatches<'a>, iter: I) -> i32
+ where I: Iterator<Item = FileLockEntry<'e>>
+{
+ debug!("Processing headers: bool value");
+ let header_path = get_header_path(mtch, "header-value-path");
+ let mut output = rt.stdout();
+
+ let filter = ::filters::ops::bool::Bool::new(true)
+ .and(|i: &bool| -> bool { *i })
+ .and(|i: &bool| -> bool { *i });
+
+ iter.fold(0, |accu, entry| {
+ trace!("Processing headers: working on {:?}", entry.get_location());
+ if let Some(v) = entry.get_header()
+ .read(header_path)
+ .map_err(Error::from)
+ .map_err_trace_exit_unwrap()
+ {
+ match v {
+ ::toml::Value::Boolean(b) => if filter.filter(&b) {
+ writeln!(output, "{} - {}", entry.get_location(), b)
+ .to_exit_code()
+ .map(|_| accu)
+ .unwrap_or_else(ExitCode::code)
+ } else { 1 },
+ _ => 1
+ }
+ } else { 1 }
+ })
+}
+
+
+
+// helpers
+//
+fn get_header_path<'a>(mtch: &'a ArgMatches<'a>, path: &'static str) -> &'a str {
+ let header_path = mtch.value_of(path).unwrap(); // safe by clap
+ debug!("Processing headers: header path = {}", header_path);
+ header_path
+}
+
diff --git a/bin/core/imag-header/src/ui.rs b/bin/core/imag-header/src/ui.rs
new file mode 100644
index 00000000..f42030eb
--- /dev/null
+++ b/bin/core/imag-header/src/ui.rs
@@ -0,0 +1,244 @@
+//
+// imag - the personal information management suite for the commandline
+// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; version
+// 2.1 of the License.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+use std::path::PathBuf;
+
+use clap::{Arg, ArgMatches, App, SubCommand};
+
+use libimagstore::storeid::StoreId;
+use libimagstore::storeid::IntoStoreId;
+use libimagrt::runtime::IdPathProvider;
+use libimagerror::trace::MapErrTrace;
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .arg(Arg::with_name("id")
+ .index(1)
+ .takes_value(true)
+ .required(false)
+ .multiple(true)
+ .help("The id of the entry/entries to edit")
+ .value_name("ENTRY"))
+
+ .arg(Arg::with_name("list-id")
+ .long("list-id")
+ .takes_value(false)
+ .required(false)
+ .multiple(false)
+ .help("List Store Id in output (format: '<id> - <value>'"))
+ .arg(Arg::with_name("list-id-format")
+ .long("list-id-format")
+ .takes_value(true)
+ .required(false)
+ .multiple(false)
+ .help("List Store Id in output with format"))
+
+ .subcommand(SubCommand::with_name("read")
+ .about("Read a header value by path")
+ .version("0.1")
+ .arg(Arg::with_name("header-value-path")
+ .index(1)
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .help("Path of the header value")
+ .value_name("PATH"))
+ )
+
+ .subcommand(SubCommand::with_name("has")
+ .about("Check whether a header value is present")
+ .version("0.1")
+ .arg(Arg::with_name("header-value-path")
+ .index(1)
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .help("Path of the header value")
+ .value_name("PATH"))
+ )
+
+ .subcommand(SubCommand::with_name("hasnt")
+ .about("Check whether a header value is not present")
+ .version("0.1")
+ .arg(Arg::with_name("header-value-path")
+ .index(1)
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .help("Path of the header value")
+ .value_name("PATH"))
+ )
+
+ .subcommand(SubCommand::with_name("int")
+ .about("Check whether a header value is a number")
+ .version("0.1")
+ .arg(Arg::with_name("header-value-path")
+ .index(1)
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .help("Path of the header value")
+ .value_name("PATH"))
+
+ .arg(Arg::with_name("header-int-eq")
+ .long("eq")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is equal to VALUE")
+ .validator(::libimagutil::cli_validators::is_integer)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-int-neq")
+ .long("neq")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is not equal to VALUE")
+ .validator(::libimagutil::cli_validators::is_integer)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-int-lt")
+ .long("lt")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is lower than VALUE")
+ .validator(::libimagutil::cli_validators::is_integer)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-int-gt")
+ .long("gt")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is greater than VALUE")
+ .validator(::libimagutil::cli_validators::is_integer)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-int-lte")
+ .long("lte")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is lower than or equal VALUE")
+ .validator(::libimagutil::cli_validators::is_integer)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-int-gte")
+ .long("gte")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is greater than or equal VALUE")
+ .validator(::libimagutil::cli_validators::is_integer)
+ .value_name("VALUE"))
+ )
+
+ .subcommand(SubCommand::with_name("float")
+ .about("Check whether a header value is a floating number")
+ .version("0.1")
+ .arg(Arg::with_name("header-value-path")
+ .index(1)
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .help("Path of the header value")
+ .value_name("PATH"))
+
+ .arg(Arg::with_name("header-float-eq")
+ .long("eq")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is equal to VALUE")
+ .validator(::libimagutil::cli_validators::is_float)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-float-neq")
+ .long("neq")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is not equal to VALUE")
+ .validator(::libimagutil::cli_validators::is_float)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-float-lt")
+ .long("lt")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is lower than VALUE")
+ .validator(::libimagutil::cli_validators::is_float)
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-float-gt")
+ .long("gt")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is greater than VALUE")
+ .validator(::libimagutil::cli_validators::is_float)
+ .value_name("VALUE"))
+ )
+
+ .subcommand(SubCommand::with_name("string")
+ .about("Check whether a header value is a string")
+ .version("0.1")
+ .arg(Arg::with_name("header-value-path")
+ .index(1)
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .help("Path of the header value")
+ .value_name("PATH"))
+
+ .arg(Arg::with_name("header-string-eq")
+ .long("eq")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is equal to VALUE")
+ .value_name("VALUE"))
+ .arg(Arg::with_name("header-string-neq")
+ .long("neq")
+ .takes_value(true)
+ .required(false)
+ .help("Check whether the value is not equal to VALUE")
+ .value_name("VALUE"))
+ )
+
+ .subcommand(SubCommand::with_name("bool")
+ .about("Check whether a header value is a bool")
+ .version("0.1")
+ .arg(Arg::with_name("header-value-path")
+ .index(1)
+ .takes_value(true)
+ .required(true)
+ .multiple(false)
+ .help("Path of the header value")
+ .value_name("PATH"))
+
+ .arg(Arg::with_name("header-bool-set")
+ .short("t")
+ .long("set")
+ .takes_value(false)
+ .required(false)
+ .help("Check whether the flag is set (true)"))
+ .arg(Arg::with_name("header-bool-unset")
+ .short("f")
+ .long("unset")
+ .takes_value(false)
+ .required(false)
+ .help("Check whether the flag is unset (false)"))
+ )
+}
+
+pub struct PathProvider;
+impl IdPathProvider for PathProvider {
+ fn get_ids(matches: &ArgMatches) -> Vec<StoreId> {
+ matches.values_of("id")
+ .unwrap()
+ .map(|s| PathBuf::from(s).into_storeid().map_err_trace_exit_unwrap())
+ .collect()
+ }
+}
+
diff --git a/doc/src/04020-module-header.md b/doc/src/04020-module-header.md
new file mode 100644
index 00000000..888a9f82
--- /dev/null
+++ b/doc/src/04020-module-header.md
@@ -0,0 +1,4 @@
+## Entry {#sec:modules:entry}
+
+Plumbing tool for modifying and querying structured data in entries.
+
diff --git a/scripts/release.sh b/scripts/release.sh
index 0d971bb2..5c41d0e8 100644
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -63,6 +63,7 @@ CRATES=(
./bin/core/imag-ids
./bin/core/imag-git
./bin/core/imag-category
+ ./bin/core/imag-header
./bin/core/imag
)