use dev_prefix::*;
use serde_json;
use tabwriter::TabWriter;
use super::types::*;
use super::display;
pub fn get_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("ls")
.about("list artifacts according to various parameters")
.settings(&[AS::DeriveDisplayOrder, COLOR])
.arg(Arg::with_name("search")
.help("artifact names given in form `REQ-foo-[bar, baz-[1,2]]` OR pearl regexp \
pattern if -p is given")
.use_delimiter(false))
.arg(Arg::with_name("pattern")
.short("p")
.help("search FIELDS using pearl regexp SEARCH.")
.value_name("FIELDS")
.takes_value(true)
.max_values(1)
.min_values(0))
.arg(Arg::with_name("long")
.short("l")
.help("print items in the 'long form'"))
.arg(Arg::with_name("completed")
.short("c")
.help("filter by completeness (ie `<45`), < and > are inclusive, '>' == `>100`")
.takes_value(true))
.arg(Arg::with_name("tested")
.short("t")
.help("give a filter for the testedness in %. see '-c'")
.takes_value(true))
.arg(Arg::with_name("all")
.short("A")
.help("If set, additional flags will be *deactivated* instead of activated"))
.arg(Arg::with_name("path")
.short("D")
.help("display the path where the artifact is defined"))
.arg(Arg::with_name("parts")
.short("P")
.help("display the parts of the artifact"))
.arg(Arg::with_name("partof")
.short("O")
.help("display the artifacts which this artifact is a partof"))
.arg(Arg::with_name("loc")
.short("L")
.help("display location name"))
.arg(Arg::with_name("text")
.short("T")
.help("display the text description of this artifact (first line only if not -l)"))
.arg(Arg::with_name("plain")
.long("plain")
.help("do not display color in the output"))
.arg(Arg::with_name("type")
.long("type")
.value_name("TYPE")
.takes_value(true)
.help("output type, default 'list'. Supported types are: list, json"))
}
pub fn _get_percent(s: &str) -> result::Result<(Option<bool>, Option<i8>), String> {
let mut s = s;
let mut lt = None;
if s.is_empty() {
return Ok((lt, None));
}
let mut had_sign = true;
match s.chars().next().unwrap() {
'<' => lt = Some(true),
'>' => lt = Some(false),
'0'...'9' => had_sign = false,
_ => {
return Err("percent must be of the form: [SIGN]NUM where NUM is between 0 and 100 and \
SIGN is an optional < or >"
.to_string())
}
}
if had_sign {
s = s.split_at(1).1;
if s.is_empty() {
return Ok((lt, None));
}
}
if s.is_empty() {
return Ok((lt, None));
}
match s.parse::<i8>() {
Ok(v) => {
if v <= 100 && v >= -100 {
Ok((lt, Some(v)))
} else {
Err("NUM must be between -100 and 100".to_string())
}
}
Err(e) => Err(e.to_string()),
}
}
fn get_percent(s: &str) -> result::Result<PercentSearch, String> {
Ok(match _get_percent(s) {
Ok((lt, perc)) => {
if lt.is_none() && perc.is_none() {
PercentSearch {
lt: false,
perc: 100,
}
} else if perc.is_none() {
if lt.unwrap() {
PercentSearch {
lt: true,
perc: 0,
}
} else {
PercentSearch {
lt: false,
perc: 100,
}
}
} else {
let lt = match lt {
None => false,
Some(l) => l,
};
let perc = match perc {
None => 100,
Some(p) => p,
};
PercentSearch {
lt: lt,
perc: perc,
}
}
}
Err(e) => return Err(e),
})
}
#[test]
fn test_get_percent() {
assert_eq!(_get_percent(""), Ok((None, None)));
assert_eq!(_get_percent("<"), Ok((Some(true), None)));
assert_eq!(_get_percent(">"), Ok((Some(false), None)));
assert_eq!(_get_percent("<10"), Ok((Some(true), Some(10))));
assert_eq!(_get_percent(">100"), Ok((Some(false), Some(100))));
assert_eq!(_get_percent(">-100"), Ok((Some(false), Some(-100))));
assert_eq!(get_percent(""),
Ok(PercentSearch {
lt: false,
perc: 100,
}));
assert_eq!(get_percent("<"),
Ok(PercentSearch {
lt: true,
perc: 0,
}));
assert_eq!(get_percent(">"),
Ok(PercentSearch {
lt: false,
perc: 100,
}));
assert_eq!(get_percent("89"),
Ok(PercentSearch {
lt: false,
perc: 89,
}));
assert_eq!(get_percent(">89"),
Ok(PercentSearch {
lt: false,
perc: 89,
}));
assert_eq!(get_percent("<89"),
Ok(PercentSearch {
lt: true,
perc: 89,
}));
assert_eq!(get_percent(">-1"),
Ok(PercentSearch {
lt: false,
perc: -1,
}));
assert!(get_percent(">101").is_err());
assert!(get_percent("a").is_err());
assert!(get_percent("<a").is_err());
}
#[cfg(not(windows))]
fn get_color(matches: &ArgMatches) -> bool {
!matches.is_present("plain")
}
#[cfg(windows)]
fn get_color(matches: &ArgMatches) -> bool {
false
}
#[derive(Debug, Eq, PartialEq)]
pub enum OutType {
List, Json,
}
#[derive(Debug)]
pub struct Cmd {
pub pattern: String,
pub fmt_settings: FmtSettings,
pub search_settings: SearchSettings,
pub ty: OutType,
}
pub fn get_cmd(matches: &ArgMatches) -> Result<Cmd> {
let mut fmt_set = FmtSettings::default();
fmt_set.long = matches.is_present("long");
fmt_set.path = matches.is_present("path");
fmt_set.parts = matches.is_present("parts");
fmt_set.partof = matches.is_present("partof");
fmt_set.loc_path = matches.is_present("loc");
fmt_set.text = matches.is_present("text");
fmt_set.color = get_color(matches);
if matches.is_present("all") {
fmt_set.path = !fmt_set.path;
fmt_set.parts = !fmt_set.parts;
fmt_set.partof = !fmt_set.partof;
fmt_set.loc_path = !fmt_set.loc_path;
fmt_set.text = !fmt_set.text;
} else if fmt_set.long &&
!(fmt_set.path || fmt_set.parts || fmt_set.partof || fmt_set.loc_path ||
fmt_set.text) {
fmt_set.path = true;
fmt_set.parts = true;
fmt_set.partof = true;
fmt_set.loc_path = true;
fmt_set.text = true;
}
let mut search_set = match (matches.is_present("pattern"), matches.value_of("pattern")) {
(true, Some(p)) => SearchSettings::from_str(p)?,
(true, None) => SearchSettings::from_str("N").unwrap(),
(false, None) => SearchSettings::default(),
_ => unreachable!(),
};
if let Some(c) = matches.value_of("completed") {
search_set.completed = try!(get_percent(c))
}
if let Some(t) = matches.value_of("tested") {
debug!("got tested: {}", t);
search_set.tested = try!(get_percent(t));
}
let ty = match matches.value_of("type").unwrap_or("list") {
"list" => OutType::List,
"json" => OutType::Json,
t => {
let msg = format!("invalid type: {}", t);
return Err(ErrorKind::CmdError(msg).into());
}
};
let cmd = Cmd {
pattern: matches.value_of("search").unwrap_or("").to_string(),
fmt_settings: fmt_set,
search_settings: search_set,
ty: ty,
};
debug!("ls search: {:?}", cmd);
Ok(cmd)
}
#[allow(trivial_regex)]
pub fn run_cmd<W: Write>(mut w: &mut W, cwd: &Path, cmd: &Cmd, project: &Project) -> Result<()> {
let mut dne: Vec<ArtNameRc> = Vec::new();
let artifacts = &project.artifacts;
let mut fmt_set = cmd.fmt_settings.clone();
if cmd.ty != OutType::List {
fmt_set.color = false;
}
let mut names: Vec<_> = if cmd.search_settings.use_regex || cmd.pattern.is_empty() {
let mut names: Vec<_> = artifacts.keys().cloned().collect();
names.sort();
names
} else {
let want_names = match ArtNames::from_str(&cmd.pattern) {
Ok(n) => n,
Err(e) => {
error!("{}", e);
return Err(ErrorKind::CmdError(format!("{}", e)).into());
}
};
let mut names = Vec::new();
for n in want_names {
if artifacts.contains_key(n.as_ref()) {
names.push(n);
} else {
dne.push(n)
}
}
dne.sort();
names.sort();
names
};
let names: Vec<_> = {
let pat = if cmd.search_settings.use_regex {
let p = RegexBuilder::new(&cmd.pattern)
.case_insensitive(true)
.build();
match p {
Ok(p) => p,
Err(e) => {
return Err(ErrorKind::CmdError(format!("Invalid pattern: {}", e)).into());
}
}
} else {
Regex::new("").unwrap()
};
names.drain(0..)
.filter(|n| {
let a = artifacts.get(n).unwrap(); ui::show_artifact(n, a, &pat, &cmd.search_settings)
})
.collect()
};
debug!("artifact names selected: {:?}", names);
if fmt_set.is_empty() {
fmt_set.parts = true;
}
let mut displayed = ArtNames::new();
match cmd.ty {
OutType::List => {
let mut tw = TabWriter::new(w);
if !names.is_empty() && !fmt_set.long {
display::write_table_header(&mut tw, &fmt_set);
}
for name in names {
let f =
ui::fmt_artifact(&name, artifacts, &fmt_set, fmt_set.recurse, &mut displayed);
f.write(&mut tw, cwd, artifacts, fmt_set.color, 0)?;
}
tw.flush()?; }
OutType::Json => {
let out_arts: Vec<_> = names.iter()
.map(|n| artifacts.get(n).unwrap().to_data(n))
.collect();
let value = serde_json::to_value(out_arts).unwrap();
w.write_all(serde_json::to_string(&value).unwrap().as_bytes())?;
}
}
if !dne.is_empty() {
return Err(ErrorKind::NameNotFound(format!("The following artifacts do not exist: {:?}",
dne))
.into());
}
Ok(())
}