1 use std::env::var_os;
2 
3 use atty::{self, Stream};
4 use clap::{arg_enum, App, Arg};
5 
6 use xd::table::*;
7 
8 macro_rules! value_t_default_exit {
9     ($m:ident, $v:expr, $t:ty) => {
10         match ::clap::value_t!($m, $v, $t) {
11             Err(::clap::Error {
12                 kind: ::clap::ErrorKind::ArgumentNotFound,
13                 ..
14             }) => <$t as ::core::default::Default>::default(),
15             Err(other) => other.exit(),
16             Ok(other) => other,
17         }
18     };
19 }
20 
21 arg_enum! {
22     #[derive(Debug)]
23     #[allow(non_camel_case_types)]
24     pub enum TableOption {
25         default,
26         classic,
27         reverse,
28     }
29 }
30 
31 impl Default for TableOption {
default() -> Self32     fn default() -> Self {
33         Self::default
34     }
35 }
36 
37 arg_enum! {
38     #[derive(Debug)]
39     #[allow(non_camel_case_types)]
40     pub enum ColorOption {
41         auto,
42         never,
43         always,
44     }
45 }
46 
47 impl ColorOption {
should(&self) -> bool48     pub fn should(&self) -> bool {
49         match self {
50             Self::never => return false,
51             Self::always => return true,
52             Self::auto => (),
53         }
54 
55         if !atty::is(Stream::Stdout) {
56             return false;
57         }
58 
59         let missing_term_is_meaningful = cfg!(unix);
60 
61         if var_os("TERM").map_or(missing_term_is_meaningful, |x| x == "dumb") {
62             return false;
63         }
64 
65         // “preference” environment variables not yet enabled, because i’m not yet convinced that
66         // either of these conventions is accepted widely enough to commit to permanent support
67 
68         #[cfg(any())]
69         if var("CLICOLOR_FORCE").as_ref().map(|x| &**x).unwrap_or("0") != "0" {
70             return true;
71         }
72 
73         #[cfg(any())]
74         if let Ok("0") = var("CLICOLOR").as_ref().map(|x| &**x) {
75             return false;
76         }
77 
78         #[cfg(any())]
79         if var("NO_COLOR").is_ok() {
80             return false;
81         }
82 
83         return true;
84     }
85 }
86 
87 impl Default for ColorOption {
default() -> Self88     fn default() -> Self {
89         Self::auto
90     }
91 }
92 
93 impl From<TableOption> for &xd::table::Table {
from(variant: TableOption) -> Self94     fn from(variant: TableOption) -> Self {
95         match variant {
96             TableOption::default => &DEFAULT,
97             TableOption::classic => &CLASSIC,
98             TableOption::reverse => &REVERSE,
99         }
100     }
101 }
102 
103 #[rustfmt::skip]
app() -> App<'static, 'static>104 pub fn app() -> App<'static, 'static> {
105     App::new("xd")
106         .version(clap::crate_version!())
107         .author(clap::crate_authors!("\n"))
108         .about("
109 Dumps binary input in a variety of formats.
110 
111 The default format describes up to 16 octets on each line, in both hexadecimal
112 and text, and the latter with a tweaked code page 437, like on an old IBM PC.
113 
114 This encoding provides a unique glyph for each octet, making it reversible,
115 and almost all of those glyphs are visually distinct, which is helpful when
116 visualising patterns in binary data that transcends printable ASCII.
117 
118 Use -h for short descriptions and --help for more details.
119 
120 Project home page: <https://bitbucket.org/delan/xd>
121 ")
122         .arg(Arg::from_usage("--example 'Use an example as input'").display_order(0))
123         .arg(Arg::from_usage("[color] --color [when] 'When to use colours or other text styles\n'")
124             .case_insensitive(true)
125             .possible_values(&ColorOption::variants())
126             // FIXME if only clap had a hide_possible_values_long_help
127             .hide_possible_values(true)
128             .long_help(concat!("When to use colours or other text styles:
129   • auto        [default] try to be smart
130   • never       never use colours or other text styles
131   • always      always use colours or other text styles
132 
133 In auto mode, we decide whether or not to use them as follows:
134   • no, if standard output is not a tty (Unix) or console (Windows)
135   • no, if the environment has TERM=dumb (all platforms)
136   • no, if the environment has no TERM (Unix)
137 ", /* "  • yes, if the environment has CLICOLOR_FORCE≠0
138 ",    "  • no, if the environment has CLICOLOR=0 or NO_COLOR
139 ", */ "  • yes, otherwise
140 ", /* FIXME why does clap trim this line off, when it leaves a blank line after other options? */ " ")))
141         .arg(Arg::from_usage("[table] --table [name] 'Character set to use for text columns\n'")
142             .case_insensitive(true)
143             .possible_values(&TableOption::variants())
144             // FIXME if only clap had a hide_possible_values_long_help
145             .hide_possible_values(true)
146             .long_help(concat!("Character set to use for text columns:
147   • default     code page 437 (full graphics) + symbol for null
148   • classic     printable ASCII + dot (.) for everything else
149   • reverse     control pictures + reverse video for high octets
150 ", /* FIXME why does clap trim this line off, when it leaves a blank line after other options? */ " ")))
151         .arg(Arg::from_usage("--unbuffered 'Flush output after each line'").display_order(0).alias("line-buffered"))
152         .arg(Arg::from_usage("[input] 'Input file path [default: standard input]'"))
153 }
154