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