Skip to content

Commit

Permalink
Add a CLI
Browse files Browse the repository at this point in the history
Against all wisdom, home-cooked. Where's the fun in using pre-built
solutions? :)
  • Loading branch information
slotThe committed Apr 30, 2024
1 parent b767e0e commit 0c5f7d2
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 12 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ The type system looks as follows:
### Standard library

- Numerical operators:

n
``` agda
(+) : JSON → JSON → JSON -- Also works for string concatenation
(-) : JSON → JSON → JSON
Expand Down Expand Up @@ -340,4 +340,12 @@ Additionally, the following keywords are available:
),
)

# Additional command line flags

For no reason at all, there are some additional command line flags;
they ostensibly have nothing to do with `rq`'s main functionality:

- `--flatten` (`-f`): Flatten the given JSON into a list.
Inspired by [gron](https://github.com/tomnomnom/gron).

[^1]: This will be indicated by `SubType ≤ T`.
92 changes: 92 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::{collections::BTreeSet, sync::LazyLock};

// XXX: This really needs proc macros to have any semblance of code-reuse. I
// reckon that this isn't worth the effort; when the CLI gets more complicated
// one should just accept the additional transitive dependencies and switch to
// clap.

pub static HELP: LazyLock<String> = LazyLock::new(|| {
[
"rq: A tiny functional language to filter and manipulate JSON.",
&USAGE,
&OPTIONS,
"POSITIONAL ARGUMENTS:
«EXPR» A function in rq's expression language.",
]
.into_iter()
.intersperse("\n\n")
.collect()
});

static USAGE: LazyLock<String> = LazyLock::new(|| {
let flatten = CLI_OPTIONS.iter().find(|o| o.long == "--flatten").unwrap();
let help = CLI_OPTIONS.iter().find(|o| o.long == "--help").unwrap();
format!(
"USAGE:
rq [EXPR] < [JSON]
rq {} < [JSON]
rq {}",
flatten.usage(),
help.usage()
)
});

static OPTIONS: LazyLock<String> = LazyLock::new(|| {
let opts: String = CLI_OPTIONS
.iter()
.map(|opt| opt.help())
.intersperse("\n".to_string())
.collect();
format!("OPTIONS:\n{opts}")
});

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct CliOption {
long: String,
short: String,
help: String,
}

impl CliOption {
fn usage(&self) -> String { format!("[{}|{}]", self.short, self.long) }

fn help(&self) -> String { format!(" {},{} {}", self.short, self.long, self.help) }
}

macro_rules! mk_option {
($long:literal, $short:literal, $help:literal $(,)?) => {{
CliOption {
long: format!("--{}", $long),
short: format!("-{}", $short),
help: $help.to_string(),
}
}};
}

macro_rules! mk_options {
($(($long:literal, $short:literal, $help:literal $(,)?)),+ $(,)?) => {
static CLI_OPTIONS: LazyLock<BTreeSet<CliOption>> = LazyLock::new (|| {
BTreeSet::from([
$(mk_option!($long, $short, $help)),+
])});
};
}

mk_options!(
("help", "h", "Show this help text."),
("flatten", "f", "Flatten the given JSON into a list.")
);

macro_rules! Help {
() => {
"--help" | "-h"
};
}
pub(crate) use Help;

macro_rules! Flatten {
() => {
"--flatten" | "-f"
};
}
pub(crate) use Flatten;
60 changes: 49 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#![feature(type_changing_struct_update)]
#![allow(rustdoc::invalid_rust_codeblocks)]

mod cli;
mod eval;
mod expr;
mod r#type;
Expand All @@ -19,28 +20,65 @@ mod util;
use std::{collections::BTreeMap, env, io::{self, BufRead, Read, Write}};

use anyhow::Result;
use cli::{Flatten, Help};
use eval::stdlib::STDLIB_HELP;
use expr::{parser::{parse_expr, parse_json}, Expr};
use r#type::expr::TCExpr;

use crate::{eval::stdlib::{STDLIB_CTX, STDLIB_TYPES}, expr::app};
use crate::{cli::HELP, eval::stdlib::{STDLIB_CTX, STDLIB_TYPES}, expr::app, util::flatten::flatten};

fn main() -> Result<()> {
let arg = env::args().nth(1);
if arg.is_none() || arg == Some("repl".to_string()) {
repl()
} else {
let mut input = String::new();
// Try to read from the given file; if that does not work read from stdin.
if let Some(mut file) = env::args().nth(2).and_then(|f| std::fs::File::open(f).ok()) {
file.read_to_string(&mut input)?;
} else {
let args = env::args().skip(1).collect::<Vec<String>>();
match args.iter().map(|s| s as &str).collect::<Vec<_>>()[..] {
["repl", ..] => repl(),
[Help!(), ..] => {
println!("{}", *HELP);
Ok(())
},
[Flatten!()] => {
let mut input = String::new();
io::stdin().read_to_string(&mut input)?;
flatten_io(&input);
Ok(())
},
[Flatten!(), f] => {
let mut input = String::new();
read_file(&mut input, f)?;
flatten_io(&input);
Ok(())
},
[x] => {
let mut input = String::new();
io::stdin().read_to_string(&mut input)?;
oneshot(&input, x)
},
[x, f] => {
let mut input = String::new();
read_file(&mut input, f)?;
oneshot(&input, x)
},
_ => {
println!("{}", *HELP);
Ok(())
},
}
}

fn flatten_io(input: &str) {
if let Some(json) = parse_json(input) {
for line in flatten(&json) {
println!("{}", line)
}
oneshot(&input, &env::args().collect::<Vec<_>>()[1])
}
}

fn read_file(input: &mut String, f: &str) -> Result<()> {
if let Ok(mut file) = std::fs::File::open(f) {
file.read_to_string(input)?;
}
Ok(())
}

fn repl() -> Result<()> {
let mut buffer = String::new();
let stdin = io::stdin();
Expand Down

0 comments on commit 0c5f7d2

Please sign in to comment.