diff options
author | Akshay <[email protected]> | 2021-03-31 12:20:28 +0100 |
---|---|---|
committer | Akshay <[email protected]> | 2021-03-31 12:20:28 +0100 |
commit | c5c43ade4bfb1c574c2ba3bfad94d0e968db7d44 (patch) | |
tree | 1f65be43c6574e89bf00f5f4732e58435025ba67 | |
parent | 431fd02329e2cd663db0acda5b229bbe632c5338 (diff) |
begin work on command line interface
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/cli.rs | 128 |
3 files changed, 136 insertions, 0 deletions
@@ -127,6 +127,12 @@ dependencies = [ | |||
127 | ] | 127 | ] |
128 | 128 | ||
129 | [[package]] | 129 | [[package]] |
130 | name = "pico-args" | ||
131 | version = "0.4.0" | ||
132 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
133 | checksum = "d70072c20945e1ab871c472a285fc772aefd4f5407723c206242f2c6f94595d6" | ||
134 | |||
135 | [[package]] | ||
130 | name = "radium" | 136 | name = "radium" |
131 | version = "0.6.2" | 137 | version = "0.6.2" |
132 | source = "registry+https://github.com/rust-lang/crates.io-index" | 138 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -156,6 +162,7 @@ dependencies = [ | |||
156 | "env_logger", | 162 | "env_logger", |
157 | "log", | 163 | "log", |
158 | "obi", | 164 | "obi", |
165 | "pico-args", | ||
159 | "sdl2", | 166 | "sdl2", |
160 | ] | 167 | ] |
161 | 168 | ||
@@ -11,3 +11,4 @@ sdl2 = {version = "0.34", features = ["ttf"]} | |||
11 | obi = { git = "https://github.com/nerdypepper/obi" } | 11 | obi = { git = "https://github.com/nerdypepper/obi" } |
12 | env_logger = "0.8.3" | 12 | env_logger = "0.8.3" |
13 | log = "0.4.0" | 13 | log = "0.4.0" |
14 | pico-args = "0.4.0" | ||
diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..1dbd098 --- /dev/null +++ b/src/cli.rs | |||
@@ -0,0 +1,128 @@ | |||
1 | use std::{ | ||
2 | default::Default, | ||
3 | fmt, | ||
4 | path::{Path, PathBuf}, | ||
5 | }; | ||
6 | |||
7 | pub static HELP_TEXT: &'static str = " | ||
8 | Usage | ||
9 | ----- | ||
10 | |||
11 | slate new <FILE> -d WIDTHxHEIGHT | ||
12 | |||
13 | Options | ||
14 | ------- | ||
15 | |||
16 | -h, --help Prints help information | ||
17 | -d, Specify dimensions for new file (default: 200x200) | ||
18 | "; | ||
19 | |||
20 | #[derive(Debug)] | ||
21 | pub enum CliError { | ||
22 | NoDimensions, | ||
23 | FileAlreadyExists, | ||
24 | FileDoesNotExist, | ||
25 | DimensionParseError, | ||
26 | NewRequiresFile, | ||
27 | InvalidSubCommand(String), | ||
28 | SubCommandParseError, | ||
29 | } | ||
30 | |||
31 | impl fmt::Display for CliError { | ||
32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
33 | match self { | ||
34 | Self::NoDimensions => write!(f, "no dimensions provided"), | ||
35 | Self::FileAlreadyExists => { | ||
36 | write!(f, "cannot initialize project, file already exists") | ||
37 | } | ||
38 | Self::FileDoesNotExist => { | ||
39 | write!(f, "cannot open existing project, file does not exist") | ||
40 | } | ||
41 | Self::DimensionParseError => write!(f, "invalid dimension string"), | ||
42 | Self::NewRequiresFile => write!(f, "new subcommand requires file name"), | ||
43 | Self::InvalidSubCommand(s) => write!(f, "invalid subcommand `{}`", s), | ||
44 | Self::SubCommandParseError => write!(f, "error parsing subcommand"), | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | impl std::error::Error for CliError {} | ||
50 | |||
51 | #[derive(Debug)] | ||
52 | pub enum Config { | ||
53 | NewProject { | ||
54 | file_name: Option<PathBuf>, | ||
55 | dimensions: (u32, u32), | ||
56 | }, | ||
57 | ExistingProject { | ||
58 | file_name: PathBuf, | ||
59 | }, | ||
60 | Help, | ||
61 | } | ||
62 | |||
63 | impl Default for Config { | ||
64 | fn default() -> Self { | ||
65 | Self::NewProject { | ||
66 | file_name: None, | ||
67 | dimensions: (200, 200), | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | pub fn parse_args() -> Result<Config, CliError> { | ||
73 | let mut args = pico_args::Arguments::from_env(); | ||
74 | |||
75 | if args.contains(["-h", "--help"]) { | ||
76 | return Ok(Config::Help); | ||
77 | } | ||
78 | |||
79 | match args.subcommand() { | ||
80 | Ok(cmd) => match cmd.as_deref() { | ||
81 | Some("new") => new_project(&mut args), | ||
82 | Some(s) => { | ||
83 | if !Path::new(s).is_file() { | ||
84 | return Err(CliError::FileDoesNotExist); | ||
85 | } | ||
86 | return Ok(Config::ExistingProject { | ||
87 | file_name: PathBuf::from(s), | ||
88 | }); | ||
89 | } | ||
90 | None => return Ok(Config::Help), | ||
91 | }, | ||
92 | _ => Err(CliError::SubCommandParseError), | ||
93 | } | ||
94 | } | ||
95 | |||
96 | fn new_project(args: &mut pico_args::Arguments) -> Result<Config, CliError> { | ||
97 | let file_name = args.free_from_str(); | ||
98 | if let Ok(f) = &file_name { | ||
99 | if Path::new(&f).is_file() { | ||
100 | return Err(CliError::FileAlreadyExists); | ||
101 | } | ||
102 | } | ||
103 | let dimensions = args | ||
104 | .opt_value_from_fn(["-d", "--dimensions"], parse_dimensions) | ||
105 | .map_err(|_| CliError::DimensionParseError)? | ||
106 | .unwrap_or((200, 200)); | ||
107 | return Ok(Config::NewProject { | ||
108 | file_name: file_name.ok(), | ||
109 | dimensions, | ||
110 | }); | ||
111 | } | ||
112 | |||
113 | fn parse_dimensions(input: &str) -> Result<(u32, u32), CliError> { | ||
114 | let dimensions: Vec<&str> = input.split('x').collect(); | ||
115 | match &dimensions[..] { | ||
116 | [width, height] => { | ||
117 | return Ok(( | ||
118 | width | ||
119 | .parse::<u32>() | ||
120 | .map_err(|_| CliError::DimensionParseError)?, | ||
121 | height | ||
122 | .parse::<u32>() | ||
123 | .map_err(|_| CliError::DimensionParseError)?, | ||
124 | )) | ||
125 | } | ||
126 | _ => return Err(CliError::DimensionParseError), | ||
127 | } | ||
128 | } | ||