mirror of
https://github.com/clap-rs/clap
synced 2025-03-04 23:37:32 +00:00
feat(Help Message): can auto wrap and aligning help text to term width
By default `clap` now automatically wraps and aligns help strings to the term width. i.e. ``` -o, --option <opt> some really long help text that should be auto aligned but isn't righ t now ``` Now looks like this: ``` -o, --option <opt> some really long help text that should be auto aligned but isn't right now ``` The wrapping also respects words, and wraps at spaces so as to not cut words in the middle. This requires the `libc` dep which is enabled (by default) with the `wrap_help` cargo feature flag. Closes #428
This commit is contained in:
parent
df38346557
commit
e36af02666
13 changed files with 413 additions and 171 deletions
|
@ -14,16 +14,18 @@ keywords = ["argument", "command", "arg", "parser", "parse"]
|
|||
[dependencies]
|
||||
bitflags = "~0.4"
|
||||
vec_map = "~0.6"
|
||||
libc = { version = "~0.2.8", optional = true }
|
||||
ansi_term = { version = "~0.7.2", optional = true }
|
||||
strsim = { version = "~0.4.0", optional = true }
|
||||
yaml-rust = { version = "~0.3", optional = true }
|
||||
clippy = { version = "~0.0.48", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["suggestions", "color"]
|
||||
default = ["suggestions", "color", "wrap_help"]
|
||||
suggestions = ["strsim"]
|
||||
color = ["ansi_term"]
|
||||
yaml = ["yaml-rust"]
|
||||
wrap_help = ["libc"]
|
||||
lints = ["clippy", "nightly"]
|
||||
nightly = [] # for building with nightly and unstable features
|
||||
unstable = [] # for building with unstable features on stable Rust
|
||||
|
|
16
README.md
16
README.md
|
@ -47,7 +47,7 @@ In v2.1.1
|
|||
|
||||
#### Improvements
|
||||
|
||||
* **Documenation Examples**: The examples in the documentation have been vastly improved
|
||||
* **Documentation Examples**: The examples in the documentation have been vastly improved
|
||||
|
||||
For full details, see [CHANGELOG.md](https://github.com/kbknapp/clap-rs/blob/master/CHANGELOG.md)
|
||||
|
||||
|
@ -433,14 +433,14 @@ default-features = false
|
|||
# Cherry-pick the features you'd like to use
|
||||
features = [ "suggestions", "color" ]
|
||||
```
|
||||
|
||||
The following is a list of optional `clap` features:
|
||||
|
||||
* **"suggestions"**: Turns on the `Did you mean '--myoption' ?` feature for when users make typos.
|
||||
* **"color"**: Turns on colored error messages. This feature only works on non-Windows OSs.
|
||||
* **"lints"**: This is **not** included by default and should only be used while developing to run basic lints against changes. This can only be used on Rust nightly.
|
||||
* **"suggestions"**: Turns on the `Did you mean '--myoption' ?` feature for when users make typos. (builds dependency `strsim`)
|
||||
* **"color"**: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term`)
|
||||
* **"wrap_help"**: Automatically detects terminal width and wraps long help text lines with proper indentation alignment (builds dependency `libc`)
|
||||
* **"lints"**: This is **not** included by default and should only be used while developing to run basic lints against changes. This can only be used on Rust nightly. (builds dependency `clippy`)
|
||||
* **"debug"**: This is **not** included by default and should only be used while developing to display debugging information.
|
||||
* **"yaml"**: This is **not** included by default. Enables building CLIs from YAML documents.
|
||||
* **"yaml"**: This is **not** included by default. Enables building CLIs from YAML documents. (builds dependency `yaml-rust`)
|
||||
* **"unstable"**: This is **not** included by default. Enables unstable features, unstable refers to whether or not they may change, not performance stability.
|
||||
|
||||
### Dependencies Tree
|
||||
|
@ -472,7 +472,7 @@ Contributions are always welcome! And there is a multitude of ways in which you
|
|||
|
||||
Another really great way to help is if you find an interesting, or helpful way in which to use `clap`. You can either add it to the [examples/](examples) directory, or file an issue and tell me. I'm all about giving credit where credit is due :)
|
||||
|
||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) before you start contributing.
|
||||
Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) before you start contributing.
|
||||
|
||||
### Running the tests
|
||||
|
||||
|
@ -553,6 +553,6 @@ As of 2.0.0 (From 1.x)
|
|||
|
||||
Old method names will be left around for several minor version bumps, or one major version bump.
|
||||
|
||||
As of 2.1.1:
|
||||
As of 2.2.0:
|
||||
|
||||
* None!
|
||||
|
|
|
@ -1448,7 +1448,6 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
try!(write!(w, "\n"));
|
||||
}
|
||||
|
||||
let tab = " ";
|
||||
let longest = if !unified_help || longest_opt == 0 {
|
||||
longest_flag
|
||||
} else {
|
||||
|
@ -1461,13 +1460,13 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) {
|
||||
let btm = ord_m.entry(f.disp_ord).or_insert(BTreeMap::new());
|
||||
let mut v = vec![];
|
||||
try!(f.write_help(&mut v, tab, longest, nlh));
|
||||
try!(f.write_help(&mut v, longest, nlh));
|
||||
btm.insert(f.name, v);
|
||||
}
|
||||
for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) {
|
||||
let btm = ord_m.entry(o.disp_ord).or_insert(BTreeMap::new());
|
||||
let mut v = vec![];
|
||||
try!(o.write_help(&mut v, tab, longest, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
|
||||
try!(o.write_help(&mut v, longest, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
|
||||
btm.insert(o.name, v);
|
||||
}
|
||||
for (_, btm) in ord_m.into_iter() {
|
||||
|
@ -1486,7 +1485,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
}
|
||||
for (_, btm) in ord_m.into_iter() {
|
||||
for (_, f) in btm.into_iter() {
|
||||
try!(f.write_help(w, tab, longest, nlh));
|
||||
try!(f.write_help(w, longest, nlh));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1499,7 +1498,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
}
|
||||
for (_, btm) in ord_m.into_iter() {
|
||||
for (_, o) in btm.into_iter() {
|
||||
try!(o.write_help(w, tab, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
|
||||
try!(o.write_help(w, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1508,7 +1507,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
try!(write!(w, "\nARGS:\n"));
|
||||
for v in self.positionals.values()
|
||||
.filter(|p| !p.settings.is_set(ArgSettings::Hidden)) {
|
||||
try!(v.write_help(w, tab, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
|
||||
try!(v.write_help(w, longest_pos, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh));
|
||||
}
|
||||
}
|
||||
if subcmds {
|
||||
|
@ -1520,7 +1519,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
}
|
||||
for (_, btm) in ord_m.into_iter() {
|
||||
for (name, sc) in btm.into_iter() {
|
||||
try!(write!(w, "{}{}", tab, name));
|
||||
try!(write!(w, " {}", name));
|
||||
write_spaces!((longest_sc + 4) - (name.len()), w);
|
||||
if let Some(a) = sc.p.meta.about {
|
||||
if a.contains("{n}") {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::rc::Rc;
|
||||
use std::fmt::Display;
|
||||
|
||||
use vec_map::VecMap;
|
||||
|
||||
use args::settings::ArgSettings;
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -20,4 +22,8 @@ pub trait AnyArg<'n, 'e>: Display {
|
|||
fn short(&self) -> Option<char>;
|
||||
fn long(&self) -> Option<&'e str>;
|
||||
fn val_delim(&self) -> Option<char>;
|
||||
fn takes_value(&self) -> bool;
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>>;
|
||||
fn help(&self) -> Option<&'e str>;
|
||||
fn default_val(&self) -> Option<&'n str>;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ use std::io;
|
|||
use std::rc::Rc;
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
use vec_map::VecMap;
|
||||
|
||||
use Arg;
|
||||
use args::AnyArg;
|
||||
use args::{AnyArg, HelpWriter};
|
||||
use args::settings::{ArgFlags, ArgSettings};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -47,9 +49,13 @@ impl<'n, 'e> FlagBuilder<'n, 'e> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn write_help<W: io::Write>(&self, w: &mut W, tab: &str, longest: usize, nlh: bool) -> io::Result<()> {
|
||||
write_arg_help!(@flag self, w, tab, longest, nlh);
|
||||
write!(w, "\n")
|
||||
pub fn write_help<W: io::Write>(&self, w: &mut W, longest: usize, nlh: bool) -> io::Result<()> {
|
||||
let hw = HelpWriter::new(
|
||||
self,
|
||||
longest,
|
||||
nlh,
|
||||
);
|
||||
hw.write_to(w)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,8 +104,10 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
|
|||
fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) }
|
||||
fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) }
|
||||
fn has_switch(&self) -> bool { true }
|
||||
fn takes_value(&self) -> bool { false }
|
||||
fn set(&mut self, s: ArgSettings) { self.settings.set(s) }
|
||||
fn max_vals(&self) -> Option<u64> { None }
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>> { None }
|
||||
fn num_vals(&self) -> Option<u64> { None }
|
||||
fn possible_vals(&self) -> Option<&[&'e str]> { None }
|
||||
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> { None }
|
||||
|
@ -107,6 +115,8 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
|
|||
fn short(&self) -> Option<char> { self.short }
|
||||
fn long(&self) -> Option<&'e str> { self.long }
|
||||
fn val_delim(&self) -> Option<char> { None }
|
||||
fn help(&self) -> Option<&'e str> { self.help }
|
||||
fn default_val(&self) -> Option<&'n str> { None }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
macro_rules! write_arg_help {
|
||||
(@opt $_self:ident, $w:ident, $tab:ident, $longest:ident, $skip_pv:ident, $nlh:ident) => {
|
||||
write_arg_help!(@short $_self, $w, $tab);
|
||||
write_arg_help!(@opt_long $_self, $w, $nlh, $longest);
|
||||
write_arg_help!(@val $_self, $w);
|
||||
if !($nlh || $_self.settings.is_set(ArgSettings::NextLineHelp)) {
|
||||
write_spaces!(if $_self.long.is_some() { $longest + 4 } else { $longest + 8 } - ($_self.to_string().len()), $w);
|
||||
}
|
||||
if let Some(h) = $_self.help {
|
||||
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
|
||||
write_arg_help!(@spec_vals $_self, $w, $skip_pv);
|
||||
}
|
||||
};
|
||||
(@flag $_self:ident, $w:ident, $tab:ident, $longest:ident, $nlh:ident) => {
|
||||
write_arg_help!(@short $_self, $w, $tab);
|
||||
write_arg_help!(@flag_long $_self, $w, $longest, $nlh);
|
||||
if let Some(h) = $_self.help {
|
||||
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
|
||||
}
|
||||
};
|
||||
(@pos $_self:ident, $w:ident, $tab:ident, $longest:ident, $skip_pv:ident, $nlh:ident) => {
|
||||
try!(write!($w, "{}", $tab));
|
||||
write_arg_help!(@val $_self, $w);
|
||||
if !($nlh || $_self.settings.is_set(ArgSettings::NextLineHelp)) {
|
||||
write_spaces!($longest + 4 - ($_self.to_string().len()), $w);
|
||||
}
|
||||
if let Some(h) = $_self.help {
|
||||
write_arg_help!(@help $_self, $w, h, $tab, $longest, $nlh);
|
||||
write_arg_help!(@spec_vals $_self, $w, $skip_pv);
|
||||
}
|
||||
};
|
||||
(@short $_self:ident, $w:ident, $tab:ident) => {
|
||||
try!(write!($w, "{}", $tab));
|
||||
if let Some(s) = $_self.short {
|
||||
try!(write!($w, "-{}", s));
|
||||
} else {
|
||||
try!(write!($w, "{}", $tab));
|
||||
}
|
||||
};
|
||||
(@flag_long $_self:ident, $w:ident, $longest:ident, $nlh:ident) => {
|
||||
if let Some(l) = $_self.long {
|
||||
write_arg_help!(@long $_self, $w, l);
|
||||
if !$nlh || !$_self.settings.is_set(ArgSettings::NextLineHelp) {
|
||||
write_spaces!(($longest + 4) - (l.len() + 2), $w);
|
||||
}
|
||||
} else {
|
||||
if !$nlh || !$_self.settings.is_set(ArgSettings::NextLineHelp) {
|
||||
// 6 is tab (4) + -- (2)
|
||||
write_spaces!(($longest + 6), $w);
|
||||
}
|
||||
}
|
||||
};
|
||||
(@opt_long $_self:ident, $w:ident, $nlh:ident, $longest:ident) => {
|
||||
if let Some(l) = $_self.long {
|
||||
write_arg_help!(@long $_self, $w, l);
|
||||
}
|
||||
try!(write!($w, " "));
|
||||
};
|
||||
(@long $_self:ident, $w:ident, $l:ident) => {
|
||||
try!(write!($w,
|
||||
"{}--{}",
|
||||
if $_self.short.is_some() {
|
||||
", "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
$l));
|
||||
};
|
||||
(@val $_self:ident, $w:ident) => {
|
||||
if let Some(ref vec) = $_self.val_names {
|
||||
let mut it = vec.iter().peekable();
|
||||
while let Some((_, val)) = it.next() {
|
||||
try!(write!($w, "<{}>", val));
|
||||
if it.peek().is_some() { try!(write!($w, " ")); }
|
||||
}
|
||||
let num = vec.len();
|
||||
if $_self.settings.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
try!(write!($w, "..."));
|
||||
}
|
||||
} else if let Some(num) = $_self.num_vals {
|
||||
for _ in 0..num {
|
||||
try!(write!($w, "<{}>", $_self.name));
|
||||
}
|
||||
} else {
|
||||
try!(write!($w,
|
||||
"<{}>{}",
|
||||
$_self.name,
|
||||
if $_self.settings.is_set(ArgSettings::Multiple) {
|
||||
"..."
|
||||
} else {
|
||||
""
|
||||
}));
|
||||
}
|
||||
};
|
||||
(@spec_vals $_self:ident, $w:ident, $skip_pv:ident) => {
|
||||
if let Some(ref pv) = $_self.default_val {
|
||||
try!(write!($w, " [default: {}]", pv));
|
||||
}
|
||||
if !$skip_pv {
|
||||
if let Some(ref pv) = $_self.possible_vals {
|
||||
try!(write!($w, " [values: {}]", pv.join(", ")));
|
||||
}
|
||||
}
|
||||
};
|
||||
(@help $_self:ident, $w:ident, $h:ident, $tab:ident, $longest:expr, $nlh:ident) => {
|
||||
if $nlh || $_self.settings.is_set(ArgSettings::NextLineHelp) {
|
||||
try!(write!($w, "\n{}{}", $tab, $tab));
|
||||
}
|
||||
if $h.contains("{n}") {
|
||||
if let Some(part) = $h.split("{n}").next() {
|
||||
try!(write!($w, "{}", part));
|
||||
}
|
||||
for part in $h.split("{n}").skip(1) {
|
||||
try!(write!($w, "\n"));
|
||||
if $nlh || $_self.settings.is_set(ArgSettings::NextLineHelp) {
|
||||
try!(write!($w, "{}{}", $tab, $tab));
|
||||
} else {
|
||||
write_spaces!($longest + 12, $w);
|
||||
}
|
||||
try!(write!($w, "{}", part));
|
||||
}
|
||||
} else {
|
||||
try!(write!($w, "{}", $h));
|
||||
}
|
||||
};
|
||||
}
|
|
@ -2,8 +2,6 @@ pub use self::flag::FlagBuilder;
|
|||
pub use self::option::OptBuilder;
|
||||
pub use self::positional::PosBuilder;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
#[allow(dead_code)]
|
||||
mod flag;
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::io;
|
|||
|
||||
use vec_map::VecMap;
|
||||
|
||||
use args::{AnyArg, Arg};
|
||||
use args::{AnyArg, Arg, HelpWriter};
|
||||
use args::settings::{ArgFlags, ArgSettings};
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
@ -104,10 +104,10 @@ impl<'n, 'e> OptBuilder<'n, 'e> {
|
|||
ob
|
||||
}
|
||||
|
||||
pub fn write_help<W: io::Write>(&self, w: &mut W, tab: &str, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> {
|
||||
debugln!("fn=write_help");
|
||||
write_arg_help!(@opt self, w, tab, longest, skip_pv, nlh);
|
||||
write!(w, "\n")
|
||||
pub fn write_help<W: io::Write>(&self, w: &mut W, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> {
|
||||
let mut hw = HelpWriter::new(self, longest, nlh);
|
||||
hw.skip_pv = skip_pv;
|
||||
hw.write_to(w)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,29 +116,30 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> {
|
|||
debugln!("fn=fmt");
|
||||
// Write the name such --long or -l
|
||||
if let Some(l) = self.long {
|
||||
try!(write!(f, "--{}", l));
|
||||
try!(write!(f, "--{} ", l));
|
||||
} else {
|
||||
try!(write!(f, "-{}", self.short.unwrap()));
|
||||
try!(write!(f, "-{} ", self.short.unwrap()));
|
||||
}
|
||||
|
||||
// Write the values such as <name1> <name2>
|
||||
if let Some(ref vec) = self.val_names {
|
||||
for (_, n) in vec {
|
||||
debugln!("writing val_name: {}", n);
|
||||
try!(write!(f, " <{}>", n));
|
||||
let mut it = vec.iter().peekable();
|
||||
while let Some((_, val)) = it.next() {
|
||||
try!(write!(f, "<{}>", val));
|
||||
if it.peek().is_some() { try!(write!(f, " ")); }
|
||||
}
|
||||
let num = vec.len();
|
||||
if self.settings.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
if self.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
try!(write!(f, "..."));
|
||||
}
|
||||
} else if let Some(num) = self.num_vals {
|
||||
let mut it = (0..num).peekable();
|
||||
while let Some(_) = it.next() {
|
||||
try!(write!(f, "<{}>", self.name));
|
||||
if it.peek().is_some() { try!(write!(f, " ")); }
|
||||
}
|
||||
} else {
|
||||
let num = self.num_vals.unwrap_or(1);
|
||||
for _ in 0..num {
|
||||
try!(write!(f, " <{}>", self.name));
|
||||
}
|
||||
if self.settings.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
try!(write!(f, "..."));
|
||||
}
|
||||
try!(write!(f, "<{}>{}", self.name, if self.is_set(ArgSettings::Multiple) { "..." } else { "" }));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -150,6 +151,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
|
|||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) }
|
||||
fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) }
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>> { self.val_names.as_ref().map(|o| o) }
|
||||
fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) }
|
||||
fn has_switch(&self) -> bool { true }
|
||||
fn set(&mut self, s: ArgSettings) { self.settings.set(s) }
|
||||
|
@ -163,6 +165,9 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
|
|||
fn short(&self) -> Option<char> { self.short }
|
||||
fn long(&self) -> Option<&'e str> { self.long }
|
||||
fn val_delim(&self) -> Option<char> { self.val_delim }
|
||||
fn takes_value(&self) -> bool { true }
|
||||
fn help(&self) -> Option<&'e str> { self.help }
|
||||
fn default_val(&self) -> Option<&'n str> { self.default_val }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::io;
|
|||
use vec_map::VecMap;
|
||||
|
||||
use Arg;
|
||||
use args::AnyArg;
|
||||
use args::{AnyArg, HelpWriter};
|
||||
use args::settings::{ArgFlags, ArgSettings};
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
@ -63,7 +63,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> {
|
|||
}
|
||||
|
||||
pub fn from_arg(a: &Arg<'n, 'e>, idx: u64, reqs: &mut Vec<&'e str>) -> Self {
|
||||
assert!(a.short.is_none() || a.long.is_none(),
|
||||
debug_assert!(a.short.is_none() || a.long.is_none(),
|
||||
format!("Argument \"{}\" has conflicting requirements, both index() and short(), \
|
||||
or long(), were supplied", a.name));
|
||||
|
||||
|
@ -105,9 +105,10 @@ impl<'n, 'e> PosBuilder<'n, 'e> {
|
|||
pb
|
||||
}
|
||||
|
||||
pub fn write_help<W: io::Write>(&self, w: &mut W, tab: &str, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> {
|
||||
write_arg_help!(@pos self, w, tab, longest, skip_pv, nlh);
|
||||
write!(w, "\n")
|
||||
pub fn write_help<W: io::Write>(&self, w: &mut W, longest: usize, skip_pv: bool, nlh: bool) -> io::Result<()> {
|
||||
let mut hw = HelpWriter::new(self, longest, nlh);
|
||||
hw.skip_pv = skip_pv;
|
||||
hw.write_to(w)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +140,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
|
|||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) }
|
||||
fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) }
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>> { self.val_names.as_ref() }
|
||||
fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) }
|
||||
fn set(&mut self, s: ArgSettings) { self.settings.set(s) }
|
||||
fn has_switch(&self) -> bool { false }
|
||||
|
@ -152,6 +154,9 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
|
|||
fn short(&self) -> Option<char> { None }
|
||||
fn long(&self) -> Option<&'e str> { None }
|
||||
fn val_delim(&self) -> Option<char> { self.val_delim }
|
||||
fn takes_value(&self) -> bool { true }
|
||||
fn help(&self) -> Option<&'e str> { self.help }
|
||||
fn default_val(&self) -> Option<&'n str> { self.default_val }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
256
src/args/help_writer.rs
Normal file
256
src/args/help_writer.rs
Normal file
|
@ -0,0 +1,256 @@
|
|||
use std::io;
|
||||
|
||||
|
||||
use args::AnyArg;
|
||||
use args::settings::ArgSettings;
|
||||
use term;
|
||||
|
||||
const TAB: &'static str = " ";
|
||||
|
||||
pub struct HelpWriter<'a, A> where A: 'a {
|
||||
a: &'a A,
|
||||
l: usize,
|
||||
nlh: bool,
|
||||
pub skip_pv: bool,
|
||||
term_w: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> {
|
||||
pub fn new(a: &'a A, l: usize, nlh: bool) -> Self {
|
||||
HelpWriter {
|
||||
a: a,
|
||||
l: l,
|
||||
nlh: nlh,
|
||||
skip_pv: false,
|
||||
term_w: term::dimensions().map(|(w, _)| w),
|
||||
}
|
||||
}
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
debugln!("fn=write_to;");
|
||||
try!(self.short(w));
|
||||
try!(self.long(w));
|
||||
try!(self.val(w));
|
||||
try!(self.help(w));
|
||||
write!(w, "\n")
|
||||
}
|
||||
|
||||
fn short<W>(&self, w: &mut W) -> io::Result<()>
|
||||
where W: io::Write
|
||||
{
|
||||
debugln!("fn=short;");
|
||||
try!(write!(w, "{}", TAB));
|
||||
if let Some(s) = self.a.short() {
|
||||
write!(w, "-{}", s)
|
||||
} else if self.a.has_switch() {
|
||||
write!(w, "{}", TAB)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn long<W>(&self, w: &mut W) -> io::Result<()>
|
||||
where W: io::Write
|
||||
{
|
||||
debugln!("fn=long;");
|
||||
if !self.a.has_switch() {
|
||||
return Ok(());
|
||||
}
|
||||
if self.a.takes_value() {
|
||||
if let Some(l) = self.a.long() {
|
||||
try!(write!(w, "{}--{}", if self.a.short().is_some() { ", " } else { "" }, l));
|
||||
}
|
||||
try!(write!(w, " "));
|
||||
} else {
|
||||
if let Some(l) = self.a.long() {
|
||||
try!(write!(w, "{}--{}", if self.a.short().is_some() { ", " } else { "" }, l));
|
||||
if !self.nlh || !self.a.is_set(ArgSettings::NextLineHelp) {
|
||||
write_spaces!((self.l + 4) - (l.len() + 2), w);
|
||||
}
|
||||
} else {
|
||||
if !self.nlh || !self.a.is_set(ArgSettings::NextLineHelp) {
|
||||
// 6 is tab (4) + -- (2)
|
||||
write_spaces!((self.l + 6), w);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn val<W>(&self, w: &mut W) -> io::Result<()>
|
||||
where W: io::Write
|
||||
{
|
||||
debugln!("fn=val;");
|
||||
if !self.a.takes_value() {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(ref vec) = self.a.val_names() {
|
||||
let mut it = vec.iter().peekable();
|
||||
while let Some((_, val)) = it.next() {
|
||||
try!(write!(w, "<{}>", val));
|
||||
if it.peek().is_some() { try!(write!(w, " ")); }
|
||||
}
|
||||
let num = vec.len();
|
||||
if self.a.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
try!(write!(w, "..."));
|
||||
}
|
||||
} else if let Some(num) = self.a.num_vals() {
|
||||
let mut it = (0..num).peekable();
|
||||
while let Some(_) = it.next() {
|
||||
try!(write!(w, "<{}>", self.a.name()));
|
||||
if it.peek().is_some() { try!(write!(w, " ")); }
|
||||
}
|
||||
} else {
|
||||
try!(write!(w, "<{}>{}", self.a.name(), if self.a.is_set(ArgSettings::Multiple) { "..." } else { "" }));
|
||||
}
|
||||
if self.a.has_switch() {
|
||||
if !(self.nlh || self.a.is_set(ArgSettings::NextLineHelp)) {
|
||||
let self_len = self.a.to_string().len();
|
||||
// subtract ourself
|
||||
let mut spcs = self.l - self_len;
|
||||
// Since we're writing spaces from the tab point we first need to know if we
|
||||
// had a long and short, or just short
|
||||
if self.a.long().is_some() {
|
||||
// Only account 4 after the val
|
||||
spcs += 4;
|
||||
} else {
|
||||
// Only account for ', --' + 4 after the val
|
||||
spcs += 8;
|
||||
}
|
||||
write_spaces!(spcs, w);
|
||||
}
|
||||
} else {
|
||||
if !(self.nlh || self.a.is_set(ArgSettings::NextLineHelp)) {
|
||||
write_spaces!(self.l + 4 - (self.a.to_string().len()), w);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn help<W>(&self, w: &mut W) -> io::Result<()>
|
||||
where W: io::Write
|
||||
{
|
||||
debugln!("fn=help;");
|
||||
let spec_vals = self.spec_vals();
|
||||
let mut help = String::new();
|
||||
let h = self.a.help().unwrap_or("");
|
||||
let spcs = if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) {
|
||||
8 // "tab" + "tab"
|
||||
} else {
|
||||
self.l + 12
|
||||
};
|
||||
// determine if our help fits or needs to wrap
|
||||
let too_long = self.term_w.is_some() && (spcs + h.len() + spec_vals.len() >= self.term_w.unwrap_or(0));
|
||||
|
||||
// Is help on next line, if so newline + 2x tab
|
||||
if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) {
|
||||
try!(write!(w, "\n{}{}", TAB, TAB));
|
||||
}
|
||||
|
||||
debug!("Too long...");
|
||||
if too_long {
|
||||
sdebugln!("Yes");
|
||||
if let Some(width) = self.term_w {
|
||||
help.push_str(h);
|
||||
help.push_str(&*spec_vals);
|
||||
debugln!("term width: {}", width);
|
||||
debugln!("help: {}", help);
|
||||
debugln!("help len: {}", help.len());
|
||||
// Determine how many newlines we need to insert
|
||||
let avail_chars = width - spcs;
|
||||
debugln!("Usable space: {}", avail_chars);
|
||||
let mut indices = vec![];
|
||||
let mut idx = 0;
|
||||
loop {
|
||||
idx += avail_chars - 1;
|
||||
if idx >= help.len() { break; }
|
||||
// 'a' arbitrary non space char
|
||||
if help.chars().nth(idx).unwrap_or('a') != ' ' {
|
||||
idx = find_idx_of_space(&*help, idx);
|
||||
}
|
||||
debugln!("Adding idx: {}", idx);
|
||||
debugln!("At {}: {:?}", idx, help.chars().nth(idx));
|
||||
indices.push(idx);
|
||||
if &help[idx..].len() <= &avail_chars {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i, idx) in indices.iter().enumerate() {
|
||||
debugln!("iter;i={},idx={}", i, idx);
|
||||
let j = idx+(2*i);
|
||||
debugln!("removing: {}", j);
|
||||
debugln!("at {}: {:?}", j, help.chars().nth(j));
|
||||
help.remove(j);
|
||||
help.insert(j, '{');
|
||||
help.insert(j + 1 , 'n');
|
||||
help.insert(j + 2, '}');
|
||||
}
|
||||
}
|
||||
} else { sdebugln!("No"); }
|
||||
let help = if !help.is_empty() {
|
||||
&*help
|
||||
} else if !spec_vals.is_empty() {
|
||||
help.push_str(h);
|
||||
help.push_str(&*spec_vals);
|
||||
&*help
|
||||
} else {
|
||||
h
|
||||
};
|
||||
if help.contains("{n}") {
|
||||
if let Some(part) = help.split("{n}").next() {
|
||||
try!(write!(w, "{}", part));
|
||||
}
|
||||
for part in help.split("{n}").skip(1) {
|
||||
try!(write!(w, "\n"));
|
||||
if self.nlh || self.a.is_set(ArgSettings::NextLineHelp) {
|
||||
try!(write!(w, "{}{}", TAB, TAB));
|
||||
} else {
|
||||
if self.a.has_switch() {
|
||||
write_spaces!(self.l + 12, w);
|
||||
} else {
|
||||
write_spaces!(self.l + 8, w);
|
||||
}
|
||||
}
|
||||
try!(write!(w, "{}", part));
|
||||
}
|
||||
} else {
|
||||
try!(write!(w, "{}", help));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spec_vals(&self) -> String
|
||||
{
|
||||
debugln!("fn=spec_vals;");
|
||||
if let Some(ref pv) = self.a.default_val() {
|
||||
debugln!("Writing defaults");
|
||||
return format!(" [default: {}] {}", pv,
|
||||
if !self.skip_pv {
|
||||
if let Some(ref pv) = self.a.possible_vals() {
|
||||
format!(" [values: {}]", pv.join(", "))
|
||||
} else { "".into() }
|
||||
} else { "".into() }
|
||||
);
|
||||
} else if !self.skip_pv {
|
||||
debugln!("Writing values");
|
||||
if let Some(ref pv) = self.a.possible_vals() {
|
||||
debugln!("Possible vals...{:?}", pv);
|
||||
return format!(" [values: {}]", pv.join(", "));
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn find_idx_of_space(full: &str, start: usize) -> usize {
|
||||
debugln!("fn=find_idx_of_space;");
|
||||
let haystack = &full[..start];
|
||||
debugln!("haystack: {}", haystack);
|
||||
for (i, c) in haystack.chars().rev().enumerate() {
|
||||
debugln!("iter;c={},i={}", c, i);
|
||||
if c == ' ' {
|
||||
debugln!("Found space returning start-i...{}", start - (i+1));
|
||||
return start - (i+1);
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
|
@ -7,6 +7,7 @@ pub use self::matched_arg::MatchedArg;
|
|||
pub use self::group::ArgGroup;
|
||||
pub use self::any_arg::AnyArg;
|
||||
pub use self::settings::ArgSettings;
|
||||
pub use self::help_writer::HelpWriter;
|
||||
|
||||
mod arg;
|
||||
pub mod any_arg;
|
||||
|
@ -18,3 +19,4 @@ mod matched_arg;
|
|||
mod group;
|
||||
#[allow(dead_code)]
|
||||
pub mod settings;
|
||||
mod help_writer;
|
||||
|
|
|
@ -405,6 +405,8 @@ extern crate strsim;
|
|||
extern crate ansi_term;
|
||||
#[cfg(feature = "yaml")]
|
||||
extern crate yaml_rust;
|
||||
#[cfg(feature = "wrap_help")]
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate vec_map;
|
||||
|
@ -425,6 +427,7 @@ mod fmt;
|
|||
mod suggestions;
|
||||
mod errors;
|
||||
mod osstringext;
|
||||
mod term;
|
||||
|
||||
const INTERNAL_ERROR_MSG: &'static str = "Fatal internal error. Please consider filing a bug \
|
||||
report at https://github.com/kbknapp/clap-rs/issues";
|
||||
|
|
82
src/term.rs
Normal file
82
src/term.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
// The following was taken and adapated from exa source
|
||||
// repo: https://github.com/ogham/exa
|
||||
// commit: b9eb364823d0d4f9085eb220233c704a13d0f611
|
||||
// license: MIT - Copyright (c) 2014 Benjamin Sago
|
||||
|
||||
//! System calls for getting the terminal size.
|
||||
//!
|
||||
//! Getting the terminal size is performed using an ioctl command that takes
|
||||
//! the file handle to the terminal -- which in this case, is stdout -- and
|
||||
//! populates a structure containing the values.
|
||||
//!
|
||||
//! The size is needed when the user wants the output formatted into columns:
|
||||
//! the default grid view, or the hybrid grid-details view.
|
||||
|
||||
#[cfg(feature = "wrap_help")]
|
||||
use std::mem::zeroed;
|
||||
#[cfg(feature = "wrap_help")]
|
||||
use libc::{c_int, c_ushort, c_ulong, STDOUT_FILENO};
|
||||
|
||||
|
||||
/// The number of rows and columns of a terminal.
|
||||
#[cfg(feature = "wrap_help")]
|
||||
struct Winsize {
|
||||
ws_row: c_ushort,
|
||||
ws_col: c_ushort,
|
||||
}
|
||||
|
||||
// Unfortunately the actual command is not standardised...
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg(feature = "wrap_help")]
|
||||
static TIOCGWINSZ: c_ulong = 0x5413;
|
||||
|
||||
#[cfg(any(target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "bitrig",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"))]
|
||||
#[cfg(feature = "wrap_help")]
|
||||
static TIOCGWINSZ: c_ulong = 0x40087468;
|
||||
|
||||
extern {
|
||||
#[cfg(feature = "wrap_help")]
|
||||
pub fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int;
|
||||
}
|
||||
|
||||
/// Runs the ioctl command. Returns (0, 0) if output is not to a terminal, or
|
||||
/// there is an error. (0, 0) is an invalid size to have anyway, which is why
|
||||
/// it can be used as a nil value.
|
||||
#[cfg(feature = "wrap_help")]
|
||||
unsafe fn get_dimensions() -> Winsize {
|
||||
let mut window: Winsize = zeroed();
|
||||
let result = ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut window);
|
||||
|
||||
if result == -1 {
|
||||
zeroed()
|
||||
}
|
||||
else {
|
||||
window
|
||||
}
|
||||
}
|
||||
|
||||
/// Query the current processes's output, returning its width and height as a
|
||||
/// number of characters. Returns `None` if the output isn't to a terminal.
|
||||
#[cfg(feature = "wrap_help")]
|
||||
pub fn dimensions() -> Option<(usize, usize)> {
|
||||
let w = unsafe { get_dimensions() };
|
||||
|
||||
if w.ws_col == 0 || w.ws_row == 0 {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some((w.ws_col as usize, w.ws_row as usize))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wrap_help"))]
|
||||
pub fn dimensions() -> Option<(usize, usize)> {
|
||||
None
|
||||
}
|
Loading…
Add table
Reference in a new issue