##// 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 pub mod cat;
1 pub mod cat;
2 pub mod debugdata;
2 pub mod debugdata;
3 pub mod debugrequirements;
3 pub mod debugrequirements;
4 pub mod files;
4 pub mod files;
5 pub mod root;
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 use crate::error::CommandError;
1 use crate::error::CommandError;
3 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 use hg::config::Config;
4 use hg::config::Config;
5 use hg::operations::cat;
5 use hg::operations::cat;
6 use hg::repo::Repo;
6 use hg::repo::Repo;
7 use hg::utils::hg_path::HgPathBuf;
7 use hg::utils::hg_path::HgPathBuf;
8 use micro_timer::timed;
8 use micro_timer::timed;
9 use std::convert::TryFrom;
9 use std::convert::TryFrom;
10
10
11 pub const HELP_TEXT: &str = "
11 pub const HELP_TEXT: &str = "
12 Output the current or given revision of files
12 Output the current or given revision of files
13 ";
13 ";
14
14
15 pub struct CatCommand<'a> {
15 #[timed]
16 rev: Option<&'a str>,
16 pub fn run(
17 files: Vec<&'a str>,
17 ui: &Ui,
18 }
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> {
27 let repo = Repo::find(config)?;
21 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
28 let cwd = hg::utils::current_dir()?;
22 Self { rev, files }
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> {
43 match rev {
26 ui.write_stdout(data)?;
44 Some(rev) => {
27 Ok(())
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 use crate::error::CommandError;
1 use crate::error::CommandError;
3 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 use hg::config::Config;
4 use hg::config::Config;
5 use hg::operations::{debug_data, DebugDataKind};
5 use hg::operations::{debug_data, DebugDataKind};
6 use hg::repo::Repo;
6 use hg::repo::Repo;
7 use micro_timer::timed;
7 use micro_timer::timed;
8
8
9 pub const HELP_TEXT: &str = "
9 pub const HELP_TEXT: &str = "
10 Dump the contents of a data file revision
10 Dump the contents of a data file revision
11 ";
11 ";
12
12
13 pub struct DebugDataCommand<'a> {
13 #[timed]
14 rev: &'a str,
14 pub fn run(
15 kind: DebugDataKind,
15 ui: &Ui,
16 }
16 config: &Config,
17
17 args: &ArgMatches,
18 impl<'a> DebugDataCommand<'a> {
18 ) -> Result<(), CommandError> {
19 pub fn new(rev: &'a str, kind: DebugDataKind) -> Self {
19 let rev = args
20 DebugDataCommand { rev, kind }
20 .value_of("rev")
21 }
21 .expect("rev should be a required argument");
22 }
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> {
34 let repo = Repo::find(config)?;
25 #[timed]
35 let data = debug_data(&repo, rev, kind).map_err(|e| (e, rev))?;
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))?;
30
36
31 let mut stdout = ui.stdout_buffer();
37 let mut stdout = ui.stdout_buffer();
32 stdout.write_all(&data)?;
38 stdout.write_all(&data)?;
33 stdout.flush()?;
39 stdout.flush()?;
34
40
35 Ok(())
41 Ok(())
36 }
37 }
42 }
@@ -1,32 +1,26 b''
1 use crate::commands::Command;
2 use crate::error::CommandError;
1 use crate::error::CommandError;
3 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 use hg::config::Config;
4 use hg::config::Config;
5 use hg::repo::Repo;
5 use hg::repo::Repo;
6
6
7 pub const HELP_TEXT: &str = "
7 pub const HELP_TEXT: &str = "
8 Print the current repo requirements.
8 Print the current repo requirements.
9 ";
9 ";
10
10
11 pub struct DebugRequirementsCommand {}
11 pub fn run(
12
12 ui: &Ui,
13 impl DebugRequirementsCommand {
13 config: &Config,
14 pub fn new() -> Self {
14 _args: &ArgMatches,
15 DebugRequirementsCommand {}
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 use crate::error::CommandError;
1 use crate::error::CommandError;
3 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 use hg::config::Config;
4 use hg::config::Config;
5 use hg::operations::list_rev_tracked_files;
5 use hg::operations::list_rev_tracked_files;
6 use hg::operations::Dirstate;
6 use hg::operations::Dirstate;
7 use hg::repo::Repo;
7 use hg::repo::Repo;
8 use hg::utils::files::{get_bytes_from_path, relativize_path};
8 use hg::utils::files::{get_bytes_from_path, relativize_path};
9 use hg::utils::hg_path::{HgPath, HgPathBuf};
9 use hg::utils::hg_path::{HgPath, HgPathBuf};
10
10
11 pub const HELP_TEXT: &str = "
11 pub const HELP_TEXT: &str = "
12 List tracked files.
12 List tracked files.
13
13
14 Returns 0 on success.
14 Returns 0 on success.
15 ";
15 ";
16
16
17 pub struct FilesCommand<'a> {
17 pub fn run(
18 rev: Option<&'a str>,
18 ui: &Ui,
19 }
19 config: &Config,
20
20 args: &ArgMatches,
21 impl<'a> FilesCommand<'a> {
21 ) -> Result<(), CommandError> {
22 pub fn new(rev: Option<&'a str>) -> Self {
22 let rev = args.value_of("rev");
23 FilesCommand { rev }
24 }
25
23
26 fn display_files(
24 let repo = Repo::find(config)?;
27 &self,
25 if let Some(rev) = rev {
28 ui: &Ui,
26 let files =
29 repo: &Repo,
27 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
30 files: impl IntoIterator<Item = &'a HgPath>,
28 display_files(ui, &repo, files.iter())
31 ) -> Result<(), CommandError> {
29 } else {
32 let cwd = hg::utils::current_dir()?;
30 let distate = Dirstate::new(&repo)?;
33 let rooted_cwd = cwd
31 let files = distate.tracked_files()?;
34 .strip_prefix(repo.working_directory_path())
32 display_files(ui, &repo, files)
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(())
46 }
33 }
47 }
34 }
48
35
49 impl<'a> Command for FilesCommand<'a> {
36 fn display_files<'a>(
50 fn run(&self, ui: &Ui, config: &Config) -> Result<(), CommandError> {
37 ui: &Ui,
51 let repo = Repo::find(config)?;
38 repo: &Repo,
52 if let Some(rev) = self.rev {
39 files: impl IntoIterator<Item = &'a HgPath>,
53 let files =
40 ) -> Result<(), CommandError> {
54 list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
41 let cwd = hg::utils::current_dir()?;
55 self.display_files(ui, &repo, files.iter())
42 let rooted_cwd = cwd
56 } else {
43 .strip_prefix(repo.working_directory_path())
57 let distate = Dirstate::new(&repo)?;
44 .expect("cwd was already checked within the repository");
58 let files = distate.tracked_files()?;
45 let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
59 self.display_files(ui, &repo, files)
46
60 }
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 use crate::error::CommandError;
1 use crate::error::CommandError;
3 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::ArgMatches;
4 use format_bytes::format_bytes;
4 use format_bytes::format_bytes;
5 use hg::config::Config;
5 use hg::config::Config;
6 use hg::repo::Repo;
6 use hg::repo::Repo;
7 use hg::utils::files::get_bytes_from_path;
7 use hg::utils::files::get_bytes_from_path;
8
8
9 pub const HELP_TEXT: &str = "
9 pub const HELP_TEXT: &str = "
10 Print the root directory of the current repository.
10 Print the root directory of the current repository.
11
11
12 Returns 0 on success.
12 Returns 0 on success.
13 ";
13 ";
14
14
15 pub struct RootCommand {}
15 pub fn run(
16
16 ui: &Ui,
17 impl RootCommand {
17 config: &Config,
18 pub fn new() -> Self {
18 _args: &ArgMatches,
19 RootCommand {}
19 ) -> Result<(), CommandError> {
20 }
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 extern crate log;
1 extern crate log;
2 use clap::App;
2 use clap::App;
3 use clap::AppSettings;
3 use clap::AppSettings;
4 use clap::Arg;
4 use clap::Arg;
5 use clap::ArgGroup;
5 use clap::ArgGroup;
6 use clap::ArgMatches;
6 use clap::ArgMatches;
7 use clap::SubCommand;
7 use clap::SubCommand;
8 use format_bytes::format_bytes;
8 use format_bytes::format_bytes;
9 use hg::operations::DebugDataKind;
10 use std::convert::TryFrom;
11
9
12 mod commands;
10 mod commands;
13 mod error;
11 mod error;
14 mod exitcode;
12 mod exitcode;
15 mod ui;
13 mod ui;
16 use commands::Command;
17 use error::CommandError;
14 use error::CommandError;
18
15
19 fn main() {
16 fn main() {
20 env_logger::init();
17 env_logger::init();
21 let app = App::new("rhg")
18 let app = App::new("rhg")
22 .setting(AppSettings::AllowInvalidUtf8)
19 .setting(AppSettings::AllowInvalidUtf8)
23 .setting(AppSettings::SubcommandRequired)
20 .setting(AppSettings::SubcommandRequired)
24 .setting(AppSettings::VersionlessSubcommands)
21 .setting(AppSettings::VersionlessSubcommands)
25 .version("0.0.1")
22 .version("0.0.1")
26 .subcommand(
23 .subcommand(
27 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
24 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
28 )
25 )
29 .subcommand(
26 .subcommand(
30 SubCommand::with_name("files")
27 SubCommand::with_name("files")
31 .arg(
28 .arg(
32 Arg::with_name("rev")
29 Arg::with_name("rev")
33 .help("search the repository as it is in REV")
30 .help("search the repository as it is in REV")
34 .short("-r")
31 .short("-r")
35 .long("--revision")
32 .long("--revision")
36 .value_name("REV")
33 .value_name("REV")
37 .takes_value(true),
34 .takes_value(true),
38 )
35 )
39 .about(commands::files::HELP_TEXT),
36 .about(commands::files::HELP_TEXT),
40 )
37 )
41 .subcommand(
38 .subcommand(
42 SubCommand::with_name("cat")
39 SubCommand::with_name("cat")
43 .arg(
40 .arg(
44 Arg::with_name("rev")
41 Arg::with_name("rev")
45 .help("search the repository as it is in REV")
42 .help("search the repository as it is in REV")
46 .short("-r")
43 .short("-r")
47 .long("--revision")
44 .long("--revision")
48 .value_name("REV")
45 .value_name("REV")
49 .takes_value(true),
46 .takes_value(true),
50 )
47 )
51 .arg(
48 .arg(
52 clap::Arg::with_name("files")
49 clap::Arg::with_name("files")
53 .required(true)
50 .required(true)
54 .multiple(true)
51 .multiple(true)
55 .empty_values(false)
52 .empty_values(false)
56 .value_name("FILE")
53 .value_name("FILE")
57 .help("Activity to start: activity@category"),
54 .help("Activity to start: activity@category"),
58 )
55 )
59 .about(commands::cat::HELP_TEXT),
56 .about(commands::cat::HELP_TEXT),
60 )
57 )
61 .subcommand(
58 .subcommand(
62 SubCommand::with_name("debugdata")
59 SubCommand::with_name("debugdata")
63 .about(commands::debugdata::HELP_TEXT)
60 .about(commands::debugdata::HELP_TEXT)
64 .arg(
61 .arg(
65 Arg::with_name("changelog")
62 Arg::with_name("changelog")
66 .help("open changelog")
63 .help("open changelog")
67 .short("-c")
64 .short("-c")
68 .long("--changelog"),
65 .long("--changelog"),
69 )
66 )
70 .arg(
67 .arg(
71 Arg::with_name("manifest")
68 Arg::with_name("manifest")
72 .help("open manifest")
69 .help("open manifest")
73 .short("-m")
70 .short("-m")
74 .long("--manifest"),
71 .long("--manifest"),
75 )
72 )
76 .group(
73 .group(
77 ArgGroup::with_name("")
74 ArgGroup::with_name("")
78 .args(&["changelog", "manifest"])
75 .args(&["changelog", "manifest"])
79 .required(true),
76 .required(true),
80 )
77 )
81 .arg(
78 .arg(
82 Arg::with_name("rev")
79 Arg::with_name("rev")
83 .help("revision")
80 .help("revision")
84 .required(true)
81 .required(true)
85 .value_name("REV"),
82 .value_name("REV"),
86 ),
83 ),
87 )
84 )
88 .subcommand(
85 .subcommand(
89 SubCommand::with_name("debugrequirements")
86 SubCommand::with_name("debugrequirements")
90 .about(commands::debugrequirements::HELP_TEXT),
87 .about(commands::debugrequirements::HELP_TEXT),
91 );
88 );
92
89
93 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
90 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
94 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
91 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
95 std::process::exit(exitcode::UNIMPLEMENTED)
92 std::process::exit(exitcode::UNIMPLEMENTED)
96 });
93 });
97
94
98 let ui = ui::Ui::new();
95 let ui = ui::Ui::new();
99
96
100 let command_result = match_subcommand(matches, &ui);
97 let command_result = match_subcommand(matches, &ui);
101
98
102 let exit_code = match command_result {
99 let exit_code = match command_result {
103 Ok(_) => exitcode::OK,
100 Ok(_) => exitcode::OK,
104
101
105 // Exit with a specific code and no error message to let a potential
102 // Exit with a specific code and no error message to let a potential
106 // wrapper script fallback to Python-based Mercurial.
103 // wrapper script fallback to Python-based Mercurial.
107 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
104 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
108
105
109 Err(CommandError::Abort { message }) => {
106 Err(CommandError::Abort { message }) => {
110 if !message.is_empty() {
107 if !message.is_empty() {
111 // Ignore errors when writing to stderr, we’re already exiting
108 // Ignore errors when writing to stderr, we’re already exiting
112 // with failure code so there’s not much more we can do.
109 // with failure code so there’s not much more we can do.
113 let _ =
110 let _ =
114 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
111 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
115 }
112 }
116 exitcode::ABORT
113 exitcode::ABORT
117 }
114 }
118 };
115 };
119 std::process::exit(exit_code)
116 std::process::exit(exit_code)
120 }
117 }
121
118
122 fn match_subcommand(
119 fn match_subcommand(
123 matches: ArgMatches,
120 matches: ArgMatches,
124 ui: &ui::Ui,
121 ui: &ui::Ui,
125 ) -> Result<(), CommandError> {
122 ) -> Result<(), CommandError> {
126 let config = hg::config::Config::load()?;
123 let config = hg::config::Config::load()?;
127
124
128 match matches.subcommand() {
125 match matches.subcommand() {
129 ("root", _) => commands::root::RootCommand::new().run(&ui, &config),
126 ("root", Some(matches)) => commands::root::run(ui, &config, matches),
130 ("files", Some(matches)) => {
127 ("files", Some(matches)) => commands::files::run(ui, &config, matches),
131 commands::files::FilesCommand::try_from(matches)?.run(&ui, &config)
128 ("cat", Some(matches)) => commands::cat::run(ui, &config, matches),
132 }
129 ("debugdata", Some(matches)) => {
133 ("cat", Some(matches)) => {
130 commands::debugdata::run(ui, &config, matches)
134 commands::cat::CatCommand::try_from(matches)?.run(&ui, &config)
135 }
131 }
136 ("debugdata", Some(matches)) => {
132 ("debugrequirements", Some(matches)) => {
137 commands::debugdata::DebugDataCommand::try_from(matches)?
133 commands::debugrequirements::run(ui, &config, matches)
138 .run(&ui, &config)
139 }
140 ("debugrequirements", _) => {
141 commands::debugrequirements::DebugRequirementsCommand::new()
142 .run(&ui, &config)
143 }
134 }
144 _ => unreachable!(), // Because of AppSettings::SubcommandRequired,
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