Skip to content

Commit

Permalink
Read TOML configuration from stdin (#145)
Browse files Browse the repository at this point in the history
* Read from stdin and build config

* Correctly determine if stdin is a tty

* Change empty string error text

* fmt
  • Loading branch information
jameslittle230 authored Feb 5, 2021
1 parent 33a9f9d commit 4e32ada
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 38 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ scraper = "0.12.0"
frontmatter = "0.4.0"
markdown = "0.3.0"
once_cell = "1"
atty = "0.2"
futures = {version = "0.3", optional = true}
hyper = {version = "0.13", optional = true}
tokio = { version = "0.2", features = ["full"], optional = true }
Expand Down
30 changes: 9 additions & 21 deletions src/bin/stork/argparse.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{ExitCode, EXIT_FAILURE, EXIT_SUCCESS};
use std::fmt;
use std::{convert::TryInto, fmt, ops::Range};

pub struct Argparse {
commands: Vec<Command>,
Expand All @@ -14,15 +14,14 @@ struct Command {

enum ValueOrRange {
Value(u8),
Range(u8, u8),
Range(Range<u8>),
}

impl fmt::Display for ValueOrRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueOrRange::Value(val) => write!(f, "{}", val),

ValueOrRange::Range(min, max) => write!(f, "between {} and {}", min, max),
ValueOrRange::Range(range) => write!(f, "between {} and {}", range.start, range.end),
}
}
}
Expand All @@ -44,19 +43,11 @@ impl Argparse {
}

#[allow(dead_code)]
pub fn register_range(&mut self, cmd_name: &str, action: fn(&[String]), args_range: (u8, u8)) {
let min = std::cmp::min(args_range.0, args_range.1);
let max = std::cmp::max(args_range.0, args_range.1);
let number_of_args = if min == max {
ValueOrRange::Value(min)
} else {
ValueOrRange::Range(min, max)
};

pub fn register_range(&mut self, cmd_name: &str, action: fn(&[String]), args_range: Range<u8>) {
self.commands.push(Command {
name: cmd_name.to_string(),
action,
number_of_args,
number_of_args: ValueOrRange::Range(args_range),
})
}

Expand All @@ -74,13 +65,10 @@ impl Argparse {

for command in &self.commands {
if args[1] == ["--", &command.name].concat() {
let number_of_args = args.len() - 2;
let valid = match command.number_of_args {
ValueOrRange::Value(val) => (number_of_args as u8) == val,

ValueOrRange::Range(min, max) => {
(number_of_args as u8) >= min && (number_of_args as u8) <= max
}
let number_of_args: u8 = (args.len() - 2).try_into().unwrap();
let valid = match &command.number_of_args {
ValueOrRange::Value(val) => number_of_args == val.to_owned(),
ValueOrRange::Range(range) => range.contains(&number_of_args),
};

if !valid {
Expand Down
51 changes: 35 additions & 16 deletions src/bin/stork/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ mod argparse;
use argparse::Argparse;

mod test_server;
use atty::Stream;
use test_server::serve;

mod display_timings;
use display_timings::*;

use std::fs::File;
use std::env;
use std::io::{BufReader, Read};
use std::time::Instant;
use std::{env, error::Error};
use std::{fs::File, io};
use stork::config::Config;
use stork::LatestVersion::structs::Index;

Expand Down Expand Up @@ -51,26 +52,48 @@ USAGE:

fn main() {
let mut a = Argparse::new();
a.register("build", build_handler, 1);
a.register_range("build", build_handler, 0..2);
a.register("test", test_handler, 1);
a.register("search", search_handler, 2);
a.register_help(&help_text());
std::process::exit(a.exec(env::args().collect()));
}

pub fn build_index(config_path: &str) -> Result<(Config, Index), Box<dyn Error>> {
let config = Config::from_file(std::path::PathBuf::from(config_path))?;
let index = stork::build(&config)?;
Ok((config, index))
pub fn build_index(optional_config_path: Option<&String>) -> (Config, Index) {
// Potential refactor: this method could return a result instead of
// std::process::exiting when there's a failure.

let config = {
match optional_config_path {
Some(config_path) => Config::from_file(std::path::PathBuf::from(config_path)),
None => {
let mut stdin_buffer = String::new();
if atty::isnt(Stream::Stdin) {
let _ = io::stdin().read_to_string(&mut stdin_buffer);
} else {
eprintln!("stork --build doesn't support interactive stdin! Pipe in a stream instead.")
}
Config::from_string(stdin_buffer)
}
}
}
.unwrap_or_else(|error| {
eprintln!("Could not read configuration: {}", error.to_string());
std::process::exit(EXIT_FAILURE);
});

let index = stork::build(&config).unwrap_or_else(|error| {
eprintln!("Could not generate index: {}", error.to_string());
std::process::exit(EXIT_FAILURE);
});

(config, index)
}

fn build_handler(args: &[String]) {
let start_time = Instant::now();

let (config, index) = build_index(&args[2]).unwrap_or_else(|e| {
eprintln!("Could not generate index: {}", e.to_string());
std::process::exit(EXIT_FAILURE);
});
let (config, index) = build_index(args.get(2));

let build_time = Instant::now();
let bytes_written = match index.write(&config) {
Expand Down Expand Up @@ -107,11 +130,7 @@ fn build_handler(args: &[String]) {
}

fn test_handler(args: &[String]) {
let (_, index) = build_index(&args[2]).unwrap_or_else(|e| {
eprintln!("Could not generate index: {}", e.to_string());
std::process::exit(EXIT_FAILURE);
});

let (_, index) = build_index(args.get(2));
let _r = serve(index);
}

Expand Down
2 changes: 2 additions & 0 deletions src/config/config_read_err.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{error::Error, fmt, path::PathBuf};
#[derive(Debug)]
pub enum ConfigReadErr {
EmptyString,
UnreadableFile(PathBuf),
UnparseableInput(toml::de::Error),
}
Expand All @@ -10,6 +11,7 @@ impl Error for ConfigReadErr {}
impl fmt::Display for ConfigReadErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc: String = match self {
ConfigReadErr::EmptyString => "Received empty configuration string".to_string(),
ConfigReadErr::UnreadableFile(s) => format!("File {} not found", s.to_string_lossy()),
ConfigReadErr::UnparseableInput(e) => e.to_string(),
};
Expand Down
9 changes: 8 additions & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ impl Config {
pub fn from_file(path: std::path::PathBuf) -> Result<Config, ConfigReadErr> {
let contents =
fs::read_to_string(&path).map_err(|_| ConfigReadErr::UnreadableFile(path))?;
toml::from_str(&contents).map_err(ConfigReadErr::UnparseableInput)
Config::from_string(contents)
}

pub fn from_string(str: String) -> Result<Config, ConfigReadErr> {
if str.is_empty() {
return Err(ConfigReadErr::EmptyString);
}
toml::from_str(&str).map_err(ConfigReadErr::UnparseableInput)
}
}

Expand Down

0 comments on commit 4e32ada

Please sign in to comment.