Compare commits

..

8 Commits

Author SHA1 Message Date
88c4207aeb Edit docs; Fix subtraction bug 2023-08-28 22:07:20 -04:00
18e4fef62f Improve help output 2023-08-28 20:03:13 -04:00
1f10bde1b4 Write README 2023-08-28 19:21:41 -04:00
3bbb64d065 Add filtering help by group 2023-08-28 17:58:35 -04:00
478ccbb529 Add help tool 2023-08-28 17:45:55 -04:00
0a16edbc97 Clean up 2023-08-28 17:24:15 -04:00
fad8d97212 Add ANSI coloring 2023-08-28 17:15:05 -04:00
e528c09623 Replace reedline with rustyline 2023-08-28 16:14:55 -04:00
10 changed files with 311 additions and 329 deletions

175
Cargo.lock generated
View File

@ -68,7 +68,7 @@ checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31"
dependencies = [ dependencies = [
"accesskit", "accesskit",
"accesskit_consumer", "accesskit_consumer",
"arrayvec 0.7.4", "arrayvec",
"once_cell", "once_cell",
"paste", "paste",
"windows 0.48.0", "windows 0.48.0",
@ -161,6 +161,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.5.0" version = "0.5.0"
@ -233,12 +242,6 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.4" version = "0.7.4"
@ -710,8 +713,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b" checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b"
dependencies = [ dependencies = [
"crossterm", "crossterm",
"strum 0.24.1", "strum",
"strum_macros 0.24.3", "strum_macros",
"unicode-width", "unicode-width",
] ]
@ -836,7 +839,6 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"parking_lot", "parking_lot",
"serde",
"signal-hook", "signal-hook",
"signal-hook-mio", "signal-hook-mio",
"winapi", "winapi",
@ -928,6 +930,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
name = "dust-lang" name = "dust-lang"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ansi_term",
"chrono", "chrono",
"clap", "clap",
"comfy-table", "comfy-table",
@ -936,11 +939,10 @@ dependencies = [
"egui_extras", "egui_extras",
"git2", "git2",
"json", "json",
"nu-ansi-term",
"rand", "rand",
"rayon", "rayon",
"reedline",
"reqwest", "reqwest",
"rustyline",
"serde", "serde",
"serde_json", "serde_json",
"sysinfo", "sysinfo",
@ -1065,6 +1067,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "enumflags2" name = "enumflags2"
version = "0.7.7" version = "0.7.7"
@ -1649,15 +1657,6 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.9" version = "1.0.9"
@ -1948,6 +1947,15 @@ dependencies = [
"jni-sys", "jni-sys",
] ]
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.24.3" version = "0.24.3"
@ -2010,15 +2018,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68"
dependencies = [
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -2365,6 +2364,16 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -2432,26 +2441,6 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "reedline"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2fde955d11817fdcb79d703932fb6b473192cb36b6a92ba21f7f4ac0513374e"
dependencies = [
"chrono",
"crossterm",
"fd-lock",
"itertools",
"nu-ansi-term",
"serde",
"strip-ansi-escapes",
"strum 0.25.0",
"strum_macros 0.25.2",
"thiserror",
"unicode-segmentation",
"unicode-width",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.4" version = "1.9.4"
@ -2557,6 +2546,41 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "rustyline"
version = "12.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
"clipboard-win",
"fd-lock",
"home",
"libc",
"log",
"memchr",
"nix 0.26.3",
"radix_trie",
"rustyline-derive",
"scopeguard",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]]
name = "rustyline-derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a32af5427251d2e4be14fc151eabe18abb4a7aad5efee7044da9f096c906a43"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.15" version = "1.0.15"
@ -2830,15 +2854,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "strip-ansi-escapes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
dependencies = [
"vte",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -2851,12 +2866,6 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]] [[package]]
name = "strum_macros" name = "strum_macros"
version = "0.24.3" version = "0.24.3"
@ -2870,19 +2879,6 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "strum_macros"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.29",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -2971,7 +2967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"arrayvec 0.7.4", "arrayvec",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"png", "png",
@ -3228,27 +3224,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vte"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
dependencies = [
"arrayvec 0.5.2",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
dependencies = [
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "waker-fn" name = "waker-fn"
version = "1.1.0" version = "1.1.0"

View File

@ -27,12 +27,12 @@ sysinfo = "0.29.6"
toml = "0.7.6" toml = "0.7.6"
toml_edit = "0.19.14" toml_edit = "0.19.14"
comfy-table = "7.0.1" comfy-table = "7.0.1"
reedline = "0.22.0"
clap = { version = "4.3.19", features = ["derive"] } clap = { version = "4.3.19", features = ["derive"] }
nu-ansi-term = "0.49"
git2 = "0.17.2" git2 = "0.17.2"
csv = "1.2.2" csv = "1.2.2"
json = "0.12.4" json = "0.12.4"
reqwest = { version = "0.11.18", features = ["blocking", "json"] } reqwest = { version = "0.11.18", features = ["blocking", "json"] }
serde_json = "1.0.104" serde_json = "1.0.104"
egui_extras = "0.22.0" egui_extras = "0.22.0"
rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] }
ansi_term = "0.12.1"

View File

@ -32,9 +32,10 @@ read("examples/assets/faithful.csv")
- [Features](#features) - [Features](#features)
- [Usage](#usage) - [Usage](#usage)
- [Installation](#installation) - [Installation](#installation)
- [Contributing](#contributing)
- [The Dust Programming Language](#the-dust-programming-language) - [The Dust Programming Language](#the-dust-programming-language)
- [Variables and Data Types](#variables-and-data-types) - [Variables and Data Types](#variables-and-data-types)
- [Commands](#commands) - [Tools](#tools)
- [Lists](#lists) - [Lists](#lists)
- [Maps](#maps) - [Maps](#maps)
- [Tables](#tables) - [Tables](#tables)
@ -56,6 +57,15 @@ read("examples/assets/faithful.csv")
Dust is an experimental project under active development. At this stage, features come and go and the API is always changing. It should not be considered for serious use yet. Dust is an experimental project under active development. At this stage, features come and go and the API is always changing. It should not be considered for serious use yet.
To get help with the shell you can use the "help" tool.
```dust
help() # Returns a table will all tool info.
help("random") # Returns a table with info on tools in the specified group.
# The above is simply a shorthand for this:
help() -> where(input, 'tool == "random"')
```
## Installation ## Installation
You must have the default rust toolchain installed and up-to-date. Install [rustup] if it is not already installed. Run `cargo install dust-lang` then run `dust` to start the interactive shell. Use `dust --help` to see the full command line options. You must have the default rust toolchain installed and up-to-date. Install [rustup] if it is not already installed. Run `cargo install dust-lang` then run `dust` to start the interactive shell. Use `dust --help` to see the full command line options.
@ -98,9 +108,9 @@ map.key = "value";
empty = (); empty = ();
``` ```
### Commands ### Tools
**Commands** are dust's built-in tools. Some of them can reconfigure your whole system while others are do very little. They may accept different inputs, or none at all. commands in the `random` group can be run without input, but the `random_integer` command can optionally take two numbers as in inclusive range. **Tools** are dust's built-in functions. Some of them can reconfigure your whole system while others do very little. They may accept different inputs, or none at all. For example, commands in the `random` group can be run without input, but the `random_integer` command can optionally take two numbers as in inclusive range.
```dust ```dust
die_roll = random_integer(1, 6); die_roll = random_integer(1, 6);
@ -108,21 +118,24 @@ d20_roll = random_integer(1, 20);
coin_flip = random_boolean(); coin_flip = random_boolean();
``` ```
Other commands can be found by pressing TAB in the interactive shell.
```dust ```dust
message = "I hate dust."; message = "I hate dust.";
replace(message, "hate", "love"); replace(message, "hate", "love")
``` ```
### Lists ### Lists
Lists are sequential collections. They can be built by grouping values with parentheses and separating them with commas. Values can be indexed by their position to access their contents. Lists are used to represent rows in tables and most commands take a list as an argument. Lists are sequential collections. They can be built by grouping values with parentheses and separating them with commas. Values can be indexed by their position to access their contents. Lists are used to represent rows in tables and most commands take a list as an argument. Their contents can be indexed using dot notation with an integer.
```dust ```dust
list = (true, 42, "Ok"); list = (true, 41, "Ok");
assert_equal(list.0, true); assert_equal(list.0, true);
list.1 = list.1 + 1;
assert_equal(list.1, 42);
``` ```
### Maps ### Maps
@ -157,7 +170,7 @@ Querying a table is similar to SQL.
```dust ```dust
names = select(animals, "name"); names = select(animals, "name");
youngins = where(animals, 'age < 5'); youngins = where(animals, 'age < 5');
old_species = select_where(animals, "species", 'age > 5') old_species = select_where(animals, "species", 'age > 5');
``` ```
The commands `create_table` and `insert` make sure that all of the memory used to hold the rows is allocated at once, so it is good practice to group your rows together instead of using a call for each row. The commands `create_table` and `insert` make sure that all of the memory used to hold the rows is allocated at once, so it is good practice to group your rows together instead of using a call for each row.

View File

@ -1,18 +1,17 @@
//! Command line interface for the whale programming language. //! Command line interface for the whale programming language.
use clap::Parser; use clap::Parser;
use nu_ansi_term::{Color, Style}; use rustyline::{
use reedline::{ completion::FilenameCompleter,
default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, DefaultPrompt, error::ReadlineError,
DefaultPromptSegment, EditCommand, Emacs, FileBackedHistory, KeyCode, KeyModifiers, Reedline, highlight::Highlighter,
ReedlineEvent, ReedlineMenu, Signal, Span, Suggestion, hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
Completer, Context, Editor, Helper, Validator,
}; };
use std::{ use std::{borrow::Cow, fs::read_to_string};
fs::{self, read_to_string},
path::PathBuf,
};
use dust_lib::{eval, eval_with_context, Tool, ToolInfo, Value, VariableMap, TOOL_LIST}; use dust_lib::{eval, eval_with_context, Value, VariableMap, TOOL_LIST};
/// Command-line arguments to be parsed. /// Command-line arguments to be parsed.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -49,205 +48,121 @@ fn main() {
} }
} }
#[derive(Helper, Completer, Validator)]
struct DustReadline {
#[rustyline(Completer)]
completer: FilenameCompleter,
tool_hints: Vec<ToolHint>,
#[rustyline(Hinter)]
_hinter: HistoryHinter,
}
impl DustReadline {
fn new() -> Self {
Self {
completer: FilenameCompleter::new(),
_hinter: HistoryHinter {},
tool_hints: TOOL_LIST
.iter()
.map(|tool| ToolHint {
display: tool.info().identifier.to_string(),
complete_to: tool.info().identifier.len(),
})
.collect(),
}
}
}
struct ToolHint {
display: String,
complete_to: usize,
}
impl Hint for ToolHint {
fn display(&self) -> &str {
&self.display
}
fn completion(&self) -> Option<&str> {
if self.complete_to > 0 {
Some(&self.display[..self.complete_to])
} else {
None
}
}
}
impl ToolHint {
fn suffix(&self, strip_chars: usize) -> ToolHint {
ToolHint {
display: self.display[strip_chars..].to_string(),
complete_to: self.complete_to.saturating_sub(strip_chars),
}
}
}
impl Hinter for DustReadline {
type Hint = ToolHint;
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
if line.is_empty() || pos < line.len() {
return None;
}
self.tool_hints.iter().find_map(|tool_hint| {
if tool_hint.display.starts_with(line) {
Some(tool_hint.suffix(pos))
} else {
None
}
})
}
}
impl Highlighter for DustReadline {
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
let highlighted = ansi_term::Colour::Red.paint(hint).to_string();
Cow::Owned(highlighted)
}
}
fn run_cli_shell() { fn run_cli_shell() {
let mut context = VariableMap::new(); let mut context = VariableMap::new();
let mut line_editor = setup_reedline(); let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
let prompt = DefaultPrompt {
left_prompt: DefaultPromptSegment::WorkingDirectory, rl.set_helper(Some(DustReadline::new()));
right_prompt: DefaultPromptSegment::CurrentDateTime,
}; if rl.load_history("target/history.txt").is_err() {
println!("No previous history.");
}
loop { loop {
let sig = line_editor.read_line(&prompt); let readline = rl.readline("* ");
match readline {
Ok(line) => {
let line = line.as_str();
match sig { rl.add_history_entry(line).unwrap();
Ok(Signal::Success(buffer)) => {
let eval_result = eval_with_context(&buffer, &mut context); let eval_result = eval_with_context(line, &mut context);
match eval_result { match eval_result {
Ok(value) => println!("{value}"), Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"), Err(error) => eprintln!("{error}"),
} }
} }
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => { Err(ReadlineError::Interrupted) => {
println!("\nExit");
break; break;
} }
signal => { Err(ReadlineError::Eof) => {
println!("Unhandled signal: {:?}", signal); break;
} }
} Err(error) => eprintln!("{error}"),
}
}
struct WhaleCompeleter {
macro_list: Vec<Suggestion>,
files: Vec<Suggestion>,
}
impl WhaleCompeleter {
pub fn new() -> Self {
WhaleCompeleter {
macro_list: Vec::new(),
files: Vec::new(),
} }
} }
pub fn set_command_list(&mut self, macro_list: Vec<&'static dyn Tool>) -> &mut Self { rl.save_history("target/history.txt").unwrap();
self.macro_list = macro_list
.iter()
.map(|r#macro| {
let ToolInfo {
identifier,
description,
group,
inputs,
} = r#macro.info();
let description = format!("{description} | {group}");
let inputs = inputs
.iter()
.map(|value_type| value_type.to_string())
.collect();
Suggestion {
value: identifier.to_string() + "()",
description: Some(description),
extra: Some(inputs),
..Default::default()
}
})
.collect();
self.macro_list
.sort_by_key(|suggestion| suggestion.extra.clone());
self
}
pub fn get_suggestions(&mut self, start: usize, end: usize) -> Vec<Suggestion> {
let macro_suggestions = self
.macro_list
.iter()
.cloned()
.map(|suggestion| Suggestion {
span: Span { start, end },
..suggestion
});
let file_suggestions = self.files.iter().cloned().map(|suggestion| Suggestion {
span: Span { start, end },
..suggestion
});
file_suggestions.chain(macro_suggestions).collect()
}
pub fn update_files(&mut self, mut path: &str) {
if path.starts_with('\"') {
path = &path[1..];
}
let path = PathBuf::from(path);
if !path.is_dir() {
return;
}
self.files = fs::read_dir(path)
.unwrap()
.map(|entry| {
let path = entry.unwrap().path();
let path = path.to_string_lossy();
Suggestion {
value: format!("\"{path}\""),
description: None,
..Default::default()
}
})
.collect();
}
}
impl Completer for WhaleCompeleter {
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let split = line.split(' ');
let current_word = split.last().unwrap_or("");
let start = pos.saturating_sub(current_word.len());
let end = line.len();
self.update_files(current_word);
self.get_suggestions(start, end)
}
}
fn setup_reedline() -> Reedline {
let mut completer = Box::new(WhaleCompeleter::new());
completer.set_command_list(TOOL_LIST.to_vec());
let completion_menu = Box::new(
ColumnarMenu::default()
.with_name("completion_menu")
.with_columns(1)
.with_text_style(Style {
foreground: Some(Color::White),
is_dimmed: false,
..Default::default()
})
.with_description_text_style(Style {
is_dimmed: true,
..Default::default()
})
.with_selected_text_style(Style {
is_bold: true,
background: Some(Color::Black),
foreground: Some(Color::White),
..Default::default()
}),
);
let mut keybindings = default_emacs_keybindings();
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
keybindings.add_binding(
KeyModifiers::SHIFT,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuPrevious,
]),
);
keybindings.add_binding(
KeyModifiers::ALT,
KeyCode::Enter,
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
);
let edit_mode = Box::new(Emacs::new(keybindings));
let history = Box::new(
FileBackedHistory::with_file(100, "target/history.txt".into())
.expect("Error configuring shell history file."),
);
let mut hinter = DefaultHinter::default();
hinter = hinter.with_style(Style {
foreground: Some(Color::Yellow),
..Default::default()
});
Reedline::create()
.with_completer(completer)
.with_menu(ReedlineMenu::EngineCompleter(completion_menu))
.with_edit_mode(edit_mode)
.with_history(history)
.with_hinter(Box::new(hinter))
.with_partial_completions(true)
.with_quick_completions(true)
} }

View File

@ -2,7 +2,58 @@ use std::{thread::sleep, time::Duration};
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use crate::{Result, Tool, ToolInfo, Value, ValueType}; use crate::{Result, Table, Tool, ToolInfo, Value, ValueType, TOOL_LIST};
pub struct Help;
impl Tool for Help {
fn info(&self) -> ToolInfo<'static> {
ToolInfo {
identifier: "help",
description: "Get help using dust.",
group: "general",
inputs: vec![ValueType::Empty, ValueType::String],
}
}
fn run(&self, argument: &Value) -> Result<Value> {
self.check_type(argument)?;
let mut table = Table::new(vec![
"tool".to_string(),
"description".to_string(),
"group".to_string(),
"inputs".to_string(),
]);
for tool in TOOL_LIST {
let tool_group = tool.info().group.to_string();
if let Ok(group) = argument.as_string() {
if &tool_group != group {
continue;
}
}
let row = vec![
Value::String(tool.info().identifier.to_string()),
Value::String(tool.info().description.to_string()),
Value::String(tool_group),
Value::List(
tool.info()
.inputs
.iter()
.map(|value_type| Value::String(value_type.to_string()))
.collect(),
),
];
table.insert(row)?;
}
Ok(Value::Table(table))
}
}
pub struct Output; pub struct Output;

View File

@ -50,7 +50,7 @@ pub mod time;
/// Master list of all tools. /// Master list of all tools.
/// ///
/// This list is used to match identifiers with tools and to provide info to the shell. /// This list is used to match identifiers with tools and to provide info to the shell.
pub const TOOL_LIST: [&'static dyn Tool; 51] = [ pub const TOOL_LIST: [&'static dyn Tool; 52] = [
&collections::Count, &collections::Count,
&collections::CreateTable, &collections::CreateTable,
&collections::Insert, &collections::Insert,
@ -82,6 +82,7 @@ pub const TOOL_LIST: [&'static dyn Tool; 51] = [
&filesystem::Trash, &filesystem::Trash,
&filesystem::Watch, &filesystem::Watch,
&filesystem::Write, &filesystem::Write,
&general::Help,
&general::Run, &general::Run,
&general::Output, &general::Output,
&general::Repeat, &general::Repeat,

View File

@ -162,7 +162,21 @@ impl Display for Table {
for row in &self.rows { for row in &self.rows {
let row = row.iter().map(|value| { let row = row.iter().map(|value| {
let text = match value { let text = match value {
Value::List(list) => format!("{list:?}"), Value::List(list) => {
let mut string = "(".to_string();
for (index, value) in list.into_iter().enumerate() {
if index > 0 {
string.push_str(", ");
}
string.push_str(&value.to_string());
}
string.push_str(")");
string
}
Value::Map(map) => format!("Map ({} items)", map.len()), Value::Map(map) => format!("Map ({} items)", map.len()),
Value::Table(table) => format!("Table ({} items)", table.len()), Value::Table(table) => format!("Table ({} items)", table.len()),
Value::Function(_) => "Function".to_string(), Value::Function(_) => "Function".to_string(),

View File

@ -1,15 +1,16 @@
//! Representation of a moment in time. //! Representation of a moment in time.
//! //!
//! Whale represent time values correctly. To do this, there must be a clear separation between //! Dust tries to represent time values correctly. To do this, there must be a clear separation
//! monotonic timestamps, naive times that do not know their locale and those that have a timezone. //! between monotonic timestamps, naive times that do not know their locale and those that have a ..
//! timezone.
//! //!
//! Only monotonic time instances are guaranteed not to repeat, although and Instance can be used to //! Only monotonic time instances are guaranteed not to repeat, although an Instant can be used to
//! create and of these variants. Users generally want the timezone included, so the `as_local` is //! create and of these variants. Users generally want the timezone included, so the `as_local` is
//! included, which will use no timezone offset if one is not available. //! included, which will use no timezone offset if one is not available.
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
time::{Instant, SystemTime}, time::{Duration, Instant, SystemTime, UNIX_EPOCH},
}; };
use chrono::{DateTime, FixedOffset, Local as LocalTime, NaiveDateTime}; use chrono::{DateTime, FixedOffset, Local as LocalTime, NaiveDateTime};
@ -54,7 +55,7 @@ impl Time {
Time::Utc(utc) => DateTime::from_utc(utc, FixedOffset::west_opt(0).unwrap()), Time::Utc(utc) => DateTime::from_utc(utc, FixedOffset::west_opt(0).unwrap()),
Time::Local(local) => local, Time::Local(local) => local,
Time::Monotonic(instant) => DateTime::from_utc( Time::Monotonic(instant) => DateTime::from_utc(
NaiveDateTime::from_timestamp_micros(instant.elapsed().as_micros() as i64).unwrap(), NaiveDateTime::from_timestamp_millis(instant.elapsed().as_millis() as i64).unwrap(),
FixedOffset::west_opt(0).unwrap(), FixedOffset::west_opt(0).unwrap(),
), ),
}; };
@ -65,7 +66,11 @@ impl Time {
impl Display for Time { impl Display for Time {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_local()) match self {
Time::Utc(inner) => write!(f, "{}", inner),
Time::Local(inner) => write!(f, "{}", inner),
Time::Monotonic(inner) => write!(f, "{:?}", inner),
}
} }
} }

View File

@ -1,9 +1,9 @@
use std::fmt::Display; use std::fmt::{self, Debug, Display, Formatter};
use crate::Value; use crate::Value;
/// The type of a `Value`. /// The type of a `Value`.
#[derive(Clone, Debug)] #[derive(Clone)]
pub enum ValueType { pub enum ValueType {
Any, Any,
String, String,
@ -38,11 +38,9 @@ impl PartialEq for ValueType {
(ValueType::ListOf(_), ValueType::List) => true, (ValueType::ListOf(_), ValueType::List) => true,
(ValueType::List, ValueType::ListOf(_)) => true, (ValueType::List, ValueType::ListOf(_)) => true,
(ValueType::ListOf(value_type), ValueType::ListExact(exact_list)) (ValueType::ListOf(value_type), ValueType::ListExact(exact_list))
| (ValueType::ListExact(exact_list), ValueType::ListOf(value_type)) => { | (ValueType::ListExact(exact_list), ValueType::ListOf(value_type)) => exact_list
exact_list .iter()
.iter() .all(|exact_type| exact_type == value_type.as_ref()),
.all(|exact_type| exact_type == value_type.as_ref())
}
(ValueType::List, ValueType::List) => true, (ValueType::List, ValueType::List) => true,
(ValueType::Empty, ValueType::Empty) => true, (ValueType::Empty, ValueType::Empty) => true,
(ValueType::Map, ValueType::Map) => true, (ValueType::Map, ValueType::Map) => true,
@ -55,7 +53,7 @@ impl PartialEq for ValueType {
} }
impl Display for ValueType { impl Display for ValueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match &self { match &self {
ValueType::Any => write!(f, "any"), ValueType::Any => write!(f, "any"),
ValueType::String => write!(f, "string"), ValueType::String => write!(f, "string"),
@ -64,15 +62,19 @@ impl Display for ValueType {
ValueType::Boolean => write!(f, "boolean"), ValueType::Boolean => write!(f, "boolean"),
ValueType::List => write!(f, "list"), ValueType::List => write!(f, "list"),
ValueType::ListOf(value_type) => { ValueType::ListOf(value_type) => {
write!(f, "list of {value_type}") write!(f, "({value_type}s)")
} }
ValueType::ListExact(list) => { ValueType::ListExact(list) => {
let items = list write!(f, "(")?;
.iter() for (index, item) in list.into_iter().enumerate() {
.map(|value_type| value_type.to_string() + " ") if index > 0 {
.collect::<String>(); write!(f, ", ")?;
}
write!(f, "list of {items}") write!(f, "{item}")?;
}
write!(f, ")")
} }
ValueType::Empty => write!(f, "empty"), ValueType::Empty => write!(f, "empty"),
ValueType::Map => write!(f, "map"), ValueType::Map => write!(f, "map"),
@ -83,6 +85,12 @@ impl Display for ValueType {
} }
} }
impl Debug for ValueType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl From<&Value> for ValueType { impl From<&Value> for ValueType {
fn from(value: &Value) -> Self { fn from(value: &Value) -> Self {
match value { match value {

View File

@ -111,7 +111,7 @@ impl VariableMap {
))); )));
}; };
let mut missing_elements = index - list.len() + 1; let mut missing_elements = index.saturating_sub(list.len()) + 1;
while missing_elements > 0 { while missing_elements > 0 {
list.push(value.clone()); list.push(value.clone());