##// END OF EJS Templates
rhg: replace command structs with functions...
Simon Sapin -
r47250:184e4655 default
parent child Browse files
Show More
@@ -1,15 +1,5 b''
1 1 pub mod cat;
2 2 pub mod debugdata;
3 3 pub mod debugrequirements;
4 4 pub mod files;
5 5 pub mod root;
6 use crate::error::CommandError;
7 use crate::ui::Ui;
8 use hg::config::Config;
9
10 /// The common trait for rhg commands
11 ///
12 /// Normalize the interface of the commands provided by rhg
13 pub trait Command {
14 fn run(&self, ui: &Ui, config: &Config) -> Result<(), CommandError>;
15 }
@@ -1,58 +1,51 b''
1 use crate::commands::Command;
2 1 use crate::error::CommandError;
3 2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 4 use hg::config::Config;
5 5 use hg::operations::cat;
6 6 use hg::repo::Repo;
7 7 use hg::utils::hg_path::HgPathBuf;
8 8 use micro_timer::timed;
9 9 use std::convert::TryFrom;
10 10
11 11 pub const HELP_TEXT: &str = "
12 12 Output the current or given revision of files
13 13 ";
14 14
15 pub struct CatCommand<'a> {
16 rev: Option<&'a str>,
17 files: Vec<&'a str>,
18 }
15 #[timed]
16 pub fn run(
17 ui: &Ui,
18 config: &Config,
19 args: &ArgMatches,
20 ) -> Result<(), CommandError> {
21 let rev = args.value_of("rev");
22 let file_args = match args.values_of("files") {
23 Some(files) => files.collect(),
24 None => vec![],
25 };
19 26
20 impl<'a> CatCommand<'a> {
21 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
22 Self { rev, files }
27 let repo = Repo::find(config)?;
28 let cwd = hg::utils::current_dir()?;
29
30 let mut files = vec![];
31 for file in file_args.iter() {
32 // TODO: actually normalize `..` path segments etc?
33 let normalized = cwd.join(&file);
34 let stripped = normalized
35 .strip_prefix(&repo.working_directory_path())
36 // TODO: error message for path arguments outside of the repo
37 .map_err(|_| CommandError::abort(""))?;
38 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
39 .map_err(|e| CommandError::abort(e.to_string()))?;
40 files.push(hg_file);
23 41 }
24 42
25 fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> {
26 ui.write_stdout(data)?;
27 Ok(())
43 match rev {
44 Some(rev) => {
45 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
46 ui.write_stdout(&data)?;
47 Ok(())
48 }
49 None => Err(CommandError::Unimplemented.into()),
28 50 }
29 51 }
30
31 impl<'a> Command for CatCommand<'a> {
32 #[timed]
33 fn run(&self, ui: &Ui, config: &Config) -> Result<(), CommandError> {
34 let repo = Repo::find(config)?;
35 let cwd = hg::utils::current_dir()?;
36
37 let mut files = vec![];
38 for file in self.files.iter() {
39 // TODO: actually normalize `..` path segments etc?
40 let normalized = cwd.join(&file);
41 let stripped = normalized
42 .strip_prefix(&repo.working_directory_path())
43 // TODO: error message for path arguments outside of the repo
44 .map_err(|_| CommandError::abort(""))?;
45 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
46 .map_err(|e| CommandError::abort(e.to_string()))?;
47 files.push(hg_file);
48 }
49
50 match self.rev {
51 Some(rev) => {
52 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
53 self.display(ui, &data)
54 }
55 None => Err(CommandError::Unimplemented.into()),
56 }
57 }
58 }
@@ -1,37 +1,42 b''
1 use crate::commands::Command;
2 1 use crate::error::CommandError;
3 2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 4 use hg::config::Config;
5 5 use hg::operations::{debug_data, DebugDataKind};
6 6 use hg::repo::Repo;
7 7 use micro_timer::timed;
8 8
9 9 pub const HELP_TEXT: &str = "
10 10 Dump the contents of a data file revision
11 11 ";
12 12
13 pub struct DebugDataCommand<'a> {
14 rev: &'a str,
15 kind: DebugDataKind,
16 }
17
18 impl<'a> DebugDataCommand<'a> {
19 pub fn new(rev: &'a str, kind: DebugDataKind) -> Self {
20 DebugDataCommand { rev, kind }
21 }
22 }
13 #[timed]
14 pub fn run(
15 ui: &Ui,
16 config: &Config,
17 args: &ArgMatches,
18 ) -> Result<(), CommandError> {
19 let rev = args
20 .value_of("rev")
21 .expect("rev should be a required argument");
22 let kind =
23 match (args.is_present("changelog"), args.is_present("manifest")) {
24 (true, false) => DebugDataKind::Changelog,
25 (false, true) => DebugDataKind::Manifest,
26 (true, true) => {
27 unreachable!("Should not happen since options are exclusive")
28 }
29 (false, false) => {
30 unreachable!("Should not happen since options are required")
31 }
32 };
23 33
24 impl<'a> Command for DebugDataCommand<'a> {
25 #[timed]
26 fn run(&self, ui: &Ui, config: &Config) -> Result<(), CommandError> {
27 let repo = Repo::find(config)?;
28 let data = debug_data(&repo, self.rev, self.kind)
29 .map_err(|e| (e, self.rev))?;
34 let repo = Repo::find(config)?;
35 let data = debug_data(&repo, rev, kind).map_err(|e| (e, rev))?;
30 36
31 let mut stdout = ui.stdout_buffer();
32 stdout.write_all(&data)?;
33 stdout.flush()?;
37 let mut stdout = ui.stdout_buffer();
38 stdout.write_all(&data)?;
39 stdout.flush()?;
34 40
35 Ok(())
36 }
41 Ok(())
37 42 }
@@ -1,32 +1,26 b''
1 use crate::commands::Command;
2 1 use crate::error::CommandError;
3 2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 4 use hg::config::Config;
5 5 use hg::repo::Repo;
6 6
7 7 pub const HELP_TEXT: &str = "
8 8 Print the current repo requirements.
9 9 ";
10 10
11 pub struct DebugRequirementsCommand {}
12
13 impl DebugRequirementsCommand {
14 pub fn new() -> Self {
15 DebugRequirementsCommand {}
11 pub fn run(
12 ui: &Ui,
13 config: &Config,
14 _args: &ArgMatches,
15 ) -> Result<(), CommandError> {
16 let repo = Repo::find(config)?;
17 let mut output = String::new();
18 let mut requirements: Vec<_> = repo.requirements().iter().collect();
19 requirements.sort();
20 for req in requirements {
21 output.push_str(req);
22 output.push('\n');
16 23 }
24 ui.write_stdout(output.as_bytes())?;
25 Ok(())
17 26 }
18
19 impl Command for DebugRequirementsCommand {
20 fn run(&self, ui: &Ui, config: &Config) -> Result<(), CommandError> {
21 let repo = Repo::find(config)?;
22 let mut output = String::new();
23 let mut requirements: Vec<_> = repo.requirements().iter().collect();
24 requirements.sort();
25 for req in requirements {
26 output.push_str(req);
27 output.push('\n');
28 }
29 ui.write_stdout(output.as_bytes())?;
30 Ok(())
31 }
32 }
@@ -1,62 +1,55 b''
1 use crate::commands::Command;
2 1 use crate::error::CommandError;
3 2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 4 use hg::config::Config;
5 5 use hg::operations::list_rev_tracked_files;
6 6 use hg::operations::Dirstate;
7 7 use hg::repo::Repo;
8 8 use hg::utils::files::{get_bytes_from_path, relativize_path};
9 9 use hg::utils::hg_path::{HgPath, HgPathBuf};
10 10
11 11 pub const HELP_TEXT: &str = "
12 12 List tracked files.
13 13
14 14 Returns 0 on success.
15 15 ";
16 16
17 pub struct FilesCommand<'a> {
18 rev: Option<&'a str>,
19 }
20
21 impl<'a> FilesCommand<'a> {
22 pub fn new(rev: Option<&'a str>) -> Self {
23 FilesCommand { rev }
24 }
17 pub fn run(
18 ui: &Ui,
19 config: &Config,
20 args: &ArgMatches,
21 ) -> Result<(), CommandError> {
22 let rev = args.value_of("rev");
25 23
26 fn display_files(
27 &self,
28 ui: &Ui,
29 repo: &Repo,
30 files: impl IntoIterator<Item = &'a HgPath>,
31 ) -> Result<(), CommandError> {
32 let cwd = hg::utils::current_dir()?;
33 let rooted_cwd = cwd
34 .strip_prefix(repo.working_directory_path())
35 .expect("cwd was already checked within the repository");
36 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
37
38 let mut stdout = ui.stdout_buffer();
39
40 for file in files {
41 stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
42 stdout.write_all(b"\n")?;
43 }
44 stdout.flush()?;
45 Ok(())
24 let repo = Repo::find(config)?;
25 if let Some(rev) = rev {
26 let files =
27 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
28 display_files(ui, &repo, files.iter())
29 } else {
30 let distate = Dirstate::new(&repo)?;
31 let files = distate.tracked_files()?;
32 display_files(ui, &repo, files)
46 33 }
47 34 }
48 35
49 impl<'a> Command for FilesCommand<'a> {
50 fn run(&self, ui: &Ui, config: &Config) -> Result<(), CommandError> {
51 let repo = Repo::find(config)?;
52 if let Some(rev) = self.rev {
53 let files =
54 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
55 self.display_files(ui, &repo, files.iter())
56 } else {
57 let distate = Dirstate::new(&repo)?;
58 let files = distate.tracked_files()?;
59 self.display_files(ui, &repo, files)
60 }
36 fn display_files<'a>(
37 ui: &Ui,
38 repo: &Repo,
39 files: impl IntoIterator<Item = &'a HgPath>,
40 ) -> Result<(), CommandError> {
41 let cwd = hg::utils::current_dir()?;
42 let rooted_cwd = cwd
43 .strip_prefix(repo.working_directory_path())
44 .expect("cwd was already checked within the repository");
45 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
46
47 let mut stdout = ui.stdout_buffer();
48
49 for file in files {
50 stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
51 stdout.write_all(b"\n")?;
61 52 }
53 stdout.flush()?;
54 Ok(())
62 55 }
@@ -1,30 +1,24 b''
1 use crate::commands::Command;
2 1 use crate::error::CommandError;
3 2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 4 use format_bytes::format_bytes;
5 5 use hg::config::Config;
6 6 use hg::repo::Repo;
7 7 use hg::utils::files::get_bytes_from_path;
8 8
9 9 pub const HELP_TEXT: &str = "
10 10 Print the root directory of the current repository.
11 11
12 12 Returns 0 on success.
13 13 ";
14 14
15 pub struct RootCommand {}
16
17 impl RootCommand {
18 pub fn new() -> Self {
19 RootCommand {}
20 }
15 pub fn run(
16 ui: &Ui,
17 config: &Config,
18 _args: &ArgMatches,
19 ) -> Result<(), CommandError> {
20 let repo = Repo::find(config)?;
21 let bytes = get_bytes_from_path(repo.working_directory_path());
22 ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?;
23 Ok(())
21 24 }
22
23 impl Command for RootCommand {
24 fn run(&self, ui: &Ui, config: &Config) -> Result<(), CommandError> {
25 let repo = Repo::find(config)?;
26 let bytes = get_bytes_from_path(repo.working_directory_path());
27 ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?;
28 Ok(())
29 }
30 }
@@ -1,194 +1,137 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 format_bytes::format_bytes;
9 use hg::operations::DebugDataKind;
10 use std::convert::TryFrom;
11 9
12 10 mod commands;
13 11 mod error;
14 12 mod exitcode;
15 13 mod ui;
16 use commands::Command;
17 14 use error::CommandError;
18 15
19 16 fn main() {
20 17 env_logger::init();
21 18 let app = App::new("rhg")
22 19 .setting(AppSettings::AllowInvalidUtf8)
23 20 .setting(AppSettings::SubcommandRequired)
24 21 .setting(AppSettings::VersionlessSubcommands)
25 22 .version("0.0.1")
26 23 .subcommand(
27 24 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
28 25 )
29 26 .subcommand(
30 27 SubCommand::with_name("files")
31 28 .arg(
32 29 Arg::with_name("rev")
33 30 .help("search the repository as it is in REV")
34 31 .short("-r")
35 32 .long("--revision")
36 33 .value_name("REV")
37 34 .takes_value(true),
38 35 )
39 36 .about(commands::files::HELP_TEXT),
40 37 )
41 38 .subcommand(
42 39 SubCommand::with_name("cat")
43 40 .arg(
44 41 Arg::with_name("rev")
45 42 .help("search the repository as it is in REV")
46 43 .short("-r")
47 44 .long("--revision")
48 45 .value_name("REV")
49 46 .takes_value(true),
50 47 )
51 48 .arg(
52 49 clap::Arg::with_name("files")
53 50 .required(true)
54 51 .multiple(true)
55 52 .empty_values(false)
56 53 .value_name("FILE")
57 54 .help("Activity to start: activity@category"),
58 55 )
59 56 .about(commands::cat::HELP_TEXT),
60 57 )
61 58 .subcommand(
62 59 SubCommand::with_name("debugdata")
63 60 .about(commands::debugdata::HELP_TEXT)
64 61 .arg(
65 62 Arg::with_name("changelog")
66 63 .help("open changelog")
67 64 .short("-c")
68 65 .long("--changelog"),
69 66 )
70 67 .arg(
71 68 Arg::with_name("manifest")
72 69 .help("open manifest")
73 70 .short("-m")
74 71 .long("--manifest"),
75 72 )
76 73 .group(
77 74 ArgGroup::with_name("")
78 75 .args(&["changelog", "manifest"])
79 76 .required(true),
80 77 )
81 78 .arg(
82 79 Arg::with_name("rev")
83 80 .help("revision")
84 81 .required(true)
85 82 .value_name("REV"),
86 83 ),
87 84 )
88 85 .subcommand(
89 86 SubCommand::with_name("debugrequirements")
90 87 .about(commands::debugrequirements::HELP_TEXT),
91 88 );
92 89
93 90 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
94 91 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
95 92 std::process::exit(exitcode::UNIMPLEMENTED)
96 93 });
97 94
98 95 let ui = ui::Ui::new();
99 96
100 97 let command_result = match_subcommand(matches, &ui);
101 98
102 99 let exit_code = match command_result {
103 100 Ok(_) => exitcode::OK,
104 101
105 102 // Exit with a specific code and no error message to let a potential
106 103 // wrapper script fallback to Python-based Mercurial.
107 104 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
108 105
109 106 Err(CommandError::Abort { message }) => {
110 107 if !message.is_empty() {
111 108 // Ignore errors when writing to stderr, we’re already exiting
112 109 // with failure code so there’s not much more we can do.
113 110 let _ =
114 111 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
115 112 }
116 113 exitcode::ABORT
117 114 }
118 115 };
119 116 std::process::exit(exit_code)
120 117 }
121 118
122 119 fn match_subcommand(
123 120 matches: ArgMatches,
124 121 ui: &ui::Ui,
125 122 ) -> Result<(), CommandError> {
126 123 let config = hg::config::Config::load()?;
127 124
128 125 match matches.subcommand() {
129 ("root", _) => commands::root::RootCommand::new().run(&ui, &config),
130 ("files", Some(matches)) => {
131 commands::files::FilesCommand::try_from(matches)?.run(&ui, &config)
132 }
133 ("cat", Some(matches)) => {
134 commands::cat::CatCommand::try_from(matches)?.run(&ui, &config)
126 ("root", Some(matches)) => commands::root::run(ui, &config, matches),
127 ("files", Some(matches)) => commands::files::run(ui, &config, matches),
128 ("cat", Some(matches)) => commands::cat::run(ui, &config, matches),
129 ("debugdata", Some(matches)) => {
130 commands::debugdata::run(ui, &config, matches)
135 131 }
136 ("debugdata", Some(matches)) => {
137 commands::debugdata::DebugDataCommand::try_from(matches)?
138 .run(&ui, &config)
139 }
140 ("debugrequirements", _) => {
141 commands::debugrequirements::DebugRequirementsCommand::new()
142 .run(&ui, &config)
132 ("debugrequirements", Some(matches)) => {
133 commands::debugrequirements::run(ui, &config, matches)
143 134 }
144 135 _ => unreachable!(), // Because of AppSettings::SubcommandRequired,
145 136 }
146 137 }
147
148 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::files::FilesCommand<'a> {
149 type Error = CommandError;
150
151 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
152 let rev = args.value_of("rev");
153 Ok(commands::files::FilesCommand::new(rev))
154 }
155 }
156
157 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::cat::CatCommand<'a> {
158 type Error = CommandError;
159
160 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
161 let rev = args.value_of("rev");
162 let files = match args.values_of("files") {
163 Some(files) => files.collect(),
164 None => vec![],
165 };
166 Ok(commands::cat::CatCommand::new(rev, files))
167 }
168 }
169
170 impl<'a> TryFrom<&'a ArgMatches<'_>>
171 for commands::debugdata::DebugDataCommand<'a>
172 {
173 type Error = CommandError;
174
175 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
176 let rev = args
177 .value_of("rev")
178 .expect("rev should be a required argument");
179 let kind = match (
180 args.is_present("changelog"),
181 args.is_present("manifest"),
182 ) {
183 (true, false) => DebugDataKind::Changelog,
184 (false, true) => DebugDataKind::Manifest,
185 (true, true) => {
186 unreachable!("Should not happen since options are exclusive")
187 }
188 (false, false) => {
189 unreachable!("Should not happen since options are required")
190 }
191 };
192 Ok(commands::debugdata::DebugDataCommand::new(rev, kind))
193 }
194 }
General Comments 0
You need to be logged in to leave comments. Login now