Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement args parsing #4

Merged
merged 3 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
thiserror = "1.0"
atty = { version = "0.2" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
thiserror = "1.0"
18 changes: 18 additions & 0 deletions config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# `config` command for using DSC resources

## DESCRIPTION

The `config` command is used to discover and invoke DSC resources.

## Usage

Usage: config [subcommand] [options]
Subcommands:
list [filter] - list all resources, optional filter
get <resource> - invoke `get` on a resource
set <resource> - invoke `set` on a resource
test <resource> - invoke `test` on a resource
flushcache - flush the resource cache
Options:
-h, --help
-n, --nocache - don't use the cache and force a new discovery
208 changes: 199 additions & 9 deletions config/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
use std::io;
use std::process::exit;
use super::*;

/// Struct containing the parsed command line arguments
#[derive(Debug)]
pub struct Args {
/// The subcommand to run
pub subcommand: SubCommand,
/// The name of the resource to run the subcommand on
pub resource: String,
pub no_cache: bool, // don't use the cache and force a new discovery, low priority
/// The filter to use when listing resources
pub filter: String,
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
/// String representing any input piped in via stdin, this is typically expected to be JSON
pub stdin: String,
/// Whether to use the cache or not
pub no_cache: bool,
}

#[derive(Debug, PartialEq, Eq)]
pub enum SubCommand {
None,
List,
Get,
Set,
Expand All @@ -13,18 +28,193 @@ pub enum SubCommand {
}

impl Args {
pub fn new() -> Self {
// TODO: parse args and populate this struct
/// Parse the command line arguments and return an Args struct
///
/// # Arguments
///
/// * `args` - Iterator of command line arguments
/// * `input` - Input stream to read from
/// * `atty` - Whether the input stream is a tty (terminal) or not
pub fn new(args: &mut dyn Iterator<Item = String>, input: &mut dyn io::Read, atty: bool) -> Self {
let mut command_args: Vec<String> = args.skip(1).collect(); // skip the first arg which is the program name
if command_args.is_empty() {
eprintln!("No subcommand provided");
show_help_and_exit();
}

let subcommand = match command_args[0].to_lowercase().as_str() {
"list" => SubCommand::List,
"get" => SubCommand::Get,
"set" => SubCommand::Set,
"test" => SubCommand::Test,
"flushcache" => SubCommand::FlushCache,
_ => {
eprintln!("Invalid subcommand provided");
show_help_and_exit();
SubCommand::None
}
};

command_args.remove(0); // remove the subcommand

let mut no_cache = false;
let mut resource = String::new();
let mut filter = String::new();
let mut stdin = Vec::new();

if !atty {
// only read if input is piped in and not a tty (terminal) otherwise stdin is used for keyboard input
input.read_to_end(&mut stdin).unwrap();
}

let stdin = match String::from_utf8(stdin) {
Ok(v) => v,
Err(e) => {
eprintln!("Invalid UTF-8 sequence: {}", e);
exit(EXIT_INVALID_INPUT);
}
};

// go through reset of provided args 
for arg in command_args {
match arg.to_lowercase().as_str() {
"-h" | "--help" => show_help_and_exit(),
"-n" | "--nocache" => no_cache = true,
_ => {
if subcommand == SubCommand::FlushCache {
eprintln!("No arguments allowed for `flushcache`");
show_help_and_exit();
}

match subcommand {
SubCommand::List => {
if !filter.is_empty() {
eprintln!("Only one filter allowed");
show_help_and_exit();
}
filter = arg;
}
_ => {
if !resource.is_empty() {
eprintln!("Only one resource allowed");
show_help_and_exit();
}
resource = arg;
}
}
}
}
}

Self {
subcommand: SubCommand::List,
resource: String::new(),
no_cache: false,
subcommand,
resource,
filter,
stdin,
no_cache,
}
}
}

impl Default for Args {
fn default() -> Self {
Args::new()
fn show_help_and_exit() {
eprintln!();
eprintln!("Usage: config [subcommand] [options]");
eprintln!("Subcommands:");
eprintln!(" list [filter] - list all resources, optional filter");
eprintln!(" get <resource> - invoke `get` on a resource");
eprintln!(" set <resource> - invoke `set` on a resource");
eprintln!(" test <resource> - invoke `test` on a resource");
eprintln!(" flushcache - flush the resource cache");
eprintln!("Options:");
eprintln!(" -h, --help");
eprintln!(" -n, --nocache - don't use the cache and force a new discovery");
exit(EXIT_INVALID_ARGS);
}

#[cfg(test)]
mod tests {
use super::*;

struct Stdin {
input: String,
}

impl Stdin {
fn new(input: &str) -> Self {
Self {
input: input.to_string(),
}
}
}

impl io::Read for Stdin {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let bytes = self.input.as_bytes();
let len = bytes.len();
buf.clone_from_slice(bytes);
Ok(len)
}

fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
let bytes = self.input.as_bytes();
let len = bytes.len();
buf.extend_from_slice(bytes);
Ok(len)
}
}

#[test]
fn test_args_list() {
let mut args = ["config", "list", "myfilter"].iter().map(|s| s.to_string());
let args = Args::new(&mut args, &mut Stdin::new(""), false);
assert_eq!(args.subcommand, SubCommand::List);
assert_eq!(args.resource, "");
assert_eq!(args.filter, "myfilter");
assert_eq!(args.stdin, "");
assert_eq!(args.no_cache, false);
}

#[test]
fn test_args_get() {
let mut args = ["config", "get", "myresource"].iter().map(|s| s.to_string());
let args = Args::new(&mut args, &mut Stdin::new("abc"), false);
assert_eq!(args.subcommand, SubCommand::Get);
assert_eq!(args.resource, "myresource");
assert_eq!(args.filter, "");
assert_eq!(args.stdin, "abc");
assert_eq!(args.no_cache, false);
}

#[test]
fn test_args_set() {
let mut args = ["config", "set", "myresource"].iter().map(|s| s.to_string());
let args = Args::new(&mut args, &mut Stdin::new("abc"), false);
assert_eq!(args.subcommand, SubCommand::Set);
assert_eq!(args.resource, "myresource");
assert_eq!(args.filter, "");
assert_eq!(args.stdin, "abc");
assert_eq!(args.no_cache, false);
}

#[test]
fn test_args_test() {
let mut args = ["config", "test", "myresource"].iter().map(|s| s.to_string());
let args = Args::new(&mut args, &mut Stdin::new("abc"), false);
assert_eq!(args.subcommand, SubCommand::Test);
assert_eq!(args.resource, "myresource");
assert_eq!(args.filter, "");
assert_eq!(args.stdin, "abc");
assert_eq!(args.no_cache, false);
}

#[test]
fn test_args_flushcache() {
let mut args = ["config", "flushcache"].iter().map(|s| s.to_string());
let args = Args::new(&mut args, &mut Stdin::new(""), false);
assert_eq!(args.subcommand, SubCommand::FlushCache);
assert_eq!(args.resource, "");
assert_eq!(args.filter, "");
assert_eq!(args.stdin, "");
assert_eq!(args.no_cache, false);
}
}
22 changes: 16 additions & 6 deletions config/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
use args::*;
use atty::Stream;
use std::process::exit;
use std::{env, io};

pub mod args;
pub mod discovery;
pub mod dscresources;
pub mod dscerror;

use args::*;
pub const EXIT_INVALID_ARGS: i32 = 1;
pub const EXIT_INVALID_INPUT: i32 = 2;

fn main() {
let args = Args::new();
let args = Args::new(&mut env::args(), &mut io::stdin(), atty::is(Stream::Stdin));
match args.subcommand {
SubCommand::List => {
// perform discovery
println!("List");
println!("List {}", args.filter);
}
SubCommand::Get => {
// perform discovery
println!("Get");
println!("Get {}: {}", args.resource, args.stdin);
}
SubCommand::Set => {
// perform discovery
println!("Set");
println!("Set {}: {}", args.resource, args.stdin);
}
SubCommand::Test => {
// perform discovery
println!("Test");
println!("Test {}: {}", args.resource, args.stdin);
}
SubCommand::FlushCache => {
println!("FlushCache");
}
_ => {
eprintln!("Invalid subcommand");
exit(EXIT_INVALID_ARGS);
}
}
}