use arg::{Cli, parse_args};
use std::borrow::Cow;
use std::env;
use std::fs::File;
use std::io::{BufReader, IsTerminal, Read, Write, stdin, stdout};
use std::path::Path;
use std::process::exit;
use std::rc::Rc;
use ziyy::Error;
use ziyy_core::{
Document, Fragment, FragmentType, Indexer, Parser, Resolver, Result, Span, Splitter,
WORD_PARSER,
};
mod arg;
fn index<'a>(source: &'a str) -> Cow<'a, str> {
let mut indexer = Indexer::new();
indexer.index(source)
}
fn parse_escapes_only<'a>(source: Cow<'a, str>) -> Result<Rc<Document<'a>>> {
let span = Span::calculate(&source);
let chunks = WORD_PARSER.parse(Fragment::new(FragmentType::Word, source, span));
let mut resolver = Resolver::new(true);
resolver.resolve(chunks)
}
fn parse<'a>(source: &'a Cow<'a, str>) -> Result<Rc<Document<'a>>> {
let mut splitter = Splitter::new();
let frags = splitter.split(source)?;
let parser = Parser::default();
let chunks = parser.parse(frags);
let mut resolver = Resolver::new(false);
resolver.resolve(chunks)
}
fn parse_to_out(source: &str, out: &mut impl Write, options: Options) {
let mut f = || {
let indexed = index(source);
let output = match options.escape_only {
true => parse_escapes_only(indexed),
false => parse(&indexed),
}?;
if options.strip {
output.root().strip_styles();
}
let mut buf = String::new();
if options.tree {
buf = output.to_string();
} else {
output.root().to_string(&mut buf);
}
let _ = out.write(buf.as_bytes());
Ok::<(), Error>(())
};
if let Err(err) = f() {
println!("{err}");
exit(1)
}
}
fn usage() {
let mut out = stdout();
let help = format!(include_str!("help.zy"), env!("CARGO_BIN_NAME"));
let indexed = index(&help);
let help = parse(&indexed).unwrap();
if !out.is_terminal() {
}
let mut buf = String::new();
help.root().to_string(&mut buf);
let _ = out.write(buf.as_bytes());
let _ = out.flush();
}
fn main() {
let mut args0 = env::args();
let mut out: Vec<u8> = vec![];
let mut stdout = stdout().lock();
if args0.len() - 1 < 1 {
usage();
exit(0);
}
args0.next();
let args = parse_args(
args0,
Cli {
short_flags: &[],
long_flags: &["mode"],
short_switches: &["h", "V", "c", "e", "n"],
long_switches: &[
"ansi",
"cli",
"help",
"no-newline",
"strip",
"version",
"tree",
],
},
);
let args = match args {
Ok(args) => args,
Err(err) => {
eprintln!("\x1b[31;1merror:\x1b[m unexpected argument '{err}' found");
exit(1);
}
};
let mut options = Options::default();
let mut params = vec![];
for arg in args {
if arg.is_long_switch_and(|s| s == "help") | arg.is_short_switch_and(|s| s == "h") {
usage();
exit(0);
} else if arg.is_long_switch_and(|s| s == "version") | arg.is_short_switch_and(|s| s == "V")
{
println!(env!("CARGO_PKG_VERSION"));
exit(0);
} else if arg.is_short_switch_and(|s| s == "c") | arg.is_long_switch_and(|s| s == "cli") {
options.cli = true;
} else if arg.is_short_switch_and(|s| s == "e") | arg.is_long_switch_and(|s| s == "ansi") {
options.escape_only = true;
} else if arg.is_short_switch_and(|s| s == "n")
| arg.is_long_switch_and(|s| s == "no-newline")
{
options.no_newline = true;
} else if arg.is_long_switch_and(|s| s == "strip") {
options.strip = true;
} else if arg.is_long_switch_and(|s| s == "tree") {
options.tree = true;
} else {
arg.is_params_and(|s| params.push(s))
}
}
if options.cli {
if params.is_empty() {
let mut buf = String::new();
let _ = stdin().read_to_string(&mut buf);
parse_to_out(&buf, &mut out, options);
} else {
parse_to_out(¶ms.join(" "), &mut out, options);
}
if !options.no_newline {
let _ = writeln!(out);
}
} else {
if params.is_empty() {
usage();
exit(1);
}
for param in ¶ms {
if !Path::new(¶m).is_file() {
usage();
exit(1);
}
let f = File::open(param).unwrap();
let mut reader = BufReader::new(f);
let mut file = String::new();
let _ = reader.read_to_string(&mut file);
if file.starts_with("#!") {
let mut lines = file.split_inclusive('\n');
lines.next();
file = lines.collect::<Vec<_>>().join("\n");
}
parse_to_out(&file, &mut out, options)
}
}
let _ = stdout.write(&out);
}
#[derive(Default, Clone, Copy)]
struct Options {
cli: bool,
escape_only: bool,
no_newline: bool,
strip: bool,
tree: bool,
}