Skip to content

Commit

Permalink
Add CLI subcommands and Bash tab completion (#600)
Browse files Browse the repository at this point in the history
* Minor aesthetic changes.

* Parse arguments with subcommands.

* Pass arguments to Amber script.

* Optionally generate documentation with usage.

* Optionally generate Bash completion script.

* Write to stdout by default if building stdin.

* Reword help text for doc subcommand.

* Minor aesthetic changes.

* Change doc subcommand to docs.

* Change comp subcommand to completion.

* Rename helper function.

* Enable postprocessors by default for run.

* Minor change to make Clippy happy.
  • Loading branch information
hdwalters authored Nov 21, 2024
1 parent 3ea8242 commit e5467d2
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 208 deletions.
26 changes: 18 additions & 8 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ copyright = "GPLv3"
[dependencies]
amber-meta = { path = "meta" }
chrono = "0.4.38"
clap = { version = "4.4.18", features = ["derive"] }
clap = { version = "4.5.20", features = ["derive"] }
clap_complete = "4.5.36"
colored = "2.0.0"
glob = "0.3"
heraclitus-compiler = "1.8.1"
Expand Down
71 changes: 43 additions & 28 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::modules::block::Block;
use crate::translate::check_all_blocks;
use crate::translate::module::TranslateModule;
use crate::utils::{ParserMetadata, TranslateMetadata};
use crate::{rules, Cli};
use crate::rules;
use postprocessor::PostProcessor;
use chrono::prelude::*;
use colored::Colorize;
Expand All @@ -24,20 +24,36 @@ const NO_CODE_PROVIDED: &str = "No code has been provided to the compiler";
const AMBER_DEBUG_PARSER: &str = "AMBER_DEBUG_PARSER";
const AMBER_DEBUG_TIME: &str = "AMBER_DEBUG_TIME";

pub struct CompilerOptions {
pub no_proc: Vec<String>,
pub minify: bool,
}

impl Default for CompilerOptions {
fn default() -> Self {
let no_proc = vec![String::from("*")];
Self { no_proc, minify: false }
}
}

impl CompilerOptions {
pub fn from_args(no_proc: &[String], minify: bool) -> Self {
let no_proc = no_proc.to_owned();
Self { no_proc, minify }
}
}

pub struct AmberCompiler {
pub cc: Compiler,
pub path: Option<String>,
pub cli_opts: Cli,
pub options: CompilerOptions,
}

impl AmberCompiler {
pub fn new(code: String, path: Option<String>, cli_opts: Cli) -> AmberCompiler {
AmberCompiler {
cc: Compiler::new("Amber", rules::get_rules()),
path,
cli_opts,
}
.load_code(AmberCompiler::comment_shebang(code))
pub fn new(code: String, path: Option<String>, options: CompilerOptions) -> AmberCompiler {
let cc = Compiler::new("Amber", rules::get_rules());
let compiler = AmberCompiler { cc, path, options };
compiler.load_code(AmberCompiler::comment_shebang(code))
}

fn comment_shebang(code: String) -> String {
Expand Down Expand Up @@ -91,14 +107,9 @@ impl AmberCompiler {
}
}

pub fn parse(
&self,
tokens: Vec<Token>,
is_docs_gen: bool,
) -> Result<(Block, ParserMetadata), Message> {
pub fn parse(&self, tokens: Vec<Token>) -> Result<(Block, ParserMetadata), Message> {
let code = self.cc.code.as_ref().expect(NO_CODE_PROVIDED).clone();
let mut meta = ParserMetadata::new(tokens, self.path.clone(), Some(code));
meta.is_docs_gen = is_docs_gen;
if let Err(Failure::Loud(err)) = check_all_blocks(&meta) {
return Err(err);
}
Expand Down Expand Up @@ -150,7 +161,7 @@ impl AmberCompiler {

pub fn translate(&self, block: Block, meta: ParserMetadata) -> Result<String, Message> {
let ast_forest = self.get_sorted_ast_forest(block, &meta);
let mut meta_translate = TranslateMetadata::new(meta, &self.cli_opts);
let mut meta_translate = TranslateMetadata::new(meta, &self.options);
let time = Instant::now();
let mut result = vec![];
for (_path, block) in ast_forest {
Expand All @@ -167,7 +178,7 @@ impl AmberCompiler {

let mut result = result.join("\n") + "\n";

let filters = self.cli_opts.no_proc.iter()
let filters = self.options.no_proc.iter()
.map(|x| WildMatchPattern::new(x))
.collect();
let postprocessors = PostProcessor::filter_default(filters);
Expand All @@ -185,13 +196,10 @@ impl AmberCompiler {
};
}

let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
let header = include_str!("header.sh")
.replace("{{ version }}", env!("CARGO_PKG_VERSION"))
.replace("{{ date }}", Local::now()
.format("%Y-%m-%d %H:%M:%S")
.to_string()
.as_str()
);
.replace("{{ date }}", now.as_str());
Ok(format!("{}{}", header, result))
}

Expand Down Expand Up @@ -255,32 +263,39 @@ impl AmberCompiler {

pub fn compile(&self) -> Result<(Vec<Message>, String), Message> {
let tokens = self.tokenize()?;
let (block, meta) = self.parse(tokens, false)?;
let (block, meta) = self.parse(tokens)?;
let messages = meta.messages.clone();
let code = self.translate(block, meta)?;
Ok((messages, code))
}

pub fn execute(code: String, flags: &[String]) -> Result<ExitStatus, std::io::Error> {
pub fn execute(mut code: String, args: Vec<String>) -> Result<ExitStatus, std::io::Error> {
if let Some(mut command) = Self::find_bash() {
let code = format!("set -- {};\n{}", flags.join(" "), code);
if !args.is_empty() {
let args = args.into_iter()
.map(|arg| arg.replace("\"", "\\\""))
.map(|arg| format!("\"{arg}\""))
.collect::<Vec<String>>();
code = format!("set -- {}\n{}", args.join(" "), code);
}
command.arg("-c").arg(code).spawn()?.wait()
} else {
let error = std::io::Error::new(ErrorKind::NotFound, "Failed to find Bash");
Err(error)
}
}

pub fn generate_docs(&self, output: String) -> Result<(), Message> {
pub fn generate_docs(&self, output: String, usage: bool) -> Result<(), Message> {
let tokens = self.tokenize()?;
let (block, meta) = self.parse(tokens, true)?;
let (block, mut meta) = self.parse(tokens)?;
meta.doc_usage = usage;
self.document(block, meta, output);
Ok(())
}

#[cfg(test)]
pub fn test_eval(&mut self) -> Result<String, Message> {
self.cli_opts.no_proc = vec!["*".into()];
self.options.no_proc = vec!["*".into()];
self.compile().map_or_else(Err, |(_, code)| {
if let Some(mut command) = Self::find_bash() {
let child = command.arg("-c").arg::<&str>(code.as_ref()).output().unwrap();
Expand Down
Loading

0 comments on commit e5467d2

Please sign in to comment.