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",
|
"enum-iterator",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
"humantime",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
|
@ -42,6 +42,7 @@ env_logger = "0.10"
|
|||||||
reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] }
|
reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] }
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
nu-ansi-term = "0.49.0"
|
nu-ansi-term = "0.49.0"
|
||||||
|
humantime = "2.1.0"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
|
@ -4,8 +4,8 @@ use enum_iterator::{all, Sequence};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
built_in_functions::string_functions, AbstractTree, BuiltInFunction, Format, Function, List,
|
built_in_functions::{json::json_functions, string::string_functions, Callable},
|
||||||
Map, Result, SyntaxNode, Type, Value,
|
AbstractTree, BuiltInFunction, Format, Function, List, Map, Result, SyntaxNode, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
static ARGS: OnceLock<Value> = OnceLock::new();
|
static ARGS: OnceLock<Value> = OnceLock::new();
|
||||||
@ -80,16 +80,18 @@ impl BuiltInValue {
|
|||||||
Value::Map(fs_context)
|
Value::Map(fs_context)
|
||||||
}),
|
}),
|
||||||
BuiltInValue::Json => JSON.get_or_init(|| {
|
BuiltInValue::Json => JSON.get_or_init(|| {
|
||||||
let json_context = Map::new();
|
let mut json_context = BTreeMap::new();
|
||||||
|
|
||||||
json_context
|
for json_function in json_functions() {
|
||||||
.set(
|
let key = json_function.name().to_string();
|
||||||
BuiltInFunction::JsonParse.name().to_string(),
|
let value =
|
||||||
Value::Function(Function::BuiltIn(BuiltInFunction::JsonParse)),
|
Value::Function(Function::BuiltIn(BuiltInFunction::Json(json_function)));
|
||||||
)
|
let r#type = value.r#type();
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
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::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
|
||||||
BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)),
|
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::{
|
use std::{
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
@ -10,13 +11,19 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{Error, Format, Map, Result, Type, Value};
|
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)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum BuiltInFunction {
|
pub enum BuiltInFunction {
|
||||||
AssertEqual,
|
AssertEqual,
|
||||||
FsRead,
|
FsRead,
|
||||||
JsonParse,
|
Json(Json),
|
||||||
Length,
|
Length,
|
||||||
Output,
|
Output,
|
||||||
RandomBoolean,
|
RandomBoolean,
|
||||||
@ -26,12 +33,12 @@ pub enum BuiltInFunction {
|
|||||||
String(StringFunction),
|
String(StringFunction),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuiltInFunction {
|
impl Callable for BuiltInFunction {
|
||||||
pub fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
BuiltInFunction::AssertEqual => "assert_equal",
|
BuiltInFunction::AssertEqual => "assert_equal",
|
||||||
BuiltInFunction::FsRead => "read",
|
BuiltInFunction::FsRead => "read",
|
||||||
BuiltInFunction::JsonParse => "parse",
|
BuiltInFunction::Json(json_function) => json_function.name(),
|
||||||
BuiltInFunction::Length => "length",
|
BuiltInFunction::Length => "length",
|
||||||
BuiltInFunction::Output => "output",
|
BuiltInFunction::Output => "output",
|
||||||
BuiltInFunction::RandomBoolean => "boolean",
|
BuiltInFunction::RandomBoolean => "boolean",
|
||||||
@ -42,11 +49,11 @@ impl BuiltInFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn r#type(&self) -> Type {
|
fn r#type(&self) -> Type {
|
||||||
match self {
|
match self {
|
||||||
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
|
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
|
||||||
BuiltInFunction::FsRead => Type::function(vec![Type::String], Type::String),
|
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::Length => Type::function(vec![Type::Collection], Type::Integer),
|
||||||
BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None),
|
BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None),
|
||||||
BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean),
|
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 {
|
match self {
|
||||||
BuiltInFunction::AssertEqual => {
|
BuiltInFunction::AssertEqual => {
|
||||||
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
Error::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||||
@ -75,13 +82,8 @@ impl BuiltInFunction {
|
|||||||
|
|
||||||
Ok(Value::string(file_content))
|
Ok(Value::string(file_content))
|
||||||
}
|
}
|
||||||
BuiltInFunction::JsonParse => {
|
BuiltInFunction::Json(json_function) => {
|
||||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
json_function.call(arguments, _source, _outer_context)
|
||||||
|
|
||||||
let string = arguments.first().unwrap().as_string()?;
|
|
||||||
let value = serde_json::from_str(string)?;
|
|
||||||
|
|
||||||
Ok(value)
|
|
||||||
}
|
}
|
||||||
BuiltInFunction::Length => {
|
BuiltInFunction::Length => {
|
||||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Error, List, Map, Result, Type, Value};
|
use crate::{Error, List, Map, Result, Type, Value};
|
||||||
|
|
||||||
|
use super::Callable;
|
||||||
|
|
||||||
pub fn string_functions() -> impl Iterator<Item = StringFunction> {
|
pub fn string_functions() -> impl Iterator<Item = StringFunction> {
|
||||||
all()
|
enum_iterator::all()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@ -41,8 +43,8 @@ pub enum StringFunction {
|
|||||||
Truncate,
|
Truncate,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StringFunction {
|
impl Callable for StringFunction {
|
||||||
pub fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
StringFunction::AsBytes => "as_bytes",
|
StringFunction::AsBytes => "as_bytes",
|
||||||
StringFunction::EndsWith => "ends_with",
|
StringFunction::EndsWith => "ends_with",
|
||||||
@ -77,7 +79,7 @@ impl StringFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn r#type(&self) -> Type {
|
fn r#type(&self) -> Type {
|
||||||
match self {
|
match self {
|
||||||
StringFunction::AsBytes => {
|
StringFunction::AsBytes => {
|
||||||
Type::function(vec![Type::String], Type::list(Type::Integer))
|
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 {
|
let value = match self {
|
||||||
StringFunction::AsBytes => {
|
StringFunction::AsBytes => {
|
||||||
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
|
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 crossterm::event::{KeyCode, KeyModifiers};
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use reedline::{
|
use reedline::{
|
||||||
default_emacs_keybindings, DefaultPrompt, DefaultPromptSegment, EditCommand, Emacs,
|
default_emacs_keybindings, ColumnarMenu, DefaultCompleter, DefaultHinter, EditCommand, Emacs,
|
||||||
Highlighter, Reedline, ReedlineEvent, Signal, SqliteBackedHistory, StyledText,
|
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};
|
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<()> {
|
fn run_shell(context: Map) -> Result<()> {
|
||||||
let mut interpreter = Interpreter::new(context.clone());
|
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();
|
let mut keybindings = default_emacs_keybindings();
|
||||||
|
|
||||||
keybindings.add_binding(
|
keybindings.add_binding(
|
||||||
@ -184,7 +214,7 @@ fn run_shell(context: Map) -> Result<()> {
|
|||||||
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
|
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
|
||||||
);
|
);
|
||||||
keybindings.add_binding(
|
keybindings.add_binding(
|
||||||
KeyModifiers::CONTROL,
|
KeyModifiers::NONE,
|
||||||
KeyCode::Tab,
|
KeyCode::Tab,
|
||||||
ReedlineEvent::UntilFound(vec![
|
ReedlineEvent::UntilFound(vec![
|
||||||
ReedlineEvent::Menu("completion_menu".to_string()),
|
ReedlineEvent::Menu("completion_menu".to_string()),
|
||||||
@ -192,7 +222,7 @@ fn run_shell(context: Map) -> Result<()> {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
keybindings.add_binding(
|
keybindings.add_binding(
|
||||||
KeyModifiers::NONE,
|
KeyModifiers::CONTROL,
|
||||||
KeyCode::Tab,
|
KeyCode::Tab,
|
||||||
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
|
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)
|
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
|
||||||
.expect("Error loading history."),
|
.expect("Error loading history."),
|
||||||
);
|
);
|
||||||
|
let hinter = Box::new(DefaultHinter::default());
|
||||||
let mut line_editor = Reedline::create()
|
let mut line_editor = Reedline::create()
|
||||||
.with_edit_mode(edit_mode)
|
.with_edit_mode(edit_mode)
|
||||||
.with_history(history)
|
.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 {
|
loop {
|
||||||
let sig = line_editor.read_line(&prompt);
|
let sig = line_editor.read_line(&prompt);
|
||||||
|
@ -5,7 +5,9 @@ use std::{
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Function {
|
pub enum Function {
|
||||||
@ -20,16 +22,6 @@ impl Function {
|
|||||||
built_in_function.call(arguments, source, outer_context)
|
built_in_function.call(arguments, source, outer_context)
|
||||||
}
|
}
|
||||||
Function::ContextDefined(function_node) => {
|
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)
|
function_node.call(arguments, source, outer_context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user