mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Parse reports from config, with defaults
This commit is contained in:
parent
2928bab41c
commit
46c3b31208
6 changed files with 624 additions and 92 deletions
|
@ -1,6 +1,7 @@
|
|||
use super::args::{arg_matching, id_list, minus_tag, plus_tag, status_colon, TaskId};
|
||||
use super::ArgList;
|
||||
use crate::usage;
|
||||
use failure::{bail, Fallible};
|
||||
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
|
||||
use taskchampion::Status;
|
||||
|
||||
|
@ -32,15 +33,61 @@ pub(crate) enum Condition {
|
|||
IdList(Vec<TaskId>),
|
||||
}
|
||||
|
||||
impl Condition {
|
||||
fn parse(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
alt((
|
||||
Self::parse_id_list,
|
||||
Self::parse_plus_tag,
|
||||
Self::parse_minus_tag,
|
||||
Self::parse_status,
|
||||
))(input)
|
||||
}
|
||||
|
||||
/// Parse a single condition string
|
||||
pub(crate) fn parse_str(input: &str) -> Fallible<Condition> {
|
||||
let input = &[input];
|
||||
Ok(match Condition::parse(input) {
|
||||
Ok((&[], cond)) => cond,
|
||||
Ok(_) => unreachable!(), // input only has one element
|
||||
Err(nom::Err::Incomplete(_)) => unreachable!(),
|
||||
Err(nom::Err::Error(e)) => bail!("invalid filter condition: {:?}", e),
|
||||
Err(nom::Err::Failure(e)) => bail!("invalid filter condition: {:?}", e),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_id_list(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: Vec<TaskId>) -> Result<Condition, ()> {
|
||||
Ok(Condition::IdList(input))
|
||||
}
|
||||
map_res(arg_matching(id_list), to_condition)(input)
|
||||
}
|
||||
|
||||
fn parse_plus_tag(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: &str) -> Result<Condition, ()> {
|
||||
Ok(Condition::HasTag(input.to_owned()))
|
||||
}
|
||||
map_res(arg_matching(plus_tag), to_condition)(input)
|
||||
}
|
||||
|
||||
fn parse_minus_tag(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: &str) -> Result<Condition, ()> {
|
||||
Ok(Condition::NoTag(input.to_owned()))
|
||||
}
|
||||
map_res(arg_matching(minus_tag), to_condition)(input)
|
||||
}
|
||||
|
||||
fn parse_status(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: Status) -> Result<Condition, ()> {
|
||||
Ok(Condition::Status(input))
|
||||
}
|
||||
map_res(arg_matching(status_colon), to_condition)(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Filter> {
|
||||
fold_many0(
|
||||
alt((
|
||||
Self::parse_id_list,
|
||||
Self::parse_plus_tag,
|
||||
Self::parse_minus_tag,
|
||||
Self::parse_status,
|
||||
)),
|
||||
Condition::parse,
|
||||
Filter {
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -80,36 +127,6 @@ impl Filter {
|
|||
self
|
||||
}
|
||||
|
||||
// parsers
|
||||
|
||||
fn parse_id_list(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: Vec<TaskId>) -> Result<Condition, ()> {
|
||||
Ok(Condition::IdList(input))
|
||||
}
|
||||
map_res(arg_matching(id_list), to_condition)(input)
|
||||
}
|
||||
|
||||
fn parse_plus_tag(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: &str) -> Result<Condition, ()> {
|
||||
Ok(Condition::HasTag(input.to_owned()))
|
||||
}
|
||||
map_res(arg_matching(plus_tag), to_condition)(input)
|
||||
}
|
||||
|
||||
fn parse_minus_tag(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: &str) -> Result<Condition, ()> {
|
||||
Ok(Condition::NoTag(input.to_owned()))
|
||||
}
|
||||
map_res(arg_matching(minus_tag), to_condition)(input)
|
||||
}
|
||||
|
||||
fn parse_status(input: ArgList) -> IResult<ArgList, Condition> {
|
||||
fn to_condition(input: Status) -> Result<Condition, ()> {
|
||||
Ok(Condition::Status(input))
|
||||
}
|
||||
map_res(arg_matching(status_colon), to_condition)(input)
|
||||
}
|
||||
|
||||
// usage
|
||||
|
||||
pub(super) fn get_usage(u: &mut usage::Usage) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::argparse::Filter;
|
||||
use crate::invocation::display_report;
|
||||
use config::Config;
|
||||
use failure::Fallible;
|
||||
use taskchampion::Replica;
|
||||
use termcolor::WriteColor;
|
||||
|
@ -7,10 +8,11 @@ use termcolor::WriteColor;
|
|||
pub(crate) fn execute<W: WriteColor>(
|
||||
w: &mut W,
|
||||
replica: &mut Replica,
|
||||
settings: &Config,
|
||||
report_name: String,
|
||||
filter: Filter,
|
||||
) -> Fallible<()> {
|
||||
display_report(w, replica, report_name, filter)
|
||||
display_report(w, replica, settings, report_name, filter)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -29,11 +31,13 @@ mod test {
|
|||
// The function being tested is only one line long, so this is sort of an integration test
|
||||
// for display_report.
|
||||
|
||||
let settings = crate::settings::default_settings().unwrap();
|
||||
let report_name = "next".to_owned();
|
||||
let filter = Filter {
|
||||
..Default::default()
|
||||
};
|
||||
execute(&mut w, &mut replica, report_name, filter).unwrap();
|
||||
|
||||
execute(&mut w, &mut replica, &settings, report_name, filter).unwrap();
|
||||
assert!(w.into_string().contains("my task"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> {
|
|||
filter,
|
||||
},
|
||||
..
|
||||
} => return cmd::report::execute(&mut w, &mut replica, report_name, filter),
|
||||
} => return cmd::report::execute(&mut w, &mut replica, &settings, report_name, filter),
|
||||
|
||||
Command {
|
||||
subcommand: Subcommand::Info { filter, debug },
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use crate::argparse::{Condition, Filter};
|
||||
use crate::argparse::Filter;
|
||||
use crate::invocation::filtered_tasks;
|
||||
use crate::report::{Column, Property, Report, Sort, SortBy};
|
||||
use crate::report::{Column, Property, Report, SortBy};
|
||||
use crate::table;
|
||||
use failure::{bail, Fallible};
|
||||
use config::Config;
|
||||
use failure::{format_err, Fallible};
|
||||
use prettytable::{Row, Table};
|
||||
use std::cmp::Ordering;
|
||||
use taskchampion::{Replica, Status, Task, Uuid};
|
||||
use taskchampion::{Replica, Task, Uuid};
|
||||
use termcolor::WriteColor;
|
||||
|
||||
// pending #123, this is a non-fallible way of looking up a task's working set index
|
||||
|
@ -102,61 +103,23 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String
|
|||
}
|
||||
}
|
||||
|
||||
fn get_report(report_name: String, filter: Filter) -> Fallible<Report> {
|
||||
let columns = vec![
|
||||
Column {
|
||||
label: "Id".to_owned(),
|
||||
property: Property::Id,
|
||||
},
|
||||
Column {
|
||||
label: "Description".to_owned(),
|
||||
property: Property::Description,
|
||||
},
|
||||
Column {
|
||||
label: "Active".to_owned(),
|
||||
property: Property::Active,
|
||||
},
|
||||
Column {
|
||||
label: "Tags".to_owned(),
|
||||
property: Property::Tags,
|
||||
},
|
||||
];
|
||||
let sort = vec![Sort {
|
||||
ascending: false,
|
||||
sort_by: SortBy::Uuid,
|
||||
}];
|
||||
let mut report = match report_name.as_ref() {
|
||||
"list" => Report {
|
||||
columns,
|
||||
sort,
|
||||
filter: Default::default(),
|
||||
},
|
||||
"next" => Report {
|
||||
columns,
|
||||
sort,
|
||||
filter: Filter {
|
||||
conditions: vec![Condition::Status(Status::Pending)],
|
||||
},
|
||||
},
|
||||
_ => bail!("Unknown report {:?}", report_name),
|
||||
};
|
||||
|
||||
// intersect the report's filter with the user-supplied filter
|
||||
report.filter = report.filter.intersect(filter);
|
||||
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
pub(super) fn display_report<W: WriteColor>(
|
||||
w: &mut W,
|
||||
replica: &mut Replica,
|
||||
settings: &Config,
|
||||
report_name: String,
|
||||
filter: Filter,
|
||||
) -> Fallible<()> {
|
||||
let mut t = Table::new();
|
||||
let report = get_report(report_name, filter)?;
|
||||
let working_set = WorkingSet::new(replica)?;
|
||||
|
||||
// Get the report from settings
|
||||
let mut report = Report::from_config(settings.get(&format!("reports.{}", report_name))?)
|
||||
.map_err(|e| format_err!("report.{}{}", report_name, e))?;
|
||||
|
||||
// include any user-supplied filter conditions
|
||||
report.filter = report.filter.intersect(filter);
|
||||
|
||||
// Get the tasks from the filter
|
||||
let mut tasks: Vec<_> = filtered_tasks(replica, &report.filter)?.collect();
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! This module contains the data structures used to define reports.
|
||||
|
||||
use crate::argparse::Filter;
|
||||
use crate::argparse::{Condition, Filter};
|
||||
use failure::{bail, format_err, Fallible};
|
||||
|
||||
/// A report specifies a filter as well as a sort order and information about which
|
||||
/// task attributes to display
|
||||
|
@ -68,3 +69,510 @@ pub(crate) enum SortBy {
|
|||
/// The task's description
|
||||
Description,
|
||||
}
|
||||
|
||||
// Conversions from config::Value. Note that these cannot ergonomically use TryFrom/TryInto; see
|
||||
// https://github.com/mehcode/config-rs/issues/162
|
||||
|
||||
impl Report {
|
||||
/// Create a Report from a config value. This should be the `report.<report_name>` value.
|
||||
/// The error message begins with any additional path information, e.g., `.sort[1].sort_by:
|
||||
/// ..`.
|
||||
pub(crate) fn from_config(cfg: config::Value) -> Fallible<Report> {
|
||||
let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?;
|
||||
let sort = if let Some(sort_array) = map.remove("sort") {
|
||||
sort_array
|
||||
.into_array()
|
||||
.map_err(|e| format_err!(".sort: {}", e))?
|
||||
.drain(..)
|
||||
.enumerate()
|
||||
.map(|(i, v)| Sort::from_config(v).map_err(|e| format_err!(".sort[{}]{}", i, e)))
|
||||
.collect::<Fallible<Vec<_>>>()?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let columns = map
|
||||
.remove("columns")
|
||||
.ok_or_else(|| format_err!(": 'columns' property is required"))?
|
||||
.into_array()
|
||||
.map_err(|e| format_err!(".columns: {}", e))?
|
||||
.drain(..)
|
||||
.enumerate()
|
||||
.map(|(i, v)| Column::from_config(v).map_err(|e| format_err!(".columns[{}]{}", i, e)))
|
||||
.collect::<Fallible<Vec<_>>>()?;
|
||||
|
||||
let conditions = if let Some(conditions) = map.remove("filter") {
|
||||
conditions
|
||||
.into_array()
|
||||
.map_err(|e| format_err!(".filter: {}", e))?
|
||||
.drain(..)
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
v.into_str()
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|s| Condition::parse_str(&s))
|
||||
.map_err(|e| format_err!(".filter[{}]: {}", i, e))
|
||||
})
|
||||
.collect::<Fallible<Vec<_>>>()?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let filter = Filter { conditions };
|
||||
|
||||
if !map.is_empty() {
|
||||
bail!(": unknown properties");
|
||||
}
|
||||
|
||||
Ok(Report {
|
||||
columns,
|
||||
sort,
|
||||
filter,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Column {
|
||||
pub(crate) fn from_config(cfg: config::Value) -> Fallible<Column> {
|
||||
let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?;
|
||||
let label = map
|
||||
.remove("label")
|
||||
.ok_or_else(|| format_err!(": 'label' property is required"))?
|
||||
.into_str()
|
||||
.map_err(|e| format_err!(".label: {}", e))?;
|
||||
let property: config::Value = map
|
||||
.remove("property")
|
||||
.ok_or_else(|| format_err!(": 'property' property is required"))?;
|
||||
let property =
|
||||
Property::from_config(property).map_err(|e| format_err!(".property{}", e))?;
|
||||
|
||||
if !map.is_empty() {
|
||||
bail!(": unknown properties");
|
||||
}
|
||||
|
||||
Ok(Column { label, property })
|
||||
}
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub(crate) fn from_config(cfg: config::Value) -> Fallible<Property> {
|
||||
let s = cfg.into_str().map_err(|e| format_err!(": {}", e))?;
|
||||
Ok(match s.as_ref() {
|
||||
"id" => Property::Id,
|
||||
"uuid" => Property::Uuid,
|
||||
"active" => Property::Active,
|
||||
"description" => Property::Description,
|
||||
"tags" => Property::Tags,
|
||||
_ => bail!(": unknown property {}", s),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Sort {
|
||||
pub(crate) fn from_config(cfg: config::Value) -> Fallible<Sort> {
|
||||
let mut map = cfg.into_table().map_err(|e| format_err!(": {}", e))?;
|
||||
let ascending = match map.remove("ascending") {
|
||||
Some(v) => v
|
||||
.into_bool()
|
||||
.map_err(|e| format_err!(".ascending: {}", e))?,
|
||||
None => true, // default
|
||||
};
|
||||
let sort_by: config::Value = map
|
||||
.remove("sort_by")
|
||||
.ok_or_else(|| format_err!(": 'sort_by' property is required"))?;
|
||||
let sort_by = SortBy::from_config(sort_by).map_err(|e| format_err!(".sort_by{}", e))?;
|
||||
|
||||
if !map.is_empty() {
|
||||
bail!(": unknown properties");
|
||||
}
|
||||
|
||||
Ok(Sort { ascending, sort_by })
|
||||
}
|
||||
}
|
||||
|
||||
impl SortBy {
|
||||
pub(crate) fn from_config(cfg: config::Value) -> Fallible<SortBy> {
|
||||
let s = cfg.into_str().map_err(|e| format_err!(": {}", e))?;
|
||||
Ok(match s.as_ref() {
|
||||
"id" => SortBy::Id,
|
||||
"uuid" => SortBy::Uuid,
|
||||
"description" => SortBy::Description,
|
||||
_ => bail!(": unknown sort_by {}", s),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use config::{Config, File, FileFormat, FileSourceString};
|
||||
use taskchampion::Status;
|
||||
use textwrap::{dedent, indent};
|
||||
|
||||
fn config_from(cfg: &str) -> config::Value {
|
||||
// wrap this in a "table" so that we can get any type of value at the top level.
|
||||
let yaml = format!("val:\n{}", indent(&dedent(&cfg), " "));
|
||||
let mut settings = Config::new();
|
||||
let cfg_file: File<FileSourceString> = File::from_str(&yaml, FileFormat::Yaml);
|
||||
settings.merge(cfg_file).unwrap();
|
||||
settings.cache.into_table().unwrap().remove("val").unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_ok() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort: []
|
||||
columns: []
|
||||
filter:
|
||||
- status:pending",
|
||||
);
|
||||
let report = Report::from_config(val).unwrap();
|
||||
assert_eq!(
|
||||
report.filter,
|
||||
Filter {
|
||||
conditions: vec![Condition::Status(Status::Pending),],
|
||||
}
|
||||
);
|
||||
assert_eq!(report.columns, vec![]);
|
||||
assert_eq!(report.sort, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_no_sort() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
columns: []",
|
||||
);
|
||||
let report = Report::from_config(val).unwrap();
|
||||
assert_eq!(report.sort, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_sort_not_array() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort: true
|
||||
columns: []",
|
||||
);
|
||||
assert_eq!(
|
||||
&Report::from_config(val).unwrap_err().to_string(),
|
||||
".sort: invalid type: boolean `true`, expected an array"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_sort_error() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort:
|
||||
- sort_by: id
|
||||
- true
|
||||
columns: []",
|
||||
);
|
||||
assert!(&Report::from_config(val)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.starts_with(".sort[1]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_unknown_prop() {
|
||||
let val = config_from(
|
||||
"
|
||||
columns: []
|
||||
filter: []
|
||||
sort: []
|
||||
nosuch: true
|
||||
",
|
||||
);
|
||||
assert_eq!(
|
||||
&Report::from_config(val).unwrap_err().to_string(),
|
||||
": unknown properties"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_no_columns() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort: []",
|
||||
);
|
||||
assert_eq!(
|
||||
&Report::from_config(val).unwrap_err().to_string(),
|
||||
": \'columns\' property is required"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_columns_not_array() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort: []
|
||||
columns: true",
|
||||
);
|
||||
assert_eq!(
|
||||
&Report::from_config(val).unwrap_err().to_string(),
|
||||
".columns: invalid type: boolean `true`, expected an array"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_column_error() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort: []
|
||||
columns:
|
||||
- label: ID
|
||||
property: id
|
||||
- true",
|
||||
);
|
||||
assert!(&Report::from_config(val)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.starts_with(".columns[1]:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_filter_not_array() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort: []
|
||||
columns: []
|
||||
filter: true",
|
||||
);
|
||||
assert_eq!(
|
||||
&Report::from_config(val).unwrap_err().to_string(),
|
||||
".filter: invalid type: boolean `true`, expected an array"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_report_filter_error() {
|
||||
let val = config_from(
|
||||
"
|
||||
filter: []
|
||||
sort: []
|
||||
columns: []
|
||||
filter:
|
||||
- nosuchfilter",
|
||||
);
|
||||
assert!(&Report::from_config(val)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.starts_with(".filter[0]: invalid filter condition:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column() {
|
||||
let val = config_from(
|
||||
"
|
||||
label: ID
|
||||
property: id",
|
||||
);
|
||||
let column = Column::from_config(val).unwrap();
|
||||
assert_eq!(
|
||||
column,
|
||||
Column {
|
||||
label: "ID".to_owned(),
|
||||
property: Property::Id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column_unknown_prop() {
|
||||
let val = config_from(
|
||||
"
|
||||
label: ID
|
||||
property: id
|
||||
nosuch: foo",
|
||||
);
|
||||
assert_eq!(
|
||||
&Column::from_config(val).unwrap_err().to_string(),
|
||||
": unknown properties"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column_no_label() {
|
||||
let val = config_from(
|
||||
"
|
||||
property: id",
|
||||
);
|
||||
assert_eq!(
|
||||
&Column::from_config(val).unwrap_err().to_string(),
|
||||
": 'label' property is required"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column_invalid_label() {
|
||||
let val = config_from(
|
||||
"
|
||||
label: []
|
||||
property: id",
|
||||
);
|
||||
assert_eq!(
|
||||
&Column::from_config(val).unwrap_err().to_string(),
|
||||
".label: invalid type: sequence, expected a string"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column_no_property() {
|
||||
let val = config_from(
|
||||
"
|
||||
label: ID",
|
||||
);
|
||||
assert_eq!(
|
||||
&Column::from_config(val).unwrap_err().to_string(),
|
||||
": 'property' property is required"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_column_invalid_property() {
|
||||
let val = config_from(
|
||||
"
|
||||
label: ID
|
||||
property: []",
|
||||
);
|
||||
assert_eq!(
|
||||
&Column::from_config(val).unwrap_err().to_string(),
|
||||
".property: invalid type: sequence, expected a string"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_property() {
|
||||
let val = config_from("uuid");
|
||||
let prop = Property::from_config(val).unwrap();
|
||||
assert_eq!(prop, Property::Uuid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_property_invalid_type() {
|
||||
let val = config_from("{}");
|
||||
assert_eq!(
|
||||
&Property::from_config(val).unwrap_err().to_string(),
|
||||
": invalid type: map, expected a string"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort() {
|
||||
let val = config_from(
|
||||
"
|
||||
ascending: false
|
||||
sort_by: id",
|
||||
);
|
||||
let sort = Sort::from_config(val).unwrap();
|
||||
assert_eq!(
|
||||
sort,
|
||||
Sort {
|
||||
ascending: false,
|
||||
sort_by: SortBy::Id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_no_ascending() {
|
||||
let val = config_from(
|
||||
"
|
||||
sort_by: id",
|
||||
);
|
||||
let sort = Sort::from_config(val).unwrap();
|
||||
assert_eq!(
|
||||
sort,
|
||||
Sort {
|
||||
ascending: true,
|
||||
sort_by: SortBy::Id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_unknown_prop() {
|
||||
let val = config_from(
|
||||
"
|
||||
sort_by: id
|
||||
nosuch: foo",
|
||||
);
|
||||
assert_eq!(
|
||||
&Sort::from_config(val).unwrap_err().to_string(),
|
||||
": unknown properties"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_no_sort_by() {
|
||||
let val = config_from(
|
||||
"
|
||||
ascending: true",
|
||||
);
|
||||
assert_eq!(
|
||||
&Sort::from_config(val).unwrap_err().to_string(),
|
||||
": 'sort_by' property is required"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_invalid_ascending() {
|
||||
let val = config_from(
|
||||
"
|
||||
sort_by: id
|
||||
ascending: {}",
|
||||
);
|
||||
assert_eq!(
|
||||
&Sort::from_config(val).unwrap_err().to_string(),
|
||||
".ascending: invalid type: map, expected a boolean"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_invalid_sort_by() {
|
||||
let val = config_from(
|
||||
"
|
||||
sort_by: {}",
|
||||
);
|
||||
assert_eq!(
|
||||
&Sort::from_config(val).unwrap_err().to_string(),
|
||||
".sort_by: invalid type: map, expected a string"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_by() {
|
||||
let val = config_from("uuid");
|
||||
let prop = SortBy::from_config(val).unwrap();
|
||||
assert_eq!(prop, SortBy::Uuid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_by_unknown() {
|
||||
let val = config_from("nosuch");
|
||||
assert_eq!(
|
||||
&SortBy::from_config(val).unwrap_err().to_string(),
|
||||
": unknown sort_by nosuch"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_by_invalid_type() {
|
||||
let val = config_from("{}");
|
||||
assert_eq!(
|
||||
&SortBy::from_config(val).unwrap_err().to_string(),
|
||||
": invalid type: map, expected a string"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,40 @@
|
|||
use config::{Config, Environment, File, FileSourceFile};
|
||||
use config::{Config, Environment, File, FileFormat, FileSourceFile, FileSourceString};
|
||||
use failure::Fallible;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub(crate) fn read_settings() -> Fallible<Config> {
|
||||
const DEFAULTS: &str = r#"
|
||||
reports:
|
||||
list:
|
||||
sort:
|
||||
- sort_by: uuid
|
||||
columns:
|
||||
- label: Id
|
||||
property: id
|
||||
- label: Description
|
||||
property: description
|
||||
- label: Active
|
||||
property: active
|
||||
- label: Tags
|
||||
property: tags
|
||||
next:
|
||||
filter:
|
||||
- "status:pending"
|
||||
sort:
|
||||
- sort_by: uuid
|
||||
columns:
|
||||
- label: Id
|
||||
property: id
|
||||
- label: Description
|
||||
property: description
|
||||
- label: Active
|
||||
property: active
|
||||
- label: Tags
|
||||
property: tags
|
||||
"#;
|
||||
|
||||
/// Get the default settings for this application
|
||||
pub(crate) fn default_settings() -> Fallible<Config> {
|
||||
let mut settings = Config::default();
|
||||
|
||||
// set up defaults
|
||||
|
@ -25,6 +56,15 @@ pub(crate) fn read_settings() -> Fallible<Config> {
|
|||
)?;
|
||||
}
|
||||
|
||||
let defaults: File<FileSourceString> = File::from_str(DEFAULTS, FileFormat::Yaml);
|
||||
settings.merge(defaults)?;
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
pub(crate) fn read_settings() -> Fallible<Config> {
|
||||
let mut settings = default_settings()?;
|
||||
|
||||
// load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion
|
||||
if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") {
|
||||
log::debug!("Loading configuration from {:?}", config_file);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue