use static strings for usage

This commit is contained in:
Dustin J. Mitchell 2020-12-24 21:07:27 +00:00
parent 7594144a2d
commit 75aaf8d4ab
7 changed files with 138 additions and 141 deletions

11
Cargo.lock generated
View file

@ -2340,6 +2340,16 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "terminal_size"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"
@ -2355,6 +2365,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [ dependencies = [
"terminal_size",
"unicode-width", "unicode-width",
] ]

View file

@ -11,7 +11,7 @@ failure = "^0.1.8"
log = "^0.4.11" log = "^0.4.11"
nom = "*" nom = "*"
prettytable-rs = "^0.8.0" prettytable-rs = "^0.8.0"
textwrap = "0.12.1" textwrap = { version="0.12.1", features=["terminal_size"] }
termcolor = "1.1.2" termcolor = "1.1.2"
atty = "0.2.14" atty = "0.2.14"

View file

@ -2,7 +2,6 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId};
use super::ArgList; use super::ArgList;
use crate::usage; use crate::usage;
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
use textwrap::dedent;
/// A filter represents a selection of a particular set of tasks. /// A filter represents a selection of a particular set of tasks.
/// ///
@ -115,31 +114,25 @@ impl Filter {
pub(super) fn get_usage(u: &mut usage::Usage) { pub(super) fn get_usage(u: &mut usage::Usage) {
u.filters.push(usage::Filter { u.filters.push(usage::Filter {
syntax: "TASKID[,TASKID,..]".to_owned(), syntax: "TASKID[,TASKID,..]",
summary: "Specific tasks".to_owned(), summary: "Specific tasks",
description: dedent( description: "
"
Select only specific tasks. Multiple tasks can be specified either separated by Select only specific tasks. Multiple tasks can be specified either separated by
commas or as separate arguments. Each task may be specfied by its working-set commas or as separate arguments. Each task may be specfied by its working-set
index (a small number) or by its UUID. Prefixes of UUIDs broken at hyphens are index (a small number) or by its UUID. Partial UUIDs, broken on a hyphen, are
also supported, such as `b5664ef8-423d` or `b5664ef8`.", also supported, such as `b5664ef8-423d` or `b5664ef8`.",
),
}); });
u.filters.push(usage::Filter { u.filters.push(usage::Filter {
syntax: "+TAG".to_owned(), syntax: "+TAG",
summary: "Tagged tasks".to_owned(), summary: "Tagged tasks",
description: dedent( description: "
"
Select tasks with the given tag.", Select tasks with the given tag.",
),
}); });
u.filters.push(usage::Filter { u.filters.push(usage::Filter {
syntax: "-TAG".to_owned(), syntax: "-TAG",
summary: "Un-tagged tasks".to_owned(), summary: "Un-tagged tasks",
description: dedent( description: "
"
Select tasks that do not have the given tag.", Select tasks that do not have the given tag.",
),
}); });
} }
} }

View file

@ -4,7 +4,6 @@ use crate::usage;
use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult};
use std::collections::HashSet; use std::collections::HashSet;
use taskchampion::Status; use taskchampion::Status;
use textwrap::dedent;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum DescriptionMod { pub enum DescriptionMod {
@ -112,29 +111,23 @@ impl Modification {
pub(super) fn get_usage(u: &mut usage::Usage) { pub(super) fn get_usage(u: &mut usage::Usage) {
u.modifications.push(usage::Modification { u.modifications.push(usage::Modification {
syntax: "DESCRIPTION".to_owned(), syntax: "DESCRIPTION",
summary: "Set description".to_owned(), summary: "Set description",
description: dedent( description: "
"
Set the task description. Multiple arguments are combined into a single Set the task description. Multiple arguments are combined into a single
space-separated description.", space-separated description.",
),
}); });
u.modifications.push(usage::Modification { u.modifications.push(usage::Modification {
syntax: "+TAG".to_owned(), syntax: "+TAG",
summary: "Tag task".to_owned(), summary: "Tag task",
description: dedent( description: "
"
Add the given tag to the task.", Add the given tag to the task.",
),
}); });
u.modifications.push(usage::Modification { u.modifications.push(usage::Modification {
syntax: "-TAG".to_owned(), syntax: "-TAG",
summary: "Un-tag task".to_owned(), summary: "Un-tag task",
description: dedent( description: "
"
Remove the given tag from the task.", Remove the given tag from the task.",
),
}); });
} }
} }

View file

@ -3,7 +3,6 @@ use super::{ArgList, DescriptionMod, Filter, Modification, Report};
use crate::usage; use crate::usage;
use nom::{branch::alt, combinator::*, sequence::*, IResult}; use nom::{branch::alt, combinator::*, sequence::*, IResult};
use taskchampion::Status; use taskchampion::Status;
use textwrap::dedent;
// IMPLEMENTATION NOTE: // IMPLEMENTATION NOTE:
// //
@ -97,10 +96,10 @@ impl Version {
fn get_usage(u: &mut usage::Usage) { fn get_usage(u: &mut usage::Usage) {
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "version".to_owned(), name: "version",
syntax: "version".to_owned(), syntax: "version",
summary: "Show the TaskChampion version".to_owned(), summary: "Show the TaskChampion version",
description: "Show the version of the TaskChampion binary".to_owned(), description: "Show the version of the TaskChampion binary",
}); });
} }
} }
@ -144,14 +143,12 @@ impl Add {
fn get_usage(u: &mut usage::Usage) { fn get_usage(u: &mut usage::Usage) {
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "add".to_owned(), name: "add",
syntax: "add [modification]".to_owned(), syntax: "add [modification]",
summary: "Add a new task".to_owned(), summary: "Add a new task",
description: dedent( description: "
"
Add a new, pending task to the list of tasks. The modification must include a Add a new, pending task to the list of tasks. The modification must include a
description.", description.",
),
}); });
} }
} }
@ -205,60 +202,49 @@ impl Modify {
fn get_usage(u: &mut usage::Usage) { fn get_usage(u: &mut usage::Usage) {
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "modify".to_owned(), name: "modify",
syntax: "[filter] modify [modification]".to_owned(), syntax: "[filter] modify [modification]",
summary: "Modify tasks".to_owned(), summary: "Modify tasks",
description: dedent( description: "
"
Modify all tasks matching the filter.", Modify all tasks matching the filter.",
),
}); });
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "prepend".to_owned(), name: "prepend",
syntax: "[filter] prepend [modification]".to_owned(), syntax: "[filter] prepend [modification]",
summary: "Prepend task description".to_owned(), summary: "Prepend task description",
description: dedent( description: "
"
Modify all tasks matching the filter by inserting the given description before each Modify all tasks matching the filter by inserting the given description before each
task's description.", task's description.",
),
}); });
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "append".to_owned(), name: "append",
syntax: "[filter] append [modification]".to_owned(), syntax: "[filter] append [modification]",
summary: "Append task description".to_owned(), summary: "Append task description",
description: dedent( description: "
"
Modify all tasks matching the filter by adding the given description to the end Modify all tasks matching the filter by adding the given description to the end
of each task's description.", of each task's description.",
),
}); });
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "start".to_owned(), name: "start",
syntax: "[filter] start [modification]".to_owned(), syntax: "[filter] start [modification]",
summary: "Start tasks".to_owned(), summary: "Start tasks",
description: dedent( description: "
" Start all tasks matching the filter, additionally applying any given modifications."
Start all tasks matching the filter, additionally applying any given modifications."),
}); });
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "stop".to_owned(), name: "stop",
syntax: "[filter] stop [modification]".to_owned(), syntax: "[filter] stop [modification]",
summary: "Stop tasks".to_owned(), summary: "Stop tasks",
description: dedent( description: "
"
Stop all tasks matching the filter, additionally applying any given modifications.", Stop all tasks matching the filter, additionally applying any given modifications.",
),
}); });
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "done".to_owned(), name: "done",
syntax: "[filter] done [modification]".to_owned(), syntax: "[filter] done [modification]",
summary: "Mark tasks as completed".to_owned(), summary: "Mark tasks as completed",
description: dedent( description: "
"
Mark all tasks matching the filter as completed, additionally applying any given Mark all tasks matching the filter as completed, additionally applying any given
modifications.", modifications.",
),
}); });
} }
} }
@ -278,13 +264,11 @@ impl List {
fn get_usage(u: &mut usage::Usage) { fn get_usage(u: &mut usage::Usage) {
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "list".to_owned(), name: "list",
syntax: "[filter] list".to_owned(), syntax: "[filter] list",
summary: "List tasks".to_owned(), summary: "List tasks",
description: dedent( description: "
"
Show a list of the tasks matching the filter", Show a list of the tasks matching the filter",
),
}); });
} }
} }
@ -314,22 +298,16 @@ impl Info {
fn get_usage(u: &mut usage::Usage) { fn get_usage(u: &mut usage::Usage) {
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "info".to_owned(), name: "info",
syntax: "[filter] info".to_owned(), syntax: "[filter] info",
summary: "Show tasks".to_owned(), summary: "Show tasks",
description: dedent( description: " Show information about all tasks matching the fiter.",
"
Show information about all tasks matching the fiter.",
),
}); });
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "debug".to_owned(), name: "debug",
syntax: "[filter] debug".to_owned(), syntax: "[filter] debug",
summary: "Show task debug details".to_owned(), summary: "Show task debug details",
description: dedent( description: " Show all key/value properties of the tasks matching the fiter.",
"
Show all key/value properties of the tasks matching the fiter.",
),
}); });
} }
} }
@ -346,14 +324,12 @@ impl Gc {
fn get_usage(u: &mut usage::Usage) { fn get_usage(u: &mut usage::Usage) {
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "gc".to_owned(), name: "gc",
syntax: "gc".to_owned(), syntax: "gc",
summary: "Perform 'garbage collection'".to_owned(), summary: "Perform 'garbage collection'",
description: dedent( description: "
"
Perform 'garbage collection'. This refreshes the list of pending tasks Perform 'garbage collection'. This refreshes the list of pending tasks
and their short id's.", and their short id's.",
),
}); });
} }
} }
@ -370,16 +346,14 @@ impl Sync {
fn get_usage(u: &mut usage::Usage) { fn get_usage(u: &mut usage::Usage) {
u.subcommands.push(usage::Subcommand { u.subcommands.push(usage::Subcommand {
name: "sync".to_owned(), name: "sync",
syntax: "sync".to_owned(), syntax: "sync",
summary: "Synchronize this replica".to_owned(), summary: "Synchronize this replica",
description: dedent( description: "
"
Synchronize this replica locally or against a remote server, as configured. Synchronize this replica locally or against a remote server, as configured.
Synchronization is a critical part of maintaining the task database, and should Synchronization is a critical part of maintaining the task database, and should
be done regularly, even if only locally. It is typically run in a crontask.", be done regularly, even if only locally. It is typically run in a crontask.",
),
}) })
} }
} }

View file

@ -8,7 +8,7 @@ pub(crate) fn execute<W: WriteColor>(
summary: bool, summary: bool,
) -> Fallible<()> { ) -> Fallible<()> {
let usage = Usage::new(); let usage = Usage::new();
usage.write_help(w, command_name, summary)?; usage.write_help(w, command_name.as_ref(), summary)?;
Ok(()) Ok(())
} }

View file

@ -3,7 +3,6 @@
use crate::argparse; use crate::argparse;
use std::io::{Result, Write}; use std::io::{Result, Write};
use textwrap::indent;
/// A top-level structure containing usage/help information for the entire CLI. /// A top-level structure containing usage/help information for the entire CLI.
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -32,7 +31,7 @@ impl Usage {
pub(crate) fn write_help<W: Write>( pub(crate) fn write_help<W: Write>(
&self, &self,
mut w: W, mut w: W,
command_name: String, command_name: &str,
summary: bool, summary: bool,
) -> Result<()> { ) -> Result<()> {
write!( write!(
@ -45,15 +44,31 @@ impl Usage {
for subcommand in self.subcommands.iter() { for subcommand in self.subcommands.iter() {
subcommand.write_help(&mut w, summary)?; subcommand.write_help(&mut w, summary)?;
} }
write!(w, "Filter Expressions:\n\n")?; writeln!(w, "Filter Expressions:\n")?;
write!(w, "Where [filter] appears above, zero or more of the following arguments can be used to limit\n")?; writeln!(
write!(w, "the tasks concerned.\n\n")?; w,
"{}",
indented(
"
Where [filter] appears above, zero or more of the following arguments can be used
to limit the tasks addressed by the subcommand.",
""
)
)?;
for filter in self.filters.iter() { for filter in self.filters.iter() {
filter.write_help(&mut w, summary)?; filter.write_help(&mut w, summary)?;
} }
write!(w, "Modifications:\n\n")?; writeln!(w, "Modifications:\n")?;
write!(w, "Where [modification] appears above, zero or more of the following arguments can be used\n")?; writeln!(
write!(w, "to modify the selected tasks.\n\n")?; w,
"{}",
indented(
"
Where [modification] appears above, zero or more of the following arguments can be
used to modify the selected tasks.",
""
)
)?;
for modification in self.modifications.iter() { for modification in self.modifications.iter() {
modification.write_help(&mut w, summary)?; modification.write_help(&mut w, summary)?;
} }
@ -64,21 +79,32 @@ impl Usage {
} }
} }
/// wrap an indented string
fn indented(string: &str, indent: &str) -> String {
let termwidth = textwrap::termwidth();
let words: Vec<&str> = string.split_whitespace().collect();
let string = words.join(" ");
textwrap::indent(
textwrap::fill(string.trim(), termwidth - indent.len()).as_ref(),
indent,
)
}
/// Usage documentation for a subcommand /// Usage documentation for a subcommand
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Subcommand { pub(crate) struct Subcommand {
/// Name of the subcommand /// Name of the subcommand
pub(crate) name: String, pub(crate) name: &'static str,
/// Syntax summary, without command_name /// Syntax summary, without command_name
pub(crate) syntax: String, pub(crate) syntax: &'static str,
/// One-line description of the subcommand. Use an initial capital and no trailing period. /// One-line description of the subcommand. Use an initial capital and no trailing period.
pub(crate) summary: String, pub(crate) summary: &'static str,
/// Multi-line description of the subcommand. It's OK for this to duplicate summary, as the /// Multi-line description of the subcommand. It's OK for this to duplicate summary, as the
/// two are not displayed together. /// two are not displayed together.
pub(crate) description: String, pub(crate) description: &'static str,
} }
impl Subcommand { impl Subcommand {
@ -86,11 +112,11 @@ impl Subcommand {
if summary { if summary {
writeln!(w, " task {} - {}", self.name, self.summary)?; writeln!(w, " task {} - {}", self.name, self.summary)?;
} else { } else {
write!( writeln!(
w, w,
" task {}\n{}\n", " task {}\n{}",
self.syntax, self.syntax,
indent(self.description.trim(), " ") indented(self.description, " ")
)?; )?;
} }
Ok(()) Ok(())
@ -101,26 +127,26 @@ impl Subcommand {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Filter { pub(crate) struct Filter {
/// Syntax summary /// Syntax summary
pub(crate) syntax: String, pub(crate) syntax: &'static str,
/// One-line description of the filter. Use all-caps words for placeholders. /// One-line description of the filter. Use all-caps words for placeholders.
pub(crate) summary: String, pub(crate) summary: &'static str,
/// Multi-line description of the filter. It's OK for this to duplicate summary, as the /// Multi-line description of the filter. It's OK for this to duplicate summary, as the
/// two are not displayed together. /// two are not displayed together.
pub(crate) description: String, pub(crate) description: &'static str,
} }
impl Filter { impl Filter {
fn write_help<W: Write>(&self, mut w: W, summary: bool) -> Result<()> { fn write_help<W: Write>(&self, mut w: W, summary: bool) -> Result<()> {
if summary { if summary {
write!(w, " {} - {}\n", self.syntax, self.summary)?; writeln!(w, " {} - {}", self.syntax, self.summary)?;
} else { } else {
write!( write!(
w, w,
" {}\n{}\n", " {}\n{}\n",
self.syntax, self.syntax,
indent(self.description.trim(), " ") indented(self.description, " ")
)?; )?;
} }
Ok(()) Ok(())
@ -131,26 +157,26 @@ impl Filter {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Modification { pub(crate) struct Modification {
/// Syntax summary /// Syntax summary
pub(crate) syntax: String, pub(crate) syntax: &'static str,
/// One-line description of the modification. Use all-caps words for placeholders. /// One-line description of the modification. Use all-caps words for placeholders.
pub(crate) summary: String, pub(crate) summary: &'static str,
/// Multi-line description of the modification. It's OK for this to duplicate summary, as the /// Multi-line description of the modification. It's OK for this to duplicate summary, as the
/// two are not displayed together. /// two are not displayed together.
pub(crate) description: String, pub(crate) description: &'static str,
} }
impl Modification { impl Modification {
fn write_help<W: Write>(&self, mut w: W, summary: bool) -> Result<()> { fn write_help<W: Write>(&self, mut w: W, summary: bool) -> Result<()> {
if summary { if summary {
write!(w, " {} - {}\n", self.syntax, self.summary)?; writeln!(w, " {} - {}", self.syntax, self.summary)?;
} else { } else {
write!( writeln!(
w, w,
" {}\n{}\n", " {}\n{}",
self.syntax, self.syntax,
indent(self.description.trim(), " ") indented(self.description, " ")
)?; )?;
} }
Ok(()) Ok(())