Add JSON functions; Modify CLI prompt
This commit is contained in:
parent
f6a1e641c9
commit
3d21196768
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1022,6 +1022,7 @@ dependencies = [
|
||||
"enum-iterator",
|
||||
"env_logger",
|
||||
"getrandom",
|
||||
"humantime",
|
||||
"libc",
|
||||
"log",
|
||||
"nu-ansi-term",
|
||||
|
@ -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"
|
||||
|
@ -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<Value> = 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)),
|
||||
|
64
src/built_in_functions/json.rs
Normal file
64
src/built_in_functions/json.rs
Normal file
@ -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<Item = Json> {
|
||||
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<Value> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Value>;
|
||||
}
|
||||
|
||||
#[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<Value> {
|
||||
fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
|
||||
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())?;
|
||||
|
@ -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<Item = StringFunction> {
|
||||
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<Value> {
|
||||
fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
|
||||
let value = match self {
|
||||
StringFunction::AsBytes => {
|
||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
60
src/main.rs
60
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<str> {
|
||||
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<str> {
|
||||
let time = humantime::format_rfc3339_seconds(SystemTime::now()).to_string();
|
||||
|
||||
Cow::Owned(time)
|
||||
}
|
||||
|
||||
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
|
||||
Cow::Borrowed(" > ")
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||
Cow::Borrowed(" > ")
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
&self,
|
||||
_history_search: reedline::PromptHistorySearch,
|
||||
) -> Cow<str> {
|
||||
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);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user