##// END OF EJS Templates
rhg: add a limited `rhg cat -r` subcommand...
Antoine Cezar -
r46113:33ded2d3 default
parent child Browse files
Show More
@@ -0,0 +1,99 b''
1 use crate::commands::Command;
2 use crate::error::{CommandError, CommandErrorKind};
3 use crate::ui::utf8_to_local;
4 use crate::ui::Ui;
5 use hg::operations::FindRoot;
6 use hg::operations::{CatRev, CatRevError, CatRevErrorKind};
7 use hg::utils::hg_path::HgPathBuf;
8 use micro_timer::timed;
9 use std::convert::TryFrom;
10
11 pub const HELP_TEXT: &str = "
12 Output the current or given revision of files
13 ";
14
15 pub struct CatCommand<'a> {
16 rev: Option<&'a str>,
17 files: Vec<&'a str>,
18 }
19
20 impl<'a> CatCommand<'a> {
21 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
22 Self { rev, files }
23 }
24
25 fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> {
26 ui.write_stdout(data)?;
27 Ok(())
28 }
29 }
30
31 impl<'a> Command for CatCommand<'a> {
32 #[timed]
33 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
34 let root = FindRoot::new().run()?;
35 let cwd = std::env::current_dir()
36 .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?;
37
38 let mut files = vec![];
39 for file in self.files.iter() {
40 let normalized = cwd.join(&file);
41 let stripped = normalized
42 .strip_prefix(&root)
43 .map_err(|_| CommandErrorKind::Abort(None))?;
44 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
45 .map_err(|_| CommandErrorKind::Abort(None))?;
46 files.push(hg_file);
47 }
48
49 match self.rev {
50 Some(rev) => {
51 let mut operation = CatRev::new(&root, rev, &files)
52 .map_err(|e| map_rev_error(rev, e))?;
53 let data =
54 operation.run().map_err(|e| map_rev_error(rev, e))?;
55 self.display(ui, &data)
56 }
57 None => Err(CommandErrorKind::Unimplemented.into()),
58 }
59 }
60 }
61
62 /// Convert `CatRevErrorKind` to `CommandError`
63 fn map_rev_error(rev: &str, err: CatRevError) -> CommandError {
64 CommandError {
65 kind: match err.kind {
66 CatRevErrorKind::IoError(err) => CommandErrorKind::Abort(Some(
67 utf8_to_local(&format!("abort: {}\n", err)).into(),
68 )),
69 CatRevErrorKind::InvalidRevision => CommandErrorKind::Abort(Some(
70 utf8_to_local(&format!(
71 "abort: invalid revision identifier{}\n",
72 rev
73 ))
74 .into(),
75 )),
76 CatRevErrorKind::UnsuportedRevlogVersion(version) => {
77 CommandErrorKind::Abort(Some(
78 utf8_to_local(&format!(
79 "abort: unsupported revlog version {}\n",
80 version
81 ))
82 .into(),
83 ))
84 }
85 CatRevErrorKind::CorruptedRevlog => CommandErrorKind::Abort(Some(
86 "abort: corrupted revlog\n".into(),
87 )),
88 CatRevErrorKind::UnknowRevlogDataFormat(format) => {
89 CommandErrorKind::Abort(Some(
90 utf8_to_local(&format!(
91 "abort: unknow revlog dataformat {:?}\n",
92 format
93 ))
94 .into(),
95 ))
96 }
97 },
98 }
99 }
@@ -1,12 +1,13 b''
1 pub mod cat;
1 2 pub mod debugdata;
2 3 pub mod files;
3 4 pub mod root;
4 5 use crate::error::CommandError;
5 6 use crate::ui::Ui;
6 7
7 8 /// The common trait for rhg commands
8 9 ///
9 10 /// Normalize the interface of the commands provided by rhg
10 11 pub trait Command {
11 12 fn run(&self, ui: &Ui) -> Result<(), CommandError>;
12 13 }
@@ -1,110 +1,113 b''
1 1 use crate::exitcode;
2 2 use crate::ui::UiError;
3 3 use hg::operations::{FindRootError, FindRootErrorKind};
4 4 use hg::utils::files::get_bytes_from_path;
5 5 use std::convert::From;
6 6 use std::path::PathBuf;
7 7
8 8 /// The kind of command error
9 9 #[derive(Debug)]
10 10 pub enum CommandErrorKind {
11 11 /// The root of the repository cannot be found
12 12 RootNotFound(PathBuf),
13 13 /// The current directory cannot be found
14 14 CurrentDirNotFound(std::io::Error),
15 15 /// The standard output stream cannot be written to
16 16 StdoutError,
17 17 /// The standard error stream cannot be written to
18 18 StderrError,
19 19 /// The command aborted
20 20 Abort(Option<Vec<u8>>),
21 /// A mercurial capability as not been implemented.
22 Unimplemented,
21 23 }
22 24
23 25 impl CommandErrorKind {
24 26 pub fn get_exit_code(&self) -> exitcode::ExitCode {
25 27 match self {
26 28 CommandErrorKind::RootNotFound(_) => exitcode::ABORT,
27 29 CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT,
28 30 CommandErrorKind::StdoutError => exitcode::ABORT,
29 31 CommandErrorKind::StderrError => exitcode::ABORT,
30 32 CommandErrorKind::Abort(_) => exitcode::ABORT,
33 CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
31 34 }
32 35 }
33 36
34 37 /// Return the message corresponding to the error kind if any
35 38 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
36 39 match self {
37 40 // TODO use formating macro
38 41 CommandErrorKind::RootNotFound(path) => {
39 42 let bytes = get_bytes_from_path(path);
40 43 Some(
41 44 [
42 45 b"abort: no repository found in '",
43 46 bytes.as_slice(),
44 47 b"' (.hg not found)!\n",
45 48 ]
46 49 .concat(),
47 50 )
48 51 }
49 52 // TODO use formating macro
50 53 CommandErrorKind::CurrentDirNotFound(e) => Some(
51 54 [
52 55 b"abort: error getting current working directory: ",
53 56 e.to_string().as_bytes(),
54 57 b"\n",
55 58 ]
56 59 .concat(),
57 60 ),
58 61 CommandErrorKind::Abort(message) => message.to_owned(),
59 62 _ => None,
60 63 }
61 64 }
62 65 }
63 66
64 67 /// The error type for the Command trait
65 68 #[derive(Debug)]
66 69 pub struct CommandError {
67 70 pub kind: CommandErrorKind,
68 71 }
69 72
70 73 impl CommandError {
71 74 /// Exist the process with the corresponding exit code.
72 75 pub fn exit(&self) {
73 76 std::process::exit(self.kind.get_exit_code())
74 77 }
75 78
76 79 /// Return the message corresponding to the command error if any
77 80 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
78 81 self.kind.get_error_message_bytes()
79 82 }
80 83 }
81 84
82 85 impl From<CommandErrorKind> for CommandError {
83 86 fn from(kind: CommandErrorKind) -> Self {
84 87 CommandError { kind }
85 88 }
86 89 }
87 90
88 91 impl From<UiError> for CommandError {
89 92 fn from(error: UiError) -> Self {
90 93 CommandError {
91 94 kind: match error {
92 95 UiError::StdoutError(_) => CommandErrorKind::StdoutError,
93 96 UiError::StderrError(_) => CommandErrorKind::StderrError,
94 97 },
95 98 }
96 99 }
97 100 }
98 101
99 102 impl From<FindRootError> for CommandError {
100 103 fn from(err: FindRootError) -> Self {
101 104 match err.kind {
102 105 FindRootErrorKind::RootNotFound(path) => CommandError {
103 106 kind: CommandErrorKind::RootNotFound(path),
104 107 },
105 108 FindRootErrorKind::GetCurrentDirError(e) => CommandError {
106 109 kind: CommandErrorKind::CurrentDirNotFound(e),
107 110 },
108 111 }
109 112 }
110 113 }
@@ -1,141 +1,177 b''
1 1 extern crate log;
2 2 use clap::App;
3 3 use clap::AppSettings;
4 4 use clap::Arg;
5 5 use clap::ArgGroup;
6 6 use clap::ArgMatches;
7 7 use clap::SubCommand;
8 8 use hg::operations::DebugDataKind;
9 9 use std::convert::TryFrom;
10 10
11 11 mod commands;
12 12 mod error;
13 13 mod exitcode;
14 14 mod ui;
15 15 use commands::Command;
16 16 use error::CommandError;
17 17
18 18 fn main() {
19 19 env_logger::init();
20 20 let app = App::new("rhg")
21 21 .setting(AppSettings::AllowInvalidUtf8)
22 22 .setting(AppSettings::SubcommandRequired)
23 23 .setting(AppSettings::VersionlessSubcommands)
24 24 .version("0.0.1")
25 25 .subcommand(
26 26 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
27 27 )
28 28 .subcommand(
29 29 SubCommand::with_name("files")
30 30 .arg(
31 31 Arg::with_name("rev")
32 32 .help("search the repository as it is in REV")
33 33 .short("-r")
34 34 .long("--revision")
35 35 .value_name("REV")
36 36 .takes_value(true),
37 37 )
38 38 .about(commands::files::HELP_TEXT),
39 39 )
40 40 .subcommand(
41 SubCommand::with_name("cat")
42 .arg(
43 Arg::with_name("rev")
44 .help("search the repository as it is in REV")
45 .short("-r")
46 .long("--revision")
47 .value_name("REV")
48 .takes_value(true),
49 )
50 .arg(
51 clap::Arg::with_name("files")
52 .required(true)
53 .multiple(true)
54 .empty_values(false)
55 .value_name("FILE")
56 .help("Activity to start: activity@category"),
57 )
58 .about(commands::cat::HELP_TEXT),
59 )
60 .subcommand(
41 61 SubCommand::with_name("debugdata")
42 62 .about(commands::debugdata::HELP_TEXT)
43 63 .arg(
44 64 Arg::with_name("changelog")
45 65 .help("open changelog")
46 66 .short("-c")
47 67 .long("--changelog"),
48 68 )
49 69 .arg(
50 70 Arg::with_name("manifest")
51 71 .help("open manifest")
52 72 .short("-m")
53 73 .long("--manifest"),
54 74 )
55 75 .group(
56 76 ArgGroup::with_name("")
57 77 .args(&["changelog", "manifest"])
58 78 .required(true),
59 79 )
60 80 .arg(
61 81 Arg::with_name("rev")
62 82 .help("revision")
63 83 .required(true)
64 84 .value_name("REV"),
65 85 ),
66 86 );
67 87
68 88 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
69 89 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
70 90 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
71 91 });
72 92
73 93 let ui = ui::Ui::new();
74 94
75 95 let command_result = match_subcommand(matches, &ui);
76 96
77 97 match command_result {
78 98 Ok(_) => std::process::exit(exitcode::OK),
79 99 Err(e) => {
80 100 let message = e.get_error_message_bytes();
81 101 if let Some(msg) = message {
82 102 match ui.write_stderr(&msg) {
83 103 Ok(_) => (),
84 104 Err(_) => std::process::exit(exitcode::ABORT),
85 105 };
86 106 };
87 107 e.exit()
88 108 }
89 109 }
90 110 }
91 111
92 112 fn match_subcommand(
93 113 matches: ArgMatches,
94 114 ui: &ui::Ui,
95 115 ) -> Result<(), CommandError> {
96 116 match matches.subcommand() {
97 117 ("root", _) => commands::root::RootCommand::new().run(&ui),
98 118 ("files", Some(matches)) => {
99 119 commands::files::FilesCommand::try_from(matches)?.run(&ui)
100 120 }
121 ("cat", Some(matches)) => {
122 commands::cat::CatCommand::try_from(matches)?.run(&ui)
123 }
101 124 ("debugdata", Some(matches)) => {
102 125 commands::debugdata::DebugDataCommand::try_from(matches)?.run(&ui)
103 126 }
104 127 _ => unreachable!(), // Because of AppSettings::SubcommandRequired,
105 128 }
106 129 }
107 130
108 131 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::files::FilesCommand<'a> {
109 132 type Error = CommandError;
110 133
111 134 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
112 135 let rev = args.value_of("rev");
113 136 Ok(commands::files::FilesCommand::new(rev))
114 137 }
115 138 }
116 139
140 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::cat::CatCommand<'a> {
141 type Error = CommandError;
142
143 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
144 let rev = args.value_of("rev");
145 let files = match args.values_of("files") {
146 Some(files) => files.collect(),
147 None => vec![],
148 };
149 Ok(commands::cat::CatCommand::new(rev, files))
150 }
151 }
152
117 153 impl<'a> TryFrom<&'a ArgMatches<'_>>
118 154 for commands::debugdata::DebugDataCommand<'a>
119 155 {
120 156 type Error = CommandError;
121 157
122 158 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
123 159 let rev = args
124 160 .value_of("rev")
125 161 .expect("rev should be a required argument");
126 162 let kind = match (
127 163 args.is_present("changelog"),
128 164 args.is_present("manifest"),
129 165 ) {
130 166 (true, false) => DebugDataKind::Changelog,
131 167 (false, true) => DebugDataKind::Manifest,
132 168 (true, true) => {
133 169 unreachable!("Should not happen since options are exclusive")
134 170 }
135 171 (false, false) => {
136 172 unreachable!("Should not happen since options are required")
137 173 }
138 174 };
139 175 Ok(commands::debugdata::DebugDataCommand::new(rev, kind))
140 176 }
141 177 }
General Comments 0
You need to be logged in to leave comments. Login now