diff --git a/Cargo.lock b/Cargo.lock index 002a18a..b0b866e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1022,6 +1022,7 @@ dependencies = [ "enum-iterator", "env_logger", "getrandom", + "humantime", "libc", "log", "nu-ansi-term", diff --git a/Cargo.toml b/Cargo.toml index d18db75..931594f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ env_logger = "0.10" reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] } crossterm = "0.27.0" nu-ansi-term = "0.49.0" +humantime = "2.1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] env_logger = "0.10" diff --git a/src/abstract_tree/built_in_value.rs b/src/abstract_tree/built_in_value.rs index 169e702..c6bffc3 100644 --- a/src/abstract_tree/built_in_value.rs +++ b/src/abstract_tree/built_in_value.rs @@ -4,8 +4,8 @@ use enum_iterator::{all, Sequence}; use serde::{Deserialize, Serialize}; use crate::{ - built_in_functions::string_functions, AbstractTree, BuiltInFunction, Format, Function, List, - Map, Result, SyntaxNode, Type, Value, + built_in_functions::{json::json_functions, string::string_functions, Callable}, + AbstractTree, BuiltInFunction, Format, Function, List, Map, Result, SyntaxNode, Type, Value, }; static ARGS: OnceLock = OnceLock::new(); @@ -80,16 +80,18 @@ impl BuiltInValue { Value::Map(fs_context) }), BuiltInValue::Json => JSON.get_or_init(|| { - let json_context = Map::new(); + let mut json_context = BTreeMap::new(); - json_context - .set( - BuiltInFunction::JsonParse.name().to_string(), - Value::Function(Function::BuiltIn(BuiltInFunction::JsonParse)), - ) - .unwrap(); + for json_function in json_functions() { + let key = json_function.name().to_string(); + let value = + Value::Function(Function::BuiltIn(BuiltInFunction::Json(json_function))); + let r#type = value.r#type(); - Value::Map(json_context) + json_context.insert(key, (value, r#type)); + } + + Value::Map(Map::with_variables(json_context)) }), BuiltInValue::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)), BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)), diff --git a/src/built_in_functions/json.rs b/src/built_in_functions/json.rs new file mode 100644 index 0000000..f9ffa4d --- /dev/null +++ b/src/built_in_functions/json.rs @@ -0,0 +1,64 @@ +use enum_iterator::Sequence; +use serde::{Deserialize, Serialize}; + +use crate::{Error, Map, Result, Type, Value}; + +use super::Callable; + +pub fn json_functions() -> impl Iterator { + enum_iterator::all() +} + +#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum Json { + Create, + CreatePretty, + Parse, +} + +impl Callable for Json { + fn name(&self) -> &'static str { + match self { + Json::Create => "create", + Json::CreatePretty => "create_pretty", + Json::Parse => "parse", + } + } + + fn r#type(&self) -> Type { + match self { + Json::Create => Type::function(vec![Type::Any], Type::String), + Json::CreatePretty => Type::function(vec![Type::Any], Type::String), + Json::Parse => Type::function(vec![Type::String], Type::Any), + } + } + + fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result { + match self { + Json::Create => { + Error::expect_argument_amount(self.name(), 1, arguments.len())?; + + let value = arguments.first().unwrap(); + let json_string = serde_json::to_string(value)?; + + Ok(Value::String(json_string)) + } + Json::CreatePretty => { + Error::expect_argument_amount(self.name(), 1, arguments.len())?; + + let value = arguments.first().unwrap(); + let json_string = serde_json::to_string_pretty(value)?; + + Ok(Value::String(json_string)) + } + Json::Parse => { + Error::expect_argument_amount(self.name(), 1, arguments.len())?; + + let json_string = arguments.first().unwrap().as_string()?; + let value = serde_json::from_str(json_string)?; + + Ok(value) + } + } + } +} diff --git a/src/built_in_functions/mod.rs b/src/built_in_functions/mod.rs index 1699a1e..42e49ce 100644 --- a/src/built_in_functions/mod.rs +++ b/src/built_in_functions/mod.rs @@ -1,4 +1,5 @@ -mod string; +pub mod json; +pub mod string; use std::{ fmt::{self, Display, Formatter}, @@ -10,13 +11,19 @@ use serde::{Deserialize, Serialize}; use crate::{Error, Format, Map, Result, Type, Value}; -pub use string::{string_functions, StringFunction}; +use self::{json::Json, string::StringFunction}; + +pub trait Callable { + fn name(&self) -> &'static str; + fn r#type(&self) -> Type; + fn call(&self, arguments: &[Value], source: &str, outer_context: &Map) -> Result; +} #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub enum BuiltInFunction { AssertEqual, FsRead, - JsonParse, + Json(Json), Length, Output, RandomBoolean, @@ -26,12 +33,12 @@ pub enum BuiltInFunction { String(StringFunction), } -impl BuiltInFunction { - pub fn name(&self) -> &'static str { +impl Callable for BuiltInFunction { + fn name(&self) -> &'static str { match self { BuiltInFunction::AssertEqual => "assert_equal", BuiltInFunction::FsRead => "read", - BuiltInFunction::JsonParse => "parse", + BuiltInFunction::Json(json_function) => json_function.name(), BuiltInFunction::Length => "length", BuiltInFunction::Output => "output", BuiltInFunction::RandomBoolean => "boolean", @@ -42,11 +49,11 @@ impl BuiltInFunction { } } - pub fn r#type(&self) -> Type { + fn r#type(&self) -> Type { match self { BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None), BuiltInFunction::FsRead => Type::function(vec![Type::String], Type::String), - BuiltInFunction::JsonParse => Type::function(vec![Type::String], Type::Any), + BuiltInFunction::Json(json_function) => json_function.r#type(), BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer), BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None), BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean), @@ -57,7 +64,7 @@ impl BuiltInFunction { } } - pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result { + fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result { match self { BuiltInFunction::AssertEqual => { Error::expect_argument_amount(self.name(), 2, arguments.len())?; @@ -75,13 +82,8 @@ impl BuiltInFunction { Ok(Value::string(file_content)) } - BuiltInFunction::JsonParse => { - Error::expect_argument_amount(self.name(), 1, arguments.len())?; - - let string = arguments.first().unwrap().as_string()?; - let value = serde_json::from_str(string)?; - - Ok(value) + BuiltInFunction::Json(json_function) => { + json_function.call(arguments, _source, _outer_context) } BuiltInFunction::Length => { Error::expect_argument_amount(self.name(), 1, arguments.len())?; diff --git a/src/built_in_functions/string.rs b/src/built_in_functions/string.rs index 7990406..5515c9c 100644 --- a/src/built_in_functions/string.rs +++ b/src/built_in_functions/string.rs @@ -1,10 +1,12 @@ -use enum_iterator::{all, Sequence}; +use enum_iterator::Sequence; use serde::{Deserialize, Serialize}; use crate::{Error, List, Map, Result, Type, Value}; +use super::Callable; + pub fn string_functions() -> impl Iterator { - all() + enum_iterator::all() } #[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] @@ -41,8 +43,8 @@ pub enum StringFunction { Truncate, } -impl StringFunction { - pub fn name(&self) -> &'static str { +impl Callable for StringFunction { + fn name(&self) -> &'static str { match self { StringFunction::AsBytes => "as_bytes", StringFunction::EndsWith => "ends_with", @@ -77,7 +79,7 @@ impl StringFunction { } } - pub fn r#type(&self) -> Type { + fn r#type(&self) -> Type { match self { StringFunction::AsBytes => { Type::function(vec![Type::String], Type::list(Type::Integer)) @@ -163,7 +165,7 @@ impl StringFunction { } } - pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result { + fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result { let value = match self { StringFunction::AsBytes => { Error::expect_argument_amount(self.name(), 1, arguments.len())?; diff --git a/src/main.rs b/src/main.rs index a060a2d..3de4052 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,12 @@ use clap::{Parser, Subcommand}; use crossterm::event::{KeyCode, KeyModifiers}; use nu_ansi_term::Style; use reedline::{ - default_emacs_keybindings, DefaultPrompt, DefaultPromptSegment, EditCommand, Emacs, - Highlighter, Reedline, ReedlineEvent, Signal, SqliteBackedHistory, StyledText, + default_emacs_keybindings, ColumnarMenu, DefaultCompleter, DefaultHinter, EditCommand, Emacs, + Highlighter, Prompt, Reedline, ReedlineEvent, ReedlineMenu, Signal, SqliteBackedHistory, + StyledText, }; -use std::{fs::read_to_string, path::PathBuf}; +use std::{borrow::Cow, fs::read_to_string, path::PathBuf, time::SystemTime}; use dust_lang::{built_in_values, Interpreter, Map, Result, Value}; @@ -170,12 +171,41 @@ impl Highlighter for DustHighlighter { } } +struct DustPrompt; + +impl Prompt for DustPrompt { + fn render_prompt_left(&self) -> Cow { + let path = std::env::current_dir() + .map(|path| path.file_name().unwrap().to_string_lossy().to_string()) + .unwrap_or_else(|_| "No workdir".to_string()); + + Cow::Owned(path) + } + + fn render_prompt_right(&self) -> Cow { + let time = humantime::format_rfc3339_seconds(SystemTime::now()).to_string(); + + Cow::Owned(time) + } + + fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow { + Cow::Borrowed(" > ") + } + + fn render_prompt_multiline_indicator(&self) -> Cow { + Cow::Borrowed(" > ") + } + + fn render_prompt_history_search_indicator( + &self, + _history_search: reedline::PromptHistorySearch, + ) -> Cow { + Cow::Borrowed(" ? ") + } +} + fn run_shell(context: Map) -> Result<()> { let mut interpreter = Interpreter::new(context.clone()); - let mut prompt = DefaultPrompt::default(); - - prompt.left_prompt = DefaultPromptSegment::Basic(">".to_string()); - let mut keybindings = default_emacs_keybindings(); keybindings.add_binding( @@ -184,7 +214,7 @@ fn run_shell(context: Map) -> Result<()> { ReedlineEvent::Edit(vec![EditCommand::InsertNewline]), ); keybindings.add_binding( - KeyModifiers::CONTROL, + KeyModifiers::NONE, KeyCode::Tab, ReedlineEvent::UntilFound(vec![ ReedlineEvent::Menu("completion_menu".to_string()), @@ -192,7 +222,7 @@ fn run_shell(context: Map) -> Result<()> { ]), ); keybindings.add_binding( - KeyModifiers::NONE, + KeyModifiers::CONTROL, KeyCode::Tab, ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]), ); @@ -202,10 +232,20 @@ fn run_shell(context: Map) -> Result<()> { SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None) .expect("Error loading history."), ); + let hinter = Box::new(DefaultHinter::default()); let mut line_editor = Reedline::create() .with_edit_mode(edit_mode) .with_history(history) - .with_highlighter(Box::new(DustHighlighter::new(context))); + .with_highlighter(Box::new(DustHighlighter::new(context))) + .with_hinter(hinter) + .with_menu(ReedlineMenu::WithCompleter { + menu: Box::new(ColumnarMenu::default().with_name("completion_menu")), + completer: Box::new(DefaultCompleter::new_with_wordlen( + vec!["test".to_string()], + 2, + )), + }); + let prompt = DustPrompt; loop { let sig = line_editor.read_line(&prompt); diff --git a/src/value/function.rs b/src/value/function.rs index a2e4706..013cee3 100644 --- a/src/value/function.rs +++ b/src/value/function.rs @@ -5,7 +5,9 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{BuiltInFunction, Format, FunctionNode, Map, Result, Type, Value}; +use crate::{ + built_in_functions::Callable, BuiltInFunction, Format, FunctionNode, Map, Result, Type, Value, +}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub enum Function { @@ -20,16 +22,6 @@ impl Function { built_in_function.call(arguments, source, outer_context) } Function::ContextDefined(function_node) => { - function_node.set( - "self".to_string(), - Value::Function(Function::ContextDefined(Arc::new(FunctionNode::new( - function_node.parameters().clone(), - function_node.body().clone(), - function_node.r#type().clone(), - *function_node.syntax_position(), - )))), - )?; - function_node.call(arguments, source, outer_context) } }