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 = [
"accesskit",
"accesskit_consumer",
"arrayvec 0.7.4",
"arrayvec",
"once_cell",
"paste",
"windows 0.48.0",
@ -161,6 +161,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anstream"
version = "0.5.0"
@ -233,12 +242,6 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -710,8 +713,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b"
dependencies = [
"crossterm",
"strum 0.24.1",
"strum_macros 0.24.3",
"strum",
"strum_macros",
"unicode-width",
]
@ -836,7 +839,6 @@ dependencies = [
"libc",
"mio",
"parking_lot",
"serde",
"signal-hook",
"signal-hook-mio",
"winapi",
@ -928,6 +930,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
name = "dust-lang"
version = "0.1.0"
dependencies = [
"ansi_term",
"chrono",
"clap",
"comfy-table",
@ -936,11 +939,10 @@ dependencies = [
"egui_extras",
"git2",
"json",
"nu-ansi-term",
"rand",
"rayon",
"reedline",
"reqwest",
"rustyline",
"serde",
"serde_json",
"sysinfo",
@ -1065,6 +1067,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "enumflags2"
version = "0.7.7"
@ -1649,15 +1657,6 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@ -1948,6 +1947,15 @@ dependencies = [
"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]]
name = "nix"
version = "0.24.3"
@ -2010,15 +2018,6 @@ dependencies = [
"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]]
name = "num-integer"
version = "0.1.45"
@ -2365,6 +2364,16 @@ dependencies = [
"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]]
name = "rand"
version = "0.8.5"
@ -2432,26 +2441,6 @@ dependencies = [
"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]]
name = "regex"
version = "1.9.4"
@ -2557,6 +2546,41 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ryu"
version = "1.0.15"
@ -2830,15 +2854,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "strsim"
version = "0.10.0"
@ -2851,12 +2866,6 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
version = "0.24.3"
@ -2870,19 +2879,6 @@ dependencies = [
"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]]
name = "syn"
version = "1.0.109"
@ -2971,7 +2967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67"
dependencies = [
"arrayref",
"arrayvec 0.7.4",
"arrayvec",
"bytemuck",
"cfg-if",
"png",
@ -3228,27 +3224,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "waker-fn"
version = "1.1.0"

View File

@ -27,12 +27,12 @@ sysinfo = "0.29.6"
toml = "0.7.6"
toml_edit = "0.19.14"
comfy-table = "7.0.1"
reedline = "0.22.0"
clap = { version = "4.3.19", features = ["derive"] }
nu-ansi-term = "0.49"
git2 = "0.17.2"
csv = "1.2.2"
json = "0.12.4"
reqwest = { version = "0.11.18", features = ["blocking", "json"] }
serde_json = "1.0.104"
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)
- [Usage](#usage)
- [Installation](#installation)
- [Contributing](#contributing)
- [The Dust Programming Language](#the-dust-programming-language)
- [Variables and Data Types](#variables-and-data-types)
- [Commands](#commands)
- [Tools](#tools)
- [Lists](#lists)
- [Maps](#maps)
- [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.
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
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 = ();
```
### 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
die_roll = random_integer(1, 6);
@ -108,21 +118,24 @@ d20_roll = random_integer(1, 20);
coin_flip = random_boolean();
```
Other commands can be found by pressing TAB in the interactive shell.
```dust
message = "I hate dust.";
replace(message, "hate", "love");
replace(message, "hate", "love")
```
### 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
list = (true, 42, "Ok");
list = (true, 41, "Ok");
assert_equal(list.0, true);
list.1 = list.1 + 1;
assert_equal(list.1, 42);
```
### Maps
@ -157,7 +170,7 @@ Querying a table is similar to SQL.
```dust
names = select(animals, "name");
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.

View File

@ -1,18 +1,17 @@
//! Command line interface for the whale programming language.
use clap::Parser;
use nu_ansi_term::{Color, Style};
use reedline::{
default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, DefaultPrompt,
DefaultPromptSegment, EditCommand, Emacs, FileBackedHistory, KeyCode, KeyModifiers, Reedline,
ReedlineEvent, ReedlineMenu, Signal, Span, Suggestion,
use rustyline::{
completion::FilenameCompleter,
error::ReadlineError,
highlight::Highlighter,
hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
Completer, Context, Editor, Helper, Validator,
};
use std::{
fs::{self, read_to_string},
path::PathBuf,
};
use std::{borrow::Cow, fs::read_to_string};
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.
#[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() {
let mut context = VariableMap::new();
let mut line_editor = setup_reedline();
let prompt = DefaultPrompt {
left_prompt: DefaultPromptSegment::WorkingDirectory,
right_prompt: DefaultPromptSegment::CurrentDateTime,
};
let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
rl.set_helper(Some(DustReadline::new()));
if rl.load_history("target/history.txt").is_err() {
println!("No previous history.");
}
loop {
let sig = line_editor.read_line(&prompt);
let readline = rl.readline("* ");
match readline {
Ok(line) => {
let line = line.as_str();
match sig {
Ok(Signal::Success(buffer)) => {
let eval_result = eval_with_context(&buffer, &mut context);
rl.add_history_entry(line).unwrap();
let eval_result = eval_with_context(line, &mut context);
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
}
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
println!("\nExit");
Err(ReadlineError::Interrupted) => {
break;
}
signal => {
println!("Unhandled signal: {:?}", signal);
Err(ReadlineError::Eof) => {
break;
}
}
}
}
struct WhaleCompeleter {
macro_list: Vec<Suggestion>,
files: Vec<Suggestion>,
}
impl WhaleCompeleter {
pub fn new() -> Self {
WhaleCompeleter {
macro_list: Vec::new(),
files: Vec::new(),
Err(error) => eprintln!("{error}"),
}
}
pub fn set_command_list(&mut self, macro_list: Vec<&'static dyn Tool>) -> &mut Self {
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)
rl.save_history("target/history.txt").unwrap();
}

View File

@ -2,7 +2,58 @@ use std::{thread::sleep, time::Duration};
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;

View File

@ -50,7 +50,7 @@ pub mod time;
/// Master list of all tools.
///
/// 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::CreateTable,
&collections::Insert,
@ -82,6 +82,7 @@ pub const TOOL_LIST: [&'static dyn Tool; 51] = [
&filesystem::Trash,
&filesystem::Watch,
&filesystem::Write,
&general::Help,
&general::Run,
&general::Output,
&general::Repeat,

View File

@ -162,7 +162,21 @@ impl Display for Table {
for row in &self.rows {
let row = row.iter().map(|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::Table(table) => format!("Table ({} items)", table.len()),
Value::Function(_) => "Function".to_string(),

View File

@ -1,15 +1,16 @@
//! Representation of a moment in time.
//!
//! Whale represent time values correctly. To do this, there must be a clear separation between
//! monotonic timestamps, naive times that do not know their locale and those that have a timezone.
//! Dust tries to represent time values correctly. To do this, there must be a clear separation
//! 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
//! included, which will use no timezone offset if one is not available.
use std::{
fmt::{self, Display, Formatter},
time::{Instant, SystemTime},
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
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::Local(local) => local,
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(),
),
};
@ -65,7 +66,11 @@ impl Time {
impl Display for Time {
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;
/// The type of a `Value`.
#[derive(Clone, Debug)]
#[derive(Clone)]
pub enum ValueType {
Any,
String,
@ -38,11 +38,9 @@ impl PartialEq for ValueType {
(ValueType::ListOf(_), ValueType::List) => true,
(ValueType::List, ValueType::ListOf(_)) => true,
(ValueType::ListOf(value_type), ValueType::ListExact(exact_list))
| (ValueType::ListExact(exact_list), ValueType::ListOf(value_type)) => {
exact_list
.iter()
.all(|exact_type| exact_type == value_type.as_ref())
}
| (ValueType::ListExact(exact_list), ValueType::ListOf(value_type)) => exact_list
.iter()
.all(|exact_type| exact_type == value_type.as_ref()),
(ValueType::List, ValueType::List) => true,
(ValueType::Empty, ValueType::Empty) => true,
(ValueType::Map, ValueType::Map) => true,
@ -55,7 +53,7 @@ impl PartialEq 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 {
ValueType::Any => write!(f, "any"),
ValueType::String => write!(f, "string"),
@ -64,15 +62,19 @@ impl Display for ValueType {
ValueType::Boolean => write!(f, "boolean"),
ValueType::List => write!(f, "list"),
ValueType::ListOf(value_type) => {
write!(f, "list of {value_type}")
write!(f, "({value_type}s)")
}
ValueType::ListExact(list) => {
let items = list
.iter()
.map(|value_type| value_type.to_string() + " ")
.collect::<String>();
write!(f, "(")?;
for (index, item) in list.into_iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "list of {items}")
write!(f, "{item}")?;
}
write!(f, ")")
}
ValueType::Empty => write!(f, "empty"),
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 {
fn from(value: &Value) -> Self {
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 {
list.push(value.clone());