diff --git a/Cargo.lock b/Cargo.lock index b02ad91..c63d7ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 4 [[package]] name = "doit" -version = "1.6.0" +version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 2b2a710..58771dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,6 @@ [package] name = "doit" -version = "1.6.0" -edition = "2021" +version = "1.7.0" +edition = "2024" + +[dependencies] diff --git a/README.md b/README.md index 827e93c..7161c9c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,15 @@ So I know you can just use something like a Makefile to build things, but I get 1. Download executable. 2. Copy to `/usr/local/bin` or place somewhere and link it like above. +### Bash Autocompletion + +DOIT supports target auto-completion in bash. This allows you to `doit ` and get a list of target options, as well as autocompletion for partial entries (ie. `doit cl` will autocomplete to `doit clean` if "clean" is a valid target in your DOIT script.). + +To get auto-completion, simply add the following to your local `.bashrc` file. +```sh +. <(doit --completion) +``` + ## Build Simple run the following commands in the project folder. Requires no additional crates or dependencies to build. diff --git a/autocomplete.sh b/autocomplete.sh new file mode 100755 index 0000000..82df13d --- /dev/null +++ b/autocomplete.sh @@ -0,0 +1,10 @@ +#!/bin/bash +_doit_completions() { + # Current word being completed + local cur="${COMP_WORDS[COMP_CWORD]}" + # Ask program for list of valid targets + local opts=$(doit -f --targets) + # Generate completions + COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) +} +complete -F _doit_completions doit \ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs index 80fe688..7acf530 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,7 +1,7 @@ use std::{ fs::{self, File}, hash::{DefaultHasher, Hash, Hasher}, - io::{Error, ErrorKind, Read, Write}, + io::{Error, Read, Write}, path::Path, process::Command, }; @@ -9,10 +9,10 @@ use std::{ use crate::{ generator::Generator, lexer::{ - token::{TokenType, Tokenizer}, Lexer, + token::{TokenType, Tokenizer}, }, - parser::Parser, + parser::{Parser, nodes::NodeType}, utils::log, }; @@ -23,6 +23,7 @@ pub enum CompileMode { PRINT_TOKENS, PRINT_NODES, PRINT_SOURCE, + PRINT_TARGETS, } pub fn build(directory: &String, filename: &str, keep: bool, force: bool, mode: CompileMode) -> Result<(), Error> { @@ -37,7 +38,10 @@ pub fn build(directory: &String, filename: &str, keep: bool, force: bool, mode: let mut hasher = DefaultHasher::new(); source.hash(&mut hasher); let new_hash = hasher.finish(); - if !force && Path::new(&(directory.to_owned() + "/hash")).exists() && Path::new(&(directory.to_owned() + "/targets")).exists() { + if !force + && Path::new(&(directory.to_owned() + "/hash")).exists() + && Path::new(&(directory.to_owned() + "/targets")).exists() + { let old_hash = fs::read_to_string(directory.to_owned() + "/hash")?; if old_hash == new_hash.to_string() { return Ok(()); @@ -48,6 +52,7 @@ pub fn build(directory: &String, filename: &str, keep: bool, force: bool, mode: match mode { CompileMode::PRINT_TOKENS => print_tokens(&mut Lexer::new(filename, &source)), CompileMode::PRINT_NODES => print_nodes(&mut Parser::new(&mut Lexer::new(filename, &source))), + CompileMode::PRINT_TARGETS => print_targets(&mut Parser::new(&mut Lexer::new(filename, &source))), _ => { let mut lexer = Lexer::new(filename, &source); let mut parser = Parser::new(&mut lexer); @@ -78,7 +83,7 @@ fn compile(directory: &String, source: &str) -> Result<(), Error> { .args(["--std=c++20", cpp, "-o", &(directory.to_owned() + "/targets")]) .spawn()?; if !output.wait()?.success() { - return Err(Error::new(ErrorKind::Other, "Failed to compile")); + return Err(Error::other("Failed to compile")); } Ok(()) } @@ -103,3 +108,19 @@ fn print_nodes(parser: &mut Parser) { Err(err) => log::error(&format!("Failed to parse: {}", err)), }; } + +fn print_targets(parser: &mut Parser) { + match parser.parse() { + Ok(root) => { + let mut filtered: Vec = root + .children + .into_iter() + .filter(|node| node.ntype == NodeType::TARGET) + .filter_map(|node| node.value.value) + .collect(); + filtered.sort(); + filtered.into_iter().for_each(|n| println!("{}", n)); + } + Err(err) => log::error(&format!("Failed to parse: {}", err)), + }; +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 7ff597c..63ac372 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -73,7 +73,7 @@ impl Lexer<'_> { fn consume_and_ignore(&mut self) -> Result<(), Error> { match self.consume() { Ok(_) => Ok(()), - Err(err) => Err(Error::new(ErrorKind::Other, err)), + Err(err) => Err(Error::other(err)), } } diff --git a/src/main.rs b/src/main.rs index bfbe9cf..97822c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![allow(dead_code)] use std::{ - env, fs, path::Path, process::{exit, Command, ExitCode} + env, fs, + path::Path, + process::{Command, ExitCode, exit}, }; mod compiler; @@ -14,7 +16,10 @@ use compiler::CompileMode; use crate::utils::log; fn print_help(program_name: &str) { - println!("\x1b[32mUsage: \x1b[33m{} \x1b[90m[options] \x1b[34m \x1b[90m[target_params]\x1b[0m\n", program_name); + println!( + "\x1b[32mUsage: \x1b[33m{} \x1b[90m[options] \x1b[34m \x1b[90m[target_params]\x1b[0m\n", + program_name + ); println!("If the target is ommitted, command will print out a list of available targets."); println!(); println!("\x1b[32m Options:\x1b[0m"); @@ -27,6 +32,7 @@ fn print_help(program_name: &str) { println!(" \x1b[90m--nodes\x1b[0m Print out the parser node tree instead of fully compiling."); println!(" \x1b[90m--source\x1b[0m Print the transpiled C++ code to stdout instead of fully compiling."); println!(" \x1b[90m--keep\x1b[0m After compiling, do not delete the .doit/targets.cpp file."); + println!(" \x1b[90m--targets\x1b[0m Print the targets to stdout. Used for autocompletion."); println!(" \x1b[90m--help\x1b[0m Prints out this help message."); } @@ -37,6 +43,7 @@ fn main() -> ExitCode { let mut print_tokens = false; let mut print_nodes = false; let mut print_source = false; + let mut print_targets = false; let mut keep_source = false; let mut filename: String = String::new(); while !args.is_empty() && args[0].starts_with('-') { @@ -46,25 +53,37 @@ fn main() -> ExitCode { "--nodes" => print_nodes = true, "--source" => print_source = true, "--keep" => keep_source = true, + "--targets" => print_targets = true, + "--completion" => { + println!( + r##"#!/bin/bash +_doit_completions() {{ + local cur="${{COMP_WORDS[COMP_CWORD]}}" + local opts=$(doit -f --targets) + COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) +}} +complete -F _doit_completions doit"## + ); + exit(0); + } "--help" => { print_help(&program_name); exit(0); - }, + } "--version" => { println!("v{}", env!("CARGO_PKG_VERSION")); exit(0); - }, + } "-t" => filename = args.remove(0), "-c" => { - if Path::new("./.doit").exists() { - if let Err(err) = fs::remove_dir_all("./.doit") { - log::error(&err.to_string()); - return ExitCode::from(1); - } + if Path::new("./.doit").exists() + && let Err(err) = fs::remove_dir_all("./.doit") { + log::error(&err.to_string()); + return ExitCode::from(1); } log::info("Cleaned!"); exit(0); - }, + } fail => { log::error(&format!("Unknown option: {}", fail)); print_help(&program_name); @@ -85,12 +104,18 @@ fn main() -> ExitCode { if let Some(path_str) = path.to_str() { filename = path_str.to_owned(); } else { - log::error(&format!("Could not calculate and absolute path for the provided doit file '{}'", filename)); + log::error(&format!( + "Could not calculate and absolute path for the provided doit file '{}'", + filename + )); return ExitCode::from(1); } - }, + } Err(err) => { - log::error(&format!("Could not calculate and absolute path for the provided doit file '{}'", filename)); + log::error(&format!( + "Could not calculate and absolute path for the provided doit file '{}'", + filename + )); log::error(&err.to_string()); return ExitCode::from(1); } @@ -115,6 +140,8 @@ fn main() -> ExitCode { CompileMode::PRINT_NODES } else if print_source { CompileMode::PRINT_SOURCE + } else if print_targets { + CompileMode::PRINT_TARGETS } else { CompileMode::NORMAL }, @@ -122,7 +149,7 @@ fn main() -> ExitCode { log::error(&err.to_string()); return ExitCode::from(1); } - if print_tokens || print_nodes || print_source { + if print_tokens || print_nodes || print_source || print_targets { return ExitCode::from(0); } @@ -136,7 +163,7 @@ fn main() -> ExitCode { let code = status.code().unwrap_or(if status.success() { 0 } else { 1 }); log::debug(&format!("Exit Code: {}", code)); ExitCode::from(code as u8) - }, + } Err(err) => { log::error(&err.to_string()); ExitCode::from(1) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 02f99af..96aa065 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -126,8 +126,7 @@ impl Parser<'_> { )), TokenType::HELP => { if scope.help.is_some() { - return Err(Error::new( - ErrorKind::Other, + return Err(Error::other( "Help block has already been defined for the current scope.", )); }