Add basic command line boilerplate and arg parsing
This commit is contained in:
commit
2d711d2dfa
|
@ -0,0 +1 @@
|
||||||
|
/target
|
|
@ -0,0 +1,131 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.131"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.131"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.8.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shepherd"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "shepherd"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_yaml = "0.8"
|
||||||
|
toml = "0.5"
|
|
@ -0,0 +1,55 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
/// The internal representation of the configuration file.
|
||||||
|
pub struct Config {
|
||||||
|
pub source_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Create new Config struct with default values
|
||||||
|
///
|
||||||
|
/// The `source_dir` field defaults to `$HOME/sources`
|
||||||
|
pub fn new() -> Config {
|
||||||
|
Config {
|
||||||
|
source_dir: format!("{}/sources", env::var("HOME").unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read TOML file and load values into a Config struct
|
||||||
|
///
|
||||||
|
/// If the filename doesn't exist, read_config() will write the current struct to the given
|
||||||
|
/// config file.
|
||||||
|
pub fn read_config(&mut self, filename: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
// Load raw yaml file. If the file doesn't exist, create it
|
||||||
|
if std::path::Path::new(filename).exists() {
|
||||||
|
let raw = fs::read_to_string(filename)?;
|
||||||
|
// Convert string to our Config struct
|
||||||
|
let toml: Config = toml::from_str(&raw)?;
|
||||||
|
*self = toml;
|
||||||
|
} else {
|
||||||
|
let config: String = toml::to_string(&self)?;
|
||||||
|
// Get the path to the config file
|
||||||
|
let path = match Path::new(filename).parent() {
|
||||||
|
Some(x) => x,
|
||||||
|
_ => Path::new(filename)
|
||||||
|
};
|
||||||
|
// Make sure the path exists
|
||||||
|
fs::create_dir_all(path).unwrap();
|
||||||
|
// Write default Config struct to file
|
||||||
|
fs::write(filename, config).expect("Couldn't write file");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a string of the current loaded config.
|
||||||
|
///
|
||||||
|
/// Useful for dumping the config
|
||||||
|
pub fn to_string(&self) -> Result<String, toml::ser::Error> {
|
||||||
|
toml::to_string_pretty(self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
pub mod config;
|
||||||
|
use config::Config;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Holds the current state of the application.
|
||||||
|
///
|
||||||
|
/// This is used when processing command line arguments, and is thus loaded before the
|
||||||
|
/// configuration file is.
|
||||||
|
pub struct State {
|
||||||
|
cmd: Option<Cmd>,
|
||||||
|
url: Option<String>,
|
||||||
|
pub config: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Used to signal which main command is going to be run
|
||||||
|
enum Cmd {
|
||||||
|
Fetch,
|
||||||
|
Help,
|
||||||
|
DumpConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// Parses command line flags/arguments and creates the application state.
|
||||||
|
///
|
||||||
|
/// Argument parsing goes left to right. The first not flag argument it comes across is set as
|
||||||
|
/// the cmd.
|
||||||
|
///
|
||||||
|
/// Parsing priority is as follows:
|
||||||
|
/// - `--long-flag` type arguments
|
||||||
|
/// - `-mcsf` multi character short flag type arguments
|
||||||
|
/// - `command` type arguments
|
||||||
|
pub fn new(mut args: std::env::Args) -> State {
|
||||||
|
let mut state = State {
|
||||||
|
cmd: None,
|
||||||
|
url: None,
|
||||||
|
config: format!("{}/.config/shepherd/config.toml", env::var("HOME").unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut arg = args.next();
|
||||||
|
// Iterate through the command line arguments
|
||||||
|
while let Some(x) = &arg {
|
||||||
|
// Long arguments
|
||||||
|
if x.starts_with("--") {
|
||||||
|
let option = x.strip_prefix("--").unwrap();
|
||||||
|
match option {
|
||||||
|
"help" => state.cmd = Some(Cmd::Help),
|
||||||
|
"dump-config" => state.cmd = Some(Cmd::DumpConfig),
|
||||||
|
"config" => {
|
||||||
|
let file = args.next();
|
||||||
|
match file {
|
||||||
|
Some(x) => state.config = x,
|
||||||
|
None => eprintln!("Expected config argument"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// short arguments
|
||||||
|
else if x.starts_with("-") {
|
||||||
|
let options = x.strip_prefix("-").unwrap().chars();
|
||||||
|
for opt in options {
|
||||||
|
match opt {
|
||||||
|
'h' => state.cmd = Some(Cmd::Help),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fetch command
|
||||||
|
else if x == "fetch" {
|
||||||
|
let url = args.next();
|
||||||
|
match url {
|
||||||
|
Some(x) => match state.cmd {
|
||||||
|
None => {
|
||||||
|
state.cmd = Some(Cmd::Fetch);
|
||||||
|
state.url = Some(x);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
eprintln!("No URL provided for \'number\' command\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg = args.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to Help command if no other command was given
|
||||||
|
state.cmd = match state.cmd {
|
||||||
|
Some(x) => Some(x),
|
||||||
|
None => Some(Cmd::Help),
|
||||||
|
};
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(state: State, config: Config) -> Result<(), Box<dyn Error>> {
|
||||||
|
match state.cmd {
|
||||||
|
Some(Cmd::Help) => {
|
||||||
|
println!("{}", help_msg());
|
||||||
|
}
|
||||||
|
Some(Cmd::DumpConfig) => {
|
||||||
|
println!("{}", config.to_string().unwrap());
|
||||||
|
}
|
||||||
|
Some(x) => {
|
||||||
|
println!("{:?} hasn't been implemented yet!", x)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help_msg() -> String {
|
||||||
|
format!(
|
||||||
|
"Git repository manager
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
shepherd [--help] <command> [<args>]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Print out this help message
|
||||||
|
--dump-config dump the current configuration
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
General
|
||||||
|
help Print out this help message
|
||||||
|
|
||||||
|
Manage Repositories
|
||||||
|
clone Add another git repo to keep track of
|
||||||
|
fetch Update currently tracked repos"
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
use std::env;
|
||||||
|
use shepherd::State;
|
||||||
|
use shepherd::config::Config;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = env::args();
|
||||||
|
let state = State::new(args);
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.read_config(&state.config).unwrap();
|
||||||
|
if let Err(e) = shepherd::run(state, config) {
|
||||||
|
eprintln!("{}", e);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue