From 75aaf8d4ab07ae1bfe569ef6af9ee3d4301d2886 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 24 Dec 2020 21:07:27 +0000 Subject: [PATCH] use static strings for usage --- Cargo.lock | 11 +++ cli/Cargo.toml | 2 +- cli/src/argparse/filter.rs | 27 +++---- cli/src/argparse/modification.rs | 25 +++--- cli/src/argparse/subcommand.rs | 132 +++++++++++++------------------ cli/src/invocation/cmd/help.rs | 2 +- cli/src/usage.rs | 80 ++++++++++++------- 7 files changed, 138 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b3aeae18..e04f55e23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2340,6 +2340,16 @@ dependencies = [ "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]] name = "textwrap" version = "0.11.0" @@ -2355,6 +2365,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" dependencies = [ + "terminal_size", "unicode-width", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9daeb1653..5b00532b5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,7 +11,7 @@ failure = "^0.1.8" log = "^0.4.11" nom = "*" prettytable-rs = "^0.8.0" -textwrap = "0.12.1" +textwrap = { version="0.12.1", features=["terminal_size"] } termcolor = "1.1.2" atty = "0.2.14" diff --git a/cli/src/argparse/filter.rs b/cli/src/argparse/filter.rs index 66845e54b..07129a75b 100644 --- a/cli/src/argparse/filter.rs +++ b/cli/src/argparse/filter.rs @@ -2,7 +2,6 @@ use super::args::{arg_matching, id_list, minus_tag, plus_tag, TaskId}; use super::ArgList; use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; -use textwrap::dedent; /// 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) { u.filters.push(usage::Filter { - syntax: "TASKID[,TASKID,..]".to_owned(), - summary: "Specific tasks".to_owned(), - description: dedent( - " + syntax: "TASKID[,TASKID,..]", + summary: "Specific tasks", + description: " 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 - 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`.", - ), }); u.filters.push(usage::Filter { - syntax: "+TAG".to_owned(), - summary: "Tagged tasks".to_owned(), - description: dedent( - " + syntax: "+TAG", + summary: "Tagged tasks", + description: " Select tasks with the given tag.", - ), }); u.filters.push(usage::Filter { - syntax: "-TAG".to_owned(), - summary: "Un-tagged tasks".to_owned(), - description: dedent( - " + syntax: "-TAG", + summary: "Un-tagged tasks", + description: " Select tasks that do not have the given tag.", - ), }); } } diff --git a/cli/src/argparse/modification.rs b/cli/src/argparse/modification.rs index 1c46856a2..fbcc97370 100644 --- a/cli/src/argparse/modification.rs +++ b/cli/src/argparse/modification.rs @@ -4,7 +4,6 @@ use crate::usage; use nom::{branch::alt, combinator::*, multi::fold_many0, IResult}; use std::collections::HashSet; use taskchampion::Status; -use textwrap::dedent; #[derive(Debug, PartialEq, Clone)] pub enum DescriptionMod { @@ -112,29 +111,23 @@ impl Modification { pub(super) fn get_usage(u: &mut usage::Usage) { u.modifications.push(usage::Modification { - syntax: "DESCRIPTION".to_owned(), - summary: "Set description".to_owned(), - description: dedent( - " + syntax: "DESCRIPTION", + summary: "Set description", + description: " Set the task description. Multiple arguments are combined into a single space-separated description.", - ), }); u.modifications.push(usage::Modification { - syntax: "+TAG".to_owned(), - summary: "Tag task".to_owned(), - description: dedent( - " + syntax: "+TAG", + summary: "Tag task", + description: " Add the given tag to the task.", - ), }); u.modifications.push(usage::Modification { - syntax: "-TAG".to_owned(), - summary: "Un-tag task".to_owned(), - description: dedent( - " + syntax: "-TAG", + summary: "Un-tag task", + description: " Remove the given tag from the task.", - ), }); } } diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 6db98105e..9b2a0712e 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -3,7 +3,6 @@ use super::{ArgList, DescriptionMod, Filter, Modification, Report}; use crate::usage; use nom::{branch::alt, combinator::*, sequence::*, IResult}; use taskchampion::Status; -use textwrap::dedent; // IMPLEMENTATION NOTE: // @@ -97,10 +96,10 @@ impl Version { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "version".to_owned(), - syntax: "version".to_owned(), - summary: "Show the TaskChampion version".to_owned(), - description: "Show the version of the TaskChampion binary".to_owned(), + name: "version", + syntax: "version", + summary: "Show the TaskChampion version", + description: "Show the version of the TaskChampion binary", }); } } @@ -144,14 +143,12 @@ impl Add { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "add".to_owned(), - syntax: "add [modification]".to_owned(), - summary: "Add a new task".to_owned(), - description: dedent( - " + name: "add", + syntax: "add [modification]", + summary: "Add a new task", + description: " Add a new, pending task to the list of tasks. The modification must include a description.", - ), }); } } @@ -205,60 +202,49 @@ impl Modify { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "modify".to_owned(), - syntax: "[filter] modify [modification]".to_owned(), - summary: "Modify tasks".to_owned(), - description: dedent( - " + name: "modify", + syntax: "[filter] modify [modification]", + summary: "Modify tasks", + description: " Modify all tasks matching the filter.", - ), }); u.subcommands.push(usage::Subcommand { - name: "prepend".to_owned(), - syntax: "[filter] prepend [modification]".to_owned(), - summary: "Prepend task description".to_owned(), - description: dedent( - " + name: "prepend", + syntax: "[filter] prepend [modification]", + summary: "Prepend task description", + description: " Modify all tasks matching the filter by inserting the given description before each task's description.", - ), }); u.subcommands.push(usage::Subcommand { - name: "append".to_owned(), - syntax: "[filter] append [modification]".to_owned(), - summary: "Append task description".to_owned(), - description: dedent( - " + name: "append", + syntax: "[filter] append [modification]", + summary: "Append task description", + description: " Modify all tasks matching the filter by adding the given description to the end of each task's description.", - ), }); u.subcommands.push(usage::Subcommand { - name: "start".to_owned(), - syntax: "[filter] start [modification]".to_owned(), - summary: "Start tasks".to_owned(), - description: dedent( - " - Start all tasks matching the filter, additionally applying any given modifications."), + name: "start", + syntax: "[filter] start [modification]", + summary: "Start tasks", + description: " + Start all tasks matching the filter, additionally applying any given modifications." }); u.subcommands.push(usage::Subcommand { - name: "stop".to_owned(), - syntax: "[filter] stop [modification]".to_owned(), - summary: "Stop tasks".to_owned(), - description: dedent( - " + name: "stop", + syntax: "[filter] stop [modification]", + summary: "Stop tasks", + description: " Stop all tasks matching the filter, additionally applying any given modifications.", - ), }); u.subcommands.push(usage::Subcommand { - name: "done".to_owned(), - syntax: "[filter] done [modification]".to_owned(), - summary: "Mark tasks as completed".to_owned(), - description: dedent( - " + name: "done", + syntax: "[filter] done [modification]", + summary: "Mark tasks as completed", + description: " Mark all tasks matching the filter as completed, additionally applying any given modifications.", - ), }); } } @@ -278,13 +264,11 @@ impl List { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "list".to_owned(), - syntax: "[filter] list".to_owned(), - summary: "List tasks".to_owned(), - description: dedent( - " + name: "list", + syntax: "[filter] list", + summary: "List tasks", + description: " Show a list of the tasks matching the filter", - ), }); } } @@ -314,22 +298,16 @@ impl Info { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "info".to_owned(), - syntax: "[filter] info".to_owned(), - summary: "Show tasks".to_owned(), - description: dedent( - " - Show information about all tasks matching the fiter.", - ), + name: "info", + syntax: "[filter] info", + summary: "Show tasks", + description: " Show information about all tasks matching the fiter.", }); u.subcommands.push(usage::Subcommand { - name: "debug".to_owned(), - syntax: "[filter] debug".to_owned(), - summary: "Show task debug details".to_owned(), - description: dedent( - " - Show all key/value properties of the tasks matching the fiter.", - ), + name: "debug", + syntax: "[filter] debug", + summary: "Show task debug details", + description: " Show all key/value properties of the tasks matching the fiter.", }); } } @@ -346,14 +324,12 @@ impl Gc { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "gc".to_owned(), - syntax: "gc".to_owned(), - summary: "Perform 'garbage collection'".to_owned(), - description: dedent( - " + name: "gc", + syntax: "gc", + summary: "Perform 'garbage collection'", + description: " Perform 'garbage collection'. This refreshes the list of pending tasks and their short id's.", - ), }); } } @@ -370,16 +346,14 @@ impl Sync { fn get_usage(u: &mut usage::Usage) { u.subcommands.push(usage::Subcommand { - name: "sync".to_owned(), - syntax: "sync".to_owned(), - summary: "Synchronize this replica".to_owned(), - description: dedent( - " + name: "sync", + syntax: "sync", + summary: "Synchronize this replica", + description: " Synchronize this replica locally or against a remote server, as configured. 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.", - ), }) } } diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index 1aaba3ed8..0847ad9ab 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -8,7 +8,7 @@ pub(crate) fn execute( summary: bool, ) -> Fallible<()> { let usage = Usage::new(); - usage.write_help(w, command_name, summary)?; + usage.write_help(w, command_name.as_ref(), summary)?; Ok(()) } diff --git a/cli/src/usage.rs b/cli/src/usage.rs index b8251ffd1..159a7d368 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -3,7 +3,6 @@ use crate::argparse; use std::io::{Result, Write}; -use textwrap::indent; /// A top-level structure containing usage/help information for the entire CLI. #[derive(Debug, Default)] @@ -32,7 +31,7 @@ impl Usage { pub(crate) fn write_help( &self, mut w: W, - command_name: String, + command_name: &str, summary: bool, ) -> Result<()> { write!( @@ -45,15 +44,31 @@ impl Usage { for subcommand in self.subcommands.iter() { subcommand.write_help(&mut w, summary)?; } - write!(w, "Filter Expressions:\n\n")?; - write!(w, "Where [filter] appears above, zero or more of the following arguments can be used to limit\n")?; - write!(w, "the tasks concerned.\n\n")?; + writeln!(w, "Filter Expressions:\n")?; + writeln!( + 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() { filter.write_help(&mut w, summary)?; } - write!(w, "Modifications:\n\n")?; - write!(w, "Where [modification] appears above, zero or more of the following arguments can be used\n")?; - write!(w, "to modify the selected tasks.\n\n")?; + writeln!(w, "Modifications:\n")?; + writeln!( + 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() { 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 #[derive(Debug, Default)] pub(crate) struct Subcommand { /// Name of the subcommand - pub(crate) name: String, + pub(crate) name: &'static str, /// 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. - 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 /// two are not displayed together. - pub(crate) description: String, + pub(crate) description: &'static str, } impl Subcommand { @@ -86,11 +112,11 @@ impl Subcommand { if summary { writeln!(w, " task {} - {}", self.name, self.summary)?; } else { - write!( + writeln!( w, - " task {}\n{}\n", + " task {}\n{}", self.syntax, - indent(self.description.trim(), " ") + indented(self.description, " ") )?; } Ok(()) @@ -101,26 +127,26 @@ impl Subcommand { #[derive(Debug, Default)] pub(crate) struct Filter { /// Syntax summary - pub(crate) syntax: String, + pub(crate) syntax: &'static str, /// 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 /// two are not displayed together. - pub(crate) description: String, + pub(crate) description: &'static str, } impl Filter { fn write_help(&self, mut w: W, summary: bool) -> Result<()> { if summary { - write!(w, " {} - {}\n", self.syntax, self.summary)?; + writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { write!( w, " {}\n{}\n", self.syntax, - indent(self.description.trim(), " ") + indented(self.description, " ") )?; } Ok(()) @@ -131,26 +157,26 @@ impl Filter { #[derive(Debug, Default)] pub(crate) struct Modification { /// Syntax summary - pub(crate) syntax: String, + pub(crate) syntax: &'static str, /// 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 /// two are not displayed together. - pub(crate) description: String, + pub(crate) description: &'static str, } impl Modification { fn write_help(&self, mut w: W, summary: bool) -> Result<()> { if summary { - write!(w, " {} - {}\n", self.syntax, self.summary)?; + writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { - write!( + writeln!( w, - " {}\n{}\n", + " {}\n{}", self.syntax, - indent(self.description.trim(), " ") + indented(self.description, " ") )?; } Ok(())