I'm new to rust, and this is my very first rust program.
I'd like to write a simple CLI app to help me running some Git command on multiple repositories. Since my goal is learning rust, I force myself not to use any dependency so that I have to implement the tools I need. I started writing a very small API for parsing command line arguments. My code builds and seems to work as expected on simple cases. I'd like to have some advice on those few lines: what's bad in this code, what should I change right now? The things I've struggled (just to make the code build):
Lifetime It looks like that when doing data structure composition, I have to propagate lifetime nearly everywhere as soon as I use references to avoid unnecessary copy. Am I right to worry about this, or should I just get use to this when coding with rust? Could it be better/simpler than what I did?
borrow checker In 'command_line_parse' I have two vectors as parameter.
- The first one is simply the command line arguments to analyse.
- The second one is a list defining each command (with argument) and options I'd like to handle.
The function return a new vector with references to the commands and options definition found in command line. A command may also have a parameter (next to the command) ; but if the next element after a command have been identified as another command or option, the command has no parameter.
In the last for of commandline.rs, I had to build and use a temporary vector 'index_match': Since I already have a mutable reference to iterate vector 'result', I wasn't able to search directly in 'result' for 'index_command_param' in a nested loop because of the borrow checker. Could I avoid building 'index_match' temporary vector to do this ?
main.rs
mod commandline;
use crate::commandline::*;
use std::env;
fn main() {
let syntax: Vec<ArgumentDefinition> = vec![
ArgumentDefinition {
short: Some("-l"),
long: Some("--list"),
kind: ArgumentKind::Option,
expect: None,
description: "list repositories.",
},
ArgumentDefinition {
short: Some("-p"),
long: Some("--pull"),
kind: ArgumentKind::Option,
expect: None,
description: "pull repositories branch.",
},
ArgumentDefinition {
short: Some("-s"),
long: Some("--switch"),
kind: ArgumentKind::Command,
expect: Some("branch"),
description: "switch repositories branch.",
},
];
let arguments: Vec<String> = env::args().collect();
let result = command_line_parse(&arguments, &syntax);
dbg!(result);
}
commandline.rs
pub type ArgumentIndex = usize;
#[derive(Debug)]
#[allow(dead_code)]
pub enum ArgumentKind {
Command,
Option,
}
#[derive(Debug)]
pub struct ArgumentDefinition<'a> {
pub short: Option<&'a str>,
pub long: Option<&'a str>,
pub kind: ArgumentKind,
pub expect: Option<&'a str>,
pub description: &'a str,
}
#[derive(Debug)]
pub struct ArgumentValue<'a> {
pub index: ArgumentIndex,
pub parameter: Option<&'a str>,
}
#[derive(Debug)]
pub struct ArgumentParseResult<'a> {
pub result: ArgumentValue<'a>,
pub argument: &'a ArgumentDefinition<'a>,
}
fn option_seek(
arguments: &Vec<String>,
pattern_short: Option<&str>,
pattern_long: Option<&str>,
) -> Option<usize> {
let closure_argument_search = |arguments: &Vec<String>, patern: Option<&str>| -> Option<usize> {
if let Some(patern_value) = patern {
const SHIFT: usize = 1;
for (current_index, current_value) in arguments[SHIFT..].iter().enumerate() {
if current_value.eq(patern_value) {
return Some(current_index + SHIFT);
}
}
}
None
};
if let Some(index) = closure_argument_search(&arguments, pattern_short) {
return Some(index);
}
if let Some(index) = closure_argument_search(&arguments, pattern_long) {
return Some(index);
}
None
}
pub fn command_line_parse<'a>(
arguments: &'a Vec<String>,
definition: &'a Vec<ArgumentDefinition>,
) -> Vec<ArgumentParseResult<'a>> {
let mut result: Vec<ArgumentParseResult> = Vec::new();
let mut index_match: Vec<ArgumentIndex> = Vec::new();
for argument_definition in definition {
if let Some(index) = option_seek(
arguments,
argument_definition.short,
argument_definition.long,
) {
index_match.push(index);
let argument_value = ArgumentValue {
index: index,
parameter: None,
};
let argument_parse_result = ArgumentParseResult {
result: argument_value,
argument: &*argument_definition,
};
result.push(argument_parse_result);
}
}
for result_element in &mut result {
match result_element.argument.kind {
ArgumentKind::Command => {
let index_command_param = result_element.result.index + 1;
if index_match
.iter()
.position(|&r| r == index_command_param)
.is_none()
{
result_element.result.parameter = Some(&arguments[index_command_param])
}
}
ArgumentKind::Option => {}
}
}
return result;
}