##// END OF EJS Templates
rhg: print error message when argument parsing fails...
Antoine Cezar -
r46011:a6a000ab default
parent child Browse files
Show More
@@ -1,57 +1,58 b''
1 use clap::App;
1 use clap::App;
2 use clap::AppSettings;
2 use clap::AppSettings;
3 use clap::SubCommand;
3 use clap::SubCommand;
4
4
5 mod commands;
5 mod commands;
6 mod error;
6 mod error;
7 mod exitcode;
7 mod exitcode;
8 mod ui;
8 mod ui;
9 use commands::Command;
9 use commands::Command;
10
10
11 fn main() {
11 fn main() {
12 let mut app = App::new("rhg")
12 let mut app = App::new("rhg")
13 .setting(AppSettings::AllowInvalidUtf8)
13 .setting(AppSettings::AllowInvalidUtf8)
14 .setting(AppSettings::SubcommandRequired)
14 .setting(AppSettings::SubcommandRequired)
15 .setting(AppSettings::VersionlessSubcommands)
15 .setting(AppSettings::VersionlessSubcommands)
16 .version("0.0.1")
16 .version("0.0.1")
17 .subcommand(
17 .subcommand(
18 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
18 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
19 )
19 )
20 .subcommand(
20 .subcommand(
21 SubCommand::with_name("files").about(commands::files::HELP_TEXT),
21 SubCommand::with_name("files").about(commands::files::HELP_TEXT),
22 );
22 );
23
23
24 let matches = app.clone().get_matches_safe().unwrap_or_else(|_| {
24 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
25 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
25 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
26 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
26 });
27 });
27
28
28 let ui = ui::Ui::new();
29 let ui = ui::Ui::new();
29
30
30 let command_result = match matches.subcommand_name() {
31 let command_result = match matches.subcommand_name() {
31 Some(name) => match name {
32 Some(name) => match name {
32 "root" => commands::root::RootCommand::new().run(&ui),
33 "root" => commands::root::RootCommand::new().run(&ui),
33 "files" => commands::files::FilesCommand::new().run(&ui),
34 "files" => commands::files::FilesCommand::new().run(&ui),
34 _ => std::process::exit(exitcode::UNIMPLEMENTED_COMMAND),
35 _ => std::process::exit(exitcode::UNIMPLEMENTED_COMMAND),
35 },
36 },
36 _ => {
37 _ => {
37 match app.print_help() {
38 match app.print_help() {
38 Ok(_) => std::process::exit(exitcode::OK),
39 Ok(_) => std::process::exit(exitcode::OK),
39 Err(_) => std::process::exit(exitcode::ABORT),
40 Err(_) => std::process::exit(exitcode::ABORT),
40 };
41 };
41 }
42 }
42 };
43 };
43
44
44 match command_result {
45 match command_result {
45 Ok(_) => std::process::exit(exitcode::OK),
46 Ok(_) => std::process::exit(exitcode::OK),
46 Err(e) => {
47 Err(e) => {
47 let message = e.get_error_message_bytes();
48 let message = e.get_error_message_bytes();
48 if let Some(msg) = message {
49 if let Some(msg) = message {
49 match ui.write_stderr(&msg) {
50 match ui.write_stderr(&msg) {
50 Ok(_) => (),
51 Ok(_) => (),
51 Err(_) => std::process::exit(exitcode::ABORT),
52 Err(_) => std::process::exit(exitcode::ABORT),
52 };
53 };
53 };
54 };
54 e.exit()
55 e.exit()
55 }
56 }
56 }
57 }
57 }
58 }
@@ -1,100 +1,105 b''
1 use std::io;
1 use std::io;
2 use std::io::{ErrorKind, Write};
2 use std::io::{ErrorKind, Write};
3
3
4 #[derive(Debug)]
4 #[derive(Debug)]
5 pub struct Ui {
5 pub struct Ui {
6 stdout: std::io::Stdout,
6 stdout: std::io::Stdout,
7 stderr: std::io::Stderr,
7 stderr: std::io::Stderr,
8 }
8 }
9
9
10 /// The kind of user interface error
10 /// The kind of user interface error
11 pub enum UiError {
11 pub enum UiError {
12 /// The standard output stream cannot be written to
12 /// The standard output stream cannot be written to
13 StdoutError(io::Error),
13 StdoutError(io::Error),
14 /// The standard error stream cannot be written to
14 /// The standard error stream cannot be written to
15 StderrError(io::Error),
15 StderrError(io::Error),
16 }
16 }
17
17
18 /// The commandline user interface
18 /// The commandline user interface
19 impl Ui {
19 impl Ui {
20 pub fn new() -> Self {
20 pub fn new() -> Self {
21 Ui {
21 Ui {
22 stdout: std::io::stdout(),
22 stdout: std::io::stdout(),
23 stderr: std::io::stderr(),
23 stderr: std::io::stderr(),
24 }
24 }
25 }
25 }
26
26
27 /// Returns a buffered handle on stdout for faster batch printing
27 /// Returns a buffered handle on stdout for faster batch printing
28 /// operations.
28 /// operations.
29 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
29 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
30 StdoutBuffer::new(self.stdout.lock())
30 StdoutBuffer::new(self.stdout.lock())
31 }
31 }
32
32
33 /// Write bytes to stdout
33 /// Write bytes to stdout
34 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
34 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
35 let mut stdout = self.stdout.lock();
35 let mut stdout = self.stdout.lock();
36
36
37 stdout.write_all(bytes).or_else(handle_stdout_error)?;
37 stdout.write_all(bytes).or_else(handle_stdout_error)?;
38
38
39 stdout.flush().or_else(handle_stdout_error)
39 stdout.flush().or_else(handle_stdout_error)
40 }
40 }
41
41
42 /// Write bytes to stderr
42 /// Write bytes to stderr
43 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
43 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
44 let mut stderr = self.stderr.lock();
44 let mut stderr = self.stderr.lock();
45
45
46 stderr.write_all(bytes).or_else(handle_stderr_error)?;
46 stderr.write_all(bytes).or_else(handle_stderr_error)?;
47
47
48 stderr.flush().or_else(handle_stderr_error)
48 stderr.flush().or_else(handle_stderr_error)
49 }
49 }
50
51 /// Write string line to stderr
52 pub fn writeln_stderr_str(&self, s: &str) -> Result<(), UiError> {
53 self.write_stderr(&format!("{}\n", s).as_bytes())
54 }
50 }
55 }
51
56
52 /// A buffered stdout writer for faster batch printing operations.
57 /// A buffered stdout writer for faster batch printing operations.
53 pub struct StdoutBuffer<W: Write> {
58 pub struct StdoutBuffer<W: Write> {
54 buf: io::BufWriter<W>,
59 buf: io::BufWriter<W>,
55 }
60 }
56
61
57 impl<W: Write> StdoutBuffer<W> {
62 impl<W: Write> StdoutBuffer<W> {
58 pub fn new(writer: W) -> Self {
63 pub fn new(writer: W) -> Self {
59 let buf = io::BufWriter::new(writer);
64 let buf = io::BufWriter::new(writer);
60 Self { buf }
65 Self { buf }
61 }
66 }
62
67
63 /// Write bytes to stdout buffer
68 /// Write bytes to stdout buffer
64 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
69 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
65 self.buf.write_all(bytes).or_else(handle_stdout_error)
70 self.buf.write_all(bytes).or_else(handle_stdout_error)
66 }
71 }
67
72
68 /// Flush bytes to stdout
73 /// Flush bytes to stdout
69 pub fn flush(&mut self) -> Result<(), UiError> {
74 pub fn flush(&mut self) -> Result<(), UiError> {
70 self.buf.flush().or_else(handle_stdout_error)
75 self.buf.flush().or_else(handle_stdout_error)
71 }
76 }
72 }
77 }
73
78
74 /// Sometimes writing to stdout is not possible, try writing to stderr to
79 /// Sometimes writing to stdout is not possible, try writing to stderr to
75 /// signal that failure, otherwise just bail.
80 /// signal that failure, otherwise just bail.
76 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
81 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
77 if let ErrorKind::BrokenPipe = error.kind() {
82 if let ErrorKind::BrokenPipe = error.kind() {
78 // This makes `| head` work for example
83 // This makes `| head` work for example
79 return Ok(());
84 return Ok(());
80 }
85 }
81 let mut stderr = io::stderr();
86 let mut stderr = io::stderr();
82
87
83 stderr
88 stderr
84 .write_all(&[b"abort: ", error.to_string().as_bytes(), b"\n"].concat())
89 .write_all(&[b"abort: ", error.to_string().as_bytes(), b"\n"].concat())
85 .map_err(UiError::StderrError)?;
90 .map_err(UiError::StderrError)?;
86
91
87 stderr.flush().map_err(UiError::StderrError)?;
92 stderr.flush().map_err(UiError::StderrError)?;
88
93
89 Err(UiError::StdoutError(error))
94 Err(UiError::StdoutError(error))
90 }
95 }
91
96
92 /// Sometimes writing to stderr is not possible.
97 /// Sometimes writing to stderr is not possible.
93 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
98 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
94 // A broken pipe should not result in a error
99 // A broken pipe should not result in a error
95 // like with `| head` for example
100 // like with `| head` for example
96 if let ErrorKind::BrokenPipe = error.kind() {
101 if let ErrorKind::BrokenPipe = error.kind() {
97 return Ok(());
102 return Ok(());
98 }
103 }
99 Err(UiError::StdoutError(error))
104 Err(UiError::StdoutError(error))
100 }
105 }
@@ -1,64 +1,70 b''
1 #require rust
1 #require rust
2
2
3 Define an rhg function that will only run if rhg exists
3 Define an rhg function that will only run if rhg exists
4 $ rhg() {
4 $ rhg() {
5 > if [ -f "$RUNTESTDIR/../rust/target/debug/rhg" ]; then
5 > if [ -f "$RUNTESTDIR/../rust/target/debug/rhg" ]; then
6 > "$RUNTESTDIR/../rust/target/debug/rhg" "$@"
6 > "$RUNTESTDIR/../rust/target/debug/rhg" "$@"
7 > else
7 > else
8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
9 > exit 80
9 > exit 80
10 > fi
10 > fi
11 > }
11 > }
12
12
13 Unimplemented command
13 Unimplemented command
14 $ rhg unimplemented-command
14 $ rhg unimplemented-command
15 error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
16
17 USAGE:
18 rhg <SUBCOMMAND>
19
20 For more information try --help
15 [252]
21 [252]
16
22
17 Finding root
23 Finding root
18 $ rhg root
24 $ rhg root
19 abort: no repository found in '$TESTTMP' (.hg not found)!
25 abort: no repository found in '$TESTTMP' (.hg not found)!
20 [255]
26 [255]
21
27
22 $ hg init repository
28 $ hg init repository
23 $ cd repository
29 $ cd repository
24 $ rhg root
30 $ rhg root
25 $TESTTMP/repository
31 $TESTTMP/repository
26
32
27 Unwritable file descriptor
33 Unwritable file descriptor
28 $ rhg root > /dev/full
34 $ rhg root > /dev/full
29 abort: No space left on device (os error 28)
35 abort: No space left on device (os error 28)
30 [255]
36 [255]
31
37
32 Deleted repository
38 Deleted repository
33 $ rm -rf `pwd`
39 $ rm -rf `pwd`
34 $ rhg root
40 $ rhg root
35 abort: error getting current working directory: $ENOENT$
41 abort: error getting current working directory: $ENOENT$
36 [255]
42 [255]
37
43
38 Listing tracked files
44 Listing tracked files
39 $ cd $TESTTMP
45 $ cd $TESTTMP
40 $ hg init repository
46 $ hg init repository
41 $ cd repository
47 $ cd repository
42 $ for i in 1 2 3; do
48 $ for i in 1 2 3; do
43 > echo $i >> file$i
49 > echo $i >> file$i
44 > hg add file$i
50 > hg add file$i
45 > done
51 > done
46 > hg commit -m "commit $i" -q
52 > hg commit -m "commit $i" -q
47
53
48 Listing tracked files from root
54 Listing tracked files from root
49 $ rhg files
55 $ rhg files
50 file1
56 file1
51 file2
57 file2
52 file3
58 file3
53
59
54 Listing tracked files from subdirectory
60 Listing tracked files from subdirectory
55 $ mkdir -p path/to/directory
61 $ mkdir -p path/to/directory
56 $ cd path/to/directory
62 $ cd path/to/directory
57 $ rhg files
63 $ rhg files
58 ../../../file1
64 ../../../file1
59 ../../../file2
65 ../../../file2
60 ../../../file3
66 ../../../file3
61
67
62 Listing tracked files through broken pipe
68 Listing tracked files through broken pipe
63 $ rhg files | head -n 1
69 $ rhg files | head -n 1
64 ../../../file1
70 ../../../file1
General Comments 0
You need to be logged in to leave comments. Login now