summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-04-24 20:10:19 +0200
committerJoel Klinghed <the_jk@spawned.biz>2024-04-24 20:10:19 +0200
commit1a0fde95fc2403041f9f477f4220618ee916853b (patch)
tree753a7bb6acb52210564e9a6d37d11dd0301a70b9
parentbb73ba4c05e6a509bc8097c285f382578bc9070c (diff)
args: Argument parser
Still getting used to rust
-rw-r--r--Cargo.lock122
-rw-r--r--Cargo.toml1
-rw-r--r--src/args.rs305
-rw-r--r--src/main.rs68
4 files changed, 494 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 17137b9..4fc0c50 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,5 +3,127 @@
version = 3
[[package]]
+name = "darling"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
+dependencies = [
+ "derive_builder_core",
+ "syn",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
name = "rrjvm"
version = "0.0.1"
+dependencies = [
+ "derive_builder",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "2.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
diff --git a/Cargo.toml b/Cargo.toml
index a10f1c0..955b96e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,3 +8,4 @@ description = "Red Rocket JVM"
license = "MIT"
[dependencies]
+derive_builder = "0.20.0"
diff --git a/src/args.rs b/src/args.rs
new file mode 100644
index 0000000..3cd9d8a
--- /dev/null
+++ b/src/args.rs
@@ -0,0 +1,305 @@
+use derive_builder::Builder;
+use std::collections::HashMap;
+
+#[allow(dead_code)]
+pub enum ValueRequirement {
+ None,
+ Required(&'static str),
+ Optional(&'static str),
+}
+
+#[derive(Builder)]
+#[builder(pattern = "owned")]
+pub struct Option {
+ #[builder(setter(into), default = "'\\0'")]
+ short: char,
+ long: &'static str,
+ #[builder(setter(into), default = "\"\"")]
+ description: &'static str,
+ #[builder(setter(name = "value"), default = "ValueRequirement::None")]
+ value_req: ValueRequirement,
+ #[builder(setter(skip))]
+ is_set: bool,
+ #[builder(setter(skip))]
+ value: std::option::Option<String>,
+}
+
+pub struct Options {
+ options: Vec<Option>,
+ short: HashMap<char, usize>,
+ long: HashMap<&'static str, usize>,
+}
+
+impl Option {
+ pub fn short(&self) -> char {
+ self.short
+ }
+
+ pub fn long(&self) -> &'static str {
+ self.long
+ }
+
+ pub fn description(&self) -> &'static str {
+ self.description
+ }
+
+ pub fn is_set(&self) -> bool {
+ self.is_set
+ }
+
+ pub fn value(&self) -> &std::option::Option<String> {
+ &self.value
+ }
+
+ fn set(&mut self, value: std::option::Option<String>) {
+ self.is_set = true;
+ self.value = value;
+ }
+}
+
+impl Options {
+ pub fn new() -> Self {
+ Options {
+ options: Vec::new(),
+ short: HashMap::new(),
+ long: HashMap::new(),
+ }
+ }
+
+ pub fn push(&mut self, option: Option) -> usize {
+ let index = self.options.len();
+ if option.short != '\0' {
+ self.short.insert(option.short, index);
+ }
+ if option.long != "" {
+ self.long.insert(option.long, index);
+ }
+ self.options.push(option);
+ index
+ }
+}
+
+impl std::ops::Index<usize> for Options {
+ type Output = Option;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ self.options.index(index)
+ }
+}
+
+pub struct Arguments {
+ pub program: std::option::Option<String>,
+ pub args: Vec<String>,
+}
+
+pub trait Parser {
+ fn run(&self, options: &mut Options, args: impl IntoIterator<Item=String>) -> Result<Arguments, String>;
+ fn print_help(&self, options: &Options);
+}
+
+#[allow(dead_code)]
+pub struct LongOnlyParser {
+}
+
+#[allow(dead_code)]
+pub struct ShortAndLongParser {
+}
+
+fn print_list(list: Vec<(String, &str)>) {
+ let mut left_len: usize = 0;
+ for (left, _) in &list {
+ left_len = std::cmp::max(left_len, left.len());
+ }
+ for (left, right) in &list {
+ println!("{left:<left_len$} {right}");
+ }
+}
+
+#[allow(dead_code)]
+impl LongOnlyParser {
+ pub fn new() -> LongOnlyParser {
+ LongOnlyParser { }
+ }
+}
+
+impl Parser for LongOnlyParser {
+ fn run(&self, options: &mut Options, args: impl IntoIterator<Item=String>) -> Result<Arguments, String> {
+ let mut ret = Vec::new();
+ let mut args_iter = args.into_iter();
+ let program = args_iter.next();
+ while let Some(arg) = args_iter.next() {
+ if arg.starts_with("-") {
+ if arg == "--" {
+ // All following arguments are just that.
+ while let Some(arg) = args_iter.next() {
+ ret.push(arg)
+ }
+ break
+ }
+ let start = 1;
+ let name;
+ let mut value;
+ if let Some(end) = arg.find('=') {
+ name = arg.get(start..end).unwrap();
+ value = Some(arg.get(end + 1..).unwrap().to_string());
+ } else {
+ name = arg.get(start..).unwrap();
+ value = None;
+ }
+ if let Some(index) = options.long.get(name) {
+ let ref mut option = options.options[*index];
+ match option.value_req {
+ ValueRequirement::None => {
+ if value.is_some() {
+ return Err(format!("option '{}' doesn't allow an argument", arg))
+ }
+ },
+ ValueRequirement::Required(_) => {
+ if value.is_none() {
+ value = args_iter.next();
+ if value.is_none() {
+ return Err(format!("option '{}' requires an argument", arg))
+ }
+ }
+ },
+ ValueRequirement::Optional(_) => {},
+ }
+ option.set(value);
+ } else {
+ return Err(format!("unrecognized option '{}'", arg))
+ }
+ } else {
+ ret.push(arg)
+ }
+ }
+ Ok(Arguments { program, args: ret })
+ }
+
+ fn print_help(&self, options: &Options) {
+ let mut lines = Vec::new();
+ for (_, index) in &options.long {
+ let ref option = options[*index];
+ let left = match option.value_req {
+ ValueRequirement::None => format!("-{}", option.long()),
+ ValueRequirement::Required(name) => format!("-{}={}", option.long(), name),
+ ValueRequirement::Optional(name) => format!("-{}[={}]", option.long(), name),
+ };
+ lines.push((left, option.description()));
+ }
+ print_list(lines);
+ }
+}
+
+#[allow(dead_code)]
+impl ShortAndLongParser {
+ pub fn new() -> ShortAndLongParser {
+ ShortAndLongParser { }
+ }
+}
+
+impl Parser for ShortAndLongParser {
+ fn run(&self, options: &mut Options, args: impl IntoIterator<Item=String>) -> Result<Arguments, String> {
+ let mut ret = Vec::new();
+ let mut args_iter = args.into_iter();
+ let program = args_iter.next();
+ while let Some(arg) = args_iter.next() {
+ if arg.starts_with("--") {
+ if arg.len() == 2 {
+ // All following arguments are just that.
+ while let Some(arg) = args_iter.next() {
+ ret.push(arg)
+ }
+ break
+ }
+ let start = 2;
+ let name;
+ let mut value;
+ if let Some(end) = arg.find('=') {
+ name = arg.get(start..end).unwrap();
+ value = Some(arg.get(end + 1..).unwrap().to_string());
+ } else {
+ name = arg.get(start..).unwrap();
+ value = None;
+ }
+ if let Some(index) = options.long.get(name) {
+ let ref mut option = options.options[*index];
+ match option.value_req {
+ ValueRequirement::None => {
+ if value.is_some() {
+ return Err(format!("option '{}' doesn't allow an argument", arg))
+ }
+ },
+ ValueRequirement::Required(_) => {
+ if value.is_none() {
+ value = args_iter.next();
+ if value.is_none() {
+ return Err(format!("option '{}' requires an argument", arg))
+ }
+ }
+ },
+ ValueRequirement::Optional(_) => {},
+ }
+ option.set(value);
+ } else {
+ return Err(format!("unrecognized option '{}'", arg))
+ }
+ } else if arg.starts_with("-") && arg.len() > 1 {
+ for c in arg.get(1..).unwrap().chars() {
+ if let Some(index) = options.short.get(&c) {
+ let ref mut option = options.options[*index];
+ let mut value = None;
+ match option.value_req {
+ ValueRequirement::None => {},
+ ValueRequirement::Required(_) => {
+ value = args_iter.next();
+ if value.is_none() {
+ return Err(format!("option requires an argument -- '{}", c))
+ }
+ },
+ ValueRequirement::Optional(_) => {},
+ }
+ option.set(value);
+ } else {
+ return Err(format!("invalid option -- '{}'", c))
+ }
+ }
+ } else {
+ ret.push(arg)
+ }
+ }
+ Ok(Arguments { program, args: ret })
+ }
+
+ fn print_help(&self, options: &Options) {
+ let mut lines = Vec::new();
+ for option in &options.options {
+ let left: String;
+ if option.short() == '\0' {
+ left = match option.value_req {
+ ValueRequirement::None => format!(" --{}", option.long()),
+ ValueRequirement::Required(name) =>
+ format!(" --{}={}", option.long(), name),
+ ValueRequirement::Optional(name) =>
+ format!(" --{}[={}]", option.long(), name),
+ };
+ } else if option.long() == "" {
+ left = match option.value_req {
+ ValueRequirement::None => format!("-{}", option.short()),
+ ValueRequirement::Required(name) => format!("-{}={}", option.short(), name),
+ ValueRequirement::Optional(name) =>
+ format!("-{}[={}]", option.short(), name),
+ };
+ } else {
+ left = match option.value_req {
+ ValueRequirement::None => format!("-{}, --{}", option.short(), option.long()),
+ ValueRequirement::Required(name) =>
+ format!("-{}, --{}={}", option.short(), option.long(), name),
+ ValueRequirement::Optional(name) =>
+ format!("-{}, --{}[={}]", option.short(), option.long(), name),
+ };
+ }
+ lines.push((left, option.description()));
+ }
+ print_list(lines);
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index e7a11a9..6b7c181 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,67 @@
-fn main() {
- println!("Hello, world!");
+mod args;
+
+use crate::args::Parser;
+use std::process::ExitCode;
+
+fn parse_args() -> Result<bool, String> {
+ let mut options = args::Options::new();
+ let help_idx = options.push(
+ args::OptionBuilder::default()
+ .short('h')
+ .long("help")
+ .description("display this and exit")
+ .build().unwrap());
+ let version_idx = options.push(
+ args::OptionBuilder::default()
+ .short('V')
+ .long("version")
+ .description("display version and exit")
+ .build().unwrap());
+ let verbose_idx = options.push(
+ args::OptionBuilder::default()
+ .long("verbose")
+ .description("be verbose")
+ .build().unwrap());
+ let test_idx = options.push(
+ args::OptionBuilder::default()
+ .short('t')
+ .long("test")
+ .description("testing")
+ .value(args::ValueRequirement::Required("FILE"))
+ .build().unwrap());
+
+ let parser = args::ShortAndLongParser::new();
+ let args = parser.run(&mut options, std::env::args())?;
+ let ref help = options[help_idx];
+ if help.is_set() {
+ parser.print_help(&options);
+ return Ok(true);
+ }
+ let ref version = options[version_idx];
+ if version.is_set() {
+ println!("Version is 0.0.1");
+ return Ok(true);
+ }
+ let ref verbose = options[verbose_idx];
+ let ref test = options[test_idx];
+ println!("verbose: {}", verbose.is_set());
+ println!("test: {:?}", test.value());
+ println!("args: {:?}", args.args);
+ Ok(false)
+}
+
+fn main() -> ExitCode {
+ match parse_args() {
+ Ok(exit) => {
+ if exit {
+ return ExitCode::SUCCESS;
+ }
+ }
+ Err(msg) => {
+ eprintln!("{}", msg);
+ return ExitCode::FAILURE;
+ }
+ }
+
+ ExitCode::SUCCESS
}