mirror of
https://github.com/liljencrantz/crush.git
synced 2024-11-20 19:21:22 +03:00
Misc fixes
This commit is contained in:
parent
99f77f03c4
commit
8215da478b
@ -453,7 +453,7 @@ fn signature_real(metadata: TokenStream, input: TokenStream) -> SignatureResult<
|
||||
#named_matchers
|
||||
#named_fallback
|
||||
(None, _value) => _unnamed.push_back((_value, _arg.location)),
|
||||
(Some(_name), _value) => return crate::lang::errors::argument_error(format!("Unknown argument name \"{}\"", _name), _location),
|
||||
(Some(_name), _value) => return crate::lang::errors::argument_error(format!("{}: Unexpected argument nameed \"{}\" with value of type {}", #command_name, _name, _value.value_type()), _location),
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,9 +466,9 @@ fn signature_real(metadata: TokenStream, input: TokenStream) -> SignatureResult<
|
||||
|
||||
let mut output = s.to_token_stream();
|
||||
output.extend(handler.into_token_stream());
|
||||
if struct_name.to_string() == "AllowedValuesStringSignature" {
|
||||
println!("{}", output.to_string());
|
||||
}
|
||||
// if struct_name.to_string() == "Sort" {
|
||||
// println!("{}", output.to_string());
|
||||
// }
|
||||
Ok(output)
|
||||
}
|
||||
_ => fail!(root.span(), "Expected a struct"),
|
||||
|
@ -71,7 +71,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||
"Logical operators (and and or)",
|
||||
Box::new(|env| {
|
||||
env.declare_condition_command(
|
||||
"__and__",
|
||||
"cond:__and__",
|
||||
and,
|
||||
"__and__ condition:(bool|command)... -> boolean",
|
||||
"True if all arguments are true",
|
||||
@ -85,7 +85,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||
)?;
|
||||
|
||||
env.declare_condition_command(
|
||||
"__or__",
|
||||
"cond:__or__",
|
||||
or,
|
||||
"__or__ condition:(bool|command)... -> boolean",
|
||||
"True if any argument is true",
|
||||
|
@ -1,47 +1,59 @@
|
||||
use signature::signature;
|
||||
use crate::lang::argument::Argument;
|
||||
use crate::lang::errors::{CrushResult, mandate};
|
||||
use crate::lang::command::Command;
|
||||
use crate::lang::command::OutputType::Unknown;
|
||||
use crate::lang::errors::{argument_error_legacy, CrushResult, mandate};
|
||||
use crate::lang::state::contexts::CommandContext;
|
||||
use crate::lang::value::Value;
|
||||
use crate::lang::data::r#struct::Struct;
|
||||
use crate::lang::pipe::pipe;
|
||||
use crate::lang::ordered_string_map::OrderedStringMap;
|
||||
use crate::lang::pipe::{pipe, Stream};
|
||||
use crate::lang::state::argument_vector::ArgumentVector;
|
||||
|
||||
pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
|
||||
#[signature(
|
||||
control.r#for,
|
||||
can_block = true,
|
||||
short = "Execute a command once for each element in a stream.",
|
||||
output = Unknown,
|
||||
example = "for i=$(host:procs) {echo $(\"Iterating over process {}\":format $i:name)}",
|
||||
example = "for i=$(seq 10) {echo $(\"Lap #{}\":format $i)}")]
|
||||
pub struct For {
|
||||
#[named()]
|
||||
iterator: OrderedStringMap<Stream>,
|
||||
body: Command,
|
||||
}
|
||||
|
||||
fn r#for(mut context: CommandContext) -> CrushResult<()> {
|
||||
let (sender, receiver) = pipe();
|
||||
|
||||
context.arguments.check_len(2)?;
|
||||
|
||||
if context.arguments.len() != 2 {
|
||||
return argument_error_legacy("Expected two parameters: A stream and a command");
|
||||
}
|
||||
let location = context.arguments[0].location;
|
||||
let body = context.arguments.command(1)?;
|
||||
let iter = context.arguments.remove(0);
|
||||
let name = iter.argument_type;
|
||||
let mut input = mandate(iter.value.stream()?, "Expected a stream")?;
|
||||
let mut cfg = For::parse(context.remove_arguments(), context.global_state.printer())?;
|
||||
|
||||
if cfg.iterator.len() != 1 {
|
||||
return argument_error_legacy("Expected exactly one stream to iterate over");
|
||||
}
|
||||
|
||||
let (name, mut input) = cfg.iterator.drain().next().unwrap();
|
||||
|
||||
while let Ok(line) = input.read() {
|
||||
let env = context.scope.create_child(&context.scope, true);
|
||||
let arguments = match &name {
|
||||
None => Vec::from(line)
|
||||
.drain(..)
|
||||
.zip(input.types().iter())
|
||||
.map(|(c, t)| Argument::named(&t.name, c, location))
|
||||
.collect(),
|
||||
Some(var_name) => {
|
||||
if input.types().len() == 1 {
|
||||
vec![Argument::new(
|
||||
Some(var_name.clone()),
|
||||
Vec::from(line).remove(0),
|
||||
location,
|
||||
)]
|
||||
} else {
|
||||
vec![Argument::new(
|
||||
Some(var_name.clone()),
|
||||
Value::Struct(Struct::from_vec(Vec::from(line), input.types().to_vec())),
|
||||
location,
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
let vvv = if input.types().len() == 1 {
|
||||
Vec::from(line).remove(0)
|
||||
} else {
|
||||
Value::Struct(Struct::from_vec(Vec::from(line), input.types().to_vec()))
|
||||
};
|
||||
body.eval(context.empty().with_scope(env.clone()).with_args(arguments, None).with_output(sender.clone()))?;
|
||||
|
||||
let arguments =
|
||||
vec![Argument::new(
|
||||
Some(name.clone()),
|
||||
vvv,
|
||||
location,
|
||||
)];
|
||||
|
||||
cfg.body.eval(context.empty().with_scope(env.clone()).with_args(arguments, None).with_output(sender.clone()))?;
|
||||
if env.is_stopped() {
|
||||
context.output.send(receiver.recv()?)?;
|
||||
break;
|
||||
|
@ -122,22 +122,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||
r#if::If::declare(env)?;
|
||||
r#while::While::declare(env)?;
|
||||
r#loop::Loop::declare(env)?;
|
||||
|
||||
env.declare_condition_command(
|
||||
"for",
|
||||
r#for::r#for,
|
||||
"for [name=](table_input_stream|table|dict|list) body:command",
|
||||
"Execute body once for every element in iterable.",
|
||||
Some(
|
||||
r#" Example:
|
||||
|
||||
for $(seq 10) {
|
||||
echo $("Lap #{}":format $value)
|
||||
}"#,
|
||||
),
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
r#for::For::declare(env)?;
|
||||
cmd::Cmd::declare(env)?;
|
||||
Break::declare(env)?;
|
||||
timeit::TimeIt::declare(env)?;
|
||||
|
@ -313,7 +313,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||
)?;
|
||||
crush.create_namespace(
|
||||
"byte_unit",
|
||||
"Formating style for table columns containing byte size",
|
||||
"Formating style for table columns containing byte size.",
|
||||
Box::new(move |env| {
|
||||
byte_unit::List::declare(env)?;
|
||||
byte_unit::Get::declare(env)?;
|
||||
|
@ -29,7 +29,7 @@ mod yaml;
|
||||
can_block = false,
|
||||
short = "Return value",
|
||||
output = Known(ValueType::Any),
|
||||
example = "val val",
|
||||
example = "val $val",
|
||||
long = "This command is useful if you want to pass a command as input in\n a pipeline instead of executing it. It is different from the echo command\n in that val sends the value through the pipeline, whereas echo prints it to screen."
|
||||
)]
|
||||
struct Val {
|
||||
|
@ -221,7 +221,7 @@ fn max(context: CommandContext) -> CrushResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! mul_function {
|
||||
macro_rules! prod_function {
|
||||
($name:ident, $var_type:ident, $var_initializer:expr, $value_type:ident) => {
|
||||
fn $name(mut s: Stream, column: usize) -> CrushResult<Value> {
|
||||
let mut res: $var_type = $var_initializer;
|
||||
@ -236,25 +236,25 @@ macro_rules! mul_function {
|
||||
};
|
||||
}
|
||||
|
||||
mul_function!(mul_int, i128, 1, Integer);
|
||||
mul_function!(mul_float, f64, 1.0, Float);
|
||||
prod_function!(prod_int, i128, 1, Integer);
|
||||
prod_function!(prod_float, f64, 1.0, Float);
|
||||
|
||||
#[signature(
|
||||
stream.mul,
|
||||
stream.prod,
|
||||
short = "Calculate the product for the specific column across all rows.",
|
||||
example = "seq 5 10 | mul")]
|
||||
pub struct Mul {
|
||||
example = "seq 5 10 | prod")]
|
||||
pub struct Prod {
|
||||
field: Option<String>,
|
||||
}
|
||||
|
||||
fn mul(context: CommandContext) -> CrushResult<()> {
|
||||
fn prod(context: CommandContext) -> CrushResult<()> {
|
||||
match context.input.recv()?.stream()? {
|
||||
Some(input) => {
|
||||
let cfg = Mul::parse(context.arguments, &context.global_state.printer())?;
|
||||
let cfg = Prod::parse(context.arguments, &context.global_state.printer())?;
|
||||
let column = parse(input.types(), cfg.field)?;
|
||||
match &input.types()[column].cell_type {
|
||||
ValueType::Integer => context.output.send(mul_int(input, column)?),
|
||||
ValueType::Float => context.output.send(mul_float(input, column)?),
|
||||
ValueType::Integer => context.output.send(prod_int(input, column)?),
|
||||
ValueType::Float => context.output.send(prod_float(input, column)?),
|
||||
t => argument_error_legacy(
|
||||
&format!("Can't calculate product of elements of type {}", t),
|
||||
),
|
||||
|
@ -9,7 +9,7 @@ use crate::lang::command::OutputType::Known;
|
||||
stream.count,
|
||||
short = "Count the number of rows in the input.",
|
||||
output = Known(ValueType::Integer),
|
||||
example = "proc:list | count # Number of processes on the system")]
|
||||
example = "host:procs | count # Number of processes on the system")]
|
||||
pub struct Count {}
|
||||
|
||||
pub fn count(context: CommandContext) -> CrushResult<()> {
|
||||
|
@ -19,7 +19,7 @@ pub struct Drop {
|
||||
}
|
||||
|
||||
fn drop(context: CommandContext) -> CrushResult<()> {
|
||||
let cfg: Drop = Drop::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||
let cfg = Drop::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||
match context.input.recv()?.stream()? {
|
||||
Some(mut input) => {
|
||||
let t = input.types();
|
||||
|
@ -42,7 +42,7 @@ fn run(
|
||||
}
|
||||
|
||||
pub fn each(context: CommandContext) -> CrushResult<()> {
|
||||
let cfg: Each = Each::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||
let cfg = Each::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||
let location = context.arguments[0].location;
|
||||
context.output.send(Value::Empty)?;
|
||||
|
||||
|
@ -21,7 +21,7 @@ pub struct Enumerate {
|
||||
}
|
||||
|
||||
fn enumerate(context: CommandContext) -> CrushResult<()> {
|
||||
let cfg: Enumerate = Enumerate::parse(context.arguments, &context.global_state.printer())?;
|
||||
let cfg = Enumerate::parse(context.arguments, &context.global_state.printer())?;
|
||||
match context.input.recv()?.stream()? {
|
||||
Some(mut input) => {
|
||||
let mut output_type = vec![
|
||||
|
@ -136,7 +136,7 @@ fn create_worker_thread(
|
||||
}
|
||||
|
||||
pub fn group(mut context: CommandContext) -> CrushResult<()> {
|
||||
let cfg: Group = Group::parse(context.remove_arguments(), &context.global_state.printer())?;
|
||||
let cfg = Group::parse(context.remove_arguments(), &context.global_state.printer())?;
|
||||
let mut input = mandate(
|
||||
context.input.recv()?.stream()?,
|
||||
"Expected input to be a stream",
|
||||
|
@ -90,7 +90,7 @@ fn get_output_type(left_type: &[ColumnType], right_type: &[ColumnType], right_ke
|
||||
example = "join user=(files) name=(user:list)")]
|
||||
pub struct Join {
|
||||
#[named()]
|
||||
#[description("Field to join")]
|
||||
#[description("Fields to join")]
|
||||
join: OrderedStringMap<Stream>,
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||
aggregation::Avg::declare(env)?;
|
||||
aggregation::Min::declare(env)?;
|
||||
aggregation::Max::declare(env)?;
|
||||
aggregation::Mul::declare(env)?;
|
||||
aggregation::Prod::declare(env)?;
|
||||
aggregation::First::declare(env)?;
|
||||
aggregation::Last::declare(env)?;
|
||||
env.declare_command(
|
||||
|
@ -11,7 +11,8 @@ use crate::lang::command::OutputType::Passthrough;
|
||||
stream.uniq,
|
||||
output = Passthrough,
|
||||
short = "Only output the first row if multiple rows has the same value for the specified column",
|
||||
example = "host:procs | sort user | uniq user")]
|
||||
long = "If no column is given, the entire rows are compared.",
|
||||
example = "host:procs | uniq user")]
|
||||
pub struct Uniq {
|
||||
field: Option<String>,
|
||||
}
|
||||
|
@ -45,15 +45,38 @@ pub enum Node {
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn to_command(self) -> CommandNode {
|
||||
pub fn val(l: Location) -> Node {
|
||||
Node::GetAttr(
|
||||
Box::from(Node::GetAttr(
|
||||
Box::from(Node::Identifier(TrackedString::new("global", l))),
|
||||
TrackedString::new("io", l))),
|
||||
TrackedString::new("val", l))
|
||||
}
|
||||
|
||||
pub fn expression_to_command(self) -> CommandNode {
|
||||
let l = self.location();
|
||||
match self {
|
||||
Node::Substitution(n) if n.commands.len() == 1 => {
|
||||
n.commands[0].clone()
|
||||
}
|
||||
_ => CommandNode {
|
||||
expressions: vec![self],
|
||||
location: l,
|
||||
_ => {
|
||||
CommandNode {
|
||||
expressions: vec![Node::val(self.location()), self],
|
||||
location: l,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expression_to_job(self) -> JobNode {
|
||||
if let Node::Substitution(s) = self {
|
||||
s
|
||||
} else {
|
||||
let location = self.location();
|
||||
let expressions = vec![Node::val(location), self];
|
||||
JobNode {
|
||||
commands: vec![CommandNode { expressions, location }],
|
||||
location,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -188,7 +211,7 @@ pub struct CommandNode {
|
||||
|
||||
impl CommandNode {
|
||||
pub fn compile(&self, env: &Scope) -> CrushResult<CommandInvocation> {
|
||||
if let Some(c) = self.expressions[0].compile_as_command(env)? {
|
||||
if let Some(c) = self.expressions[0].compile_as_special_command(env)? {
|
||||
if self.expressions.len() == 1 {
|
||||
Ok(c)
|
||||
} else {
|
||||
@ -291,7 +314,7 @@ impl Node {
|
||||
|
||||
Node::GetItem(a, o) => ValueDefinition::JobDefinition(
|
||||
Job::new(vec![self
|
||||
.compile_as_command(env)?
|
||||
.compile_as_special_command(env)?
|
||||
.unwrap()],
|
||||
a.location().union(o.location()),
|
||||
)),
|
||||
@ -417,7 +440,7 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_as_command(&self, env: &Scope) -> CrushResult<Option<CommandInvocation>> {
|
||||
pub fn compile_as_special_command(&self, env: &Scope) -> CrushResult<Option<CommandInvocation>> {
|
||||
match self {
|
||||
Node::Assignment(target, _style, op, value) => {
|
||||
Node::compile_standalone_assignment(target, op, value, env)
|
||||
|
@ -140,6 +140,9 @@ struct SimpleCommand {
|
||||
arguments: Vec<ArgumentDescription>,
|
||||
}
|
||||
|
||||
/**
|
||||
A command that can block iff any of its arguments can block, e.g. `and` or `or`.
|
||||
*/
|
||||
struct ConditionCommand {
|
||||
call: fn(context: CommandContext) -> CrushResult<()>,
|
||||
full_name: Vec<String>,
|
||||
|
@ -147,16 +147,7 @@ fn eval_other(
|
||||
context: JobContext,
|
||||
location: Location,
|
||||
) -> CrushResult<Option<ThreadId>> {
|
||||
if local_arguments.len() == 0 {
|
||||
eval_command(
|
||||
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
||||
None,
|
||||
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(value, location))],
|
||||
context,
|
||||
)
|
||||
} else {
|
||||
error(&format!("{} is not a command.", value))
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_type(
|
||||
|
@ -34,20 +34,11 @@ Job: JobNode = {
|
||||
};
|
||||
|
||||
Expr: Box<Node> = {
|
||||
<j:JobExpr> => j.to_node()
|
||||
};
|
||||
|
||||
JobExpr: Box<JobNode> = {
|
||||
<c:AssignmentExpr> => {
|
||||
let l = c.location();
|
||||
Box::from(JobNode{
|
||||
commands: vec![c.to_command()],
|
||||
location: l,
|
||||
})
|
||||
},
|
||||
<mut j:JobExpr> Pipe <c:AssignmentExpr> => {
|
||||
j.commands.push(c.to_command());
|
||||
j
|
||||
AssignmentExpr,
|
||||
<mut j:Expr> Pipe <c:AssignmentExpr> => {
|
||||
let mut jj = j.expression_to_job();
|
||||
jj.commands.push(c.expression_to_command());
|
||||
Box::from(Node::Substitution(jj))
|
||||
},
|
||||
}
|
||||
|
||||
@ -70,21 +61,17 @@ ComparisonExpr: Box<Node> = {
|
||||
}
|
||||
|
||||
CommandExpr: Box<Node> = {
|
||||
<c:TermExpr> => c,
|
||||
<lt:TermExpr> <t:Plus> <rt:TermExpr> => {
|
||||
operator_method("__add__", t, lt, rt)
|
||||
},
|
||||
<lt:TermExpr> <t:Minus> <rt:TermExpr> => {
|
||||
operator_method("__sub__", t, lt, rt)
|
||||
},
|
||||
};
|
||||
TermExpr,
|
||||
<lt: CommandExpr> <t:Plus> <rt:TermExpr> => operator_method("__add__", t, lt, rt),
|
||||
<lt: CommandExpr> <t:Minus> <rt:TermExpr> => operator_method("__sub__", t, lt, rt),
|
||||
};
|
||||
|
||||
TermExpr: Box<Node> = {
|
||||
FactorExpr,
|
||||
<l: @L> <lt:FactorExpr> <op:Star> <rt:FactorExpr> <r: @R> => {
|
||||
<l: @L> <lt:TermExpr> <op:Star> <rt:FactorExpr> <r: @R> => {
|
||||
operator_method("__mul__", op, lt, rt)
|
||||
},
|
||||
<l: @L> <lt:FactorExpr> <op:Slash> <rt:FactorExpr> <r: @R> => {
|
||||
<l: @L> <lt:TermExpr> <op:Slash> <rt:FactorExpr> <r: @R> => {
|
||||
operator_method("__div__", op, lt, rt)
|
||||
},
|
||||
}
|
||||
@ -108,31 +95,35 @@ ItemExpr: Box<Node> = {
|
||||
<f:Float> => Node::float(f),
|
||||
<i: ItemExpr> GetItemStart <e: Assignment> GetItemEnd => Box::from(Node::GetItem(i, e)),
|
||||
<i: ItemExpr> MemberOperator <l: Identifier> => Box::from(Node::GetAttr(i, l.into())),
|
||||
<i: ItemExpr> ExprModeStart <mut v: OptExpressionList> SubEnd => {
|
||||
<i: ItemExpr> ExprModeStart <mut ov: OptExpressionList> SubEnd => {
|
||||
let l = i.location();
|
||||
v.insert(0, *i);
|
||||
let c = CommandNode {
|
||||
expressions: v,
|
||||
location: l,
|
||||
let mut v = match ov {
|
||||
Some(vv) => vv,
|
||||
None => vec![],
|
||||
};
|
||||
v.insert(0, *i);
|
||||
let c = CommandNode {
|
||||
expressions: v,
|
||||
location: l,
|
||||
};
|
||||
|
||||
Box::from(Node::Substitution(
|
||||
JobNode{
|
||||
commands: vec![c],
|
||||
location: l,
|
||||
}
|
||||
))
|
||||
Box::from(Node::Substitution(
|
||||
JobNode{
|
||||
commands: vec![c],
|
||||
location: l,
|
||||
}
|
||||
))
|
||||
},
|
||||
}
|
||||
|
||||
OptExpressionList: Vec<Node> = {
|
||||
=> vec![],
|
||||
ExpressionList,
|
||||
OptExpressionList: Option<Vec<Node>> = {
|
||||
=> None,
|
||||
<e: ExpressionList> => Some(e),
|
||||
}
|
||||
|
||||
ExpressionList: Vec<Node> = {
|
||||
<e: Expr> => vec![*e],
|
||||
<mut l: ExpressionList> Separator <e: Expr> => {
|
||||
<e: AssignmentExpr> => vec![*e],
|
||||
<mut l: ExpressionList> Separator <e: AssignmentExpr> => {
|
||||
l.push(*e);
|
||||
l
|
||||
},
|
||||
@ -226,15 +217,7 @@ Item: Box<Node> = {
|
||||
<i: Item> MemberOperator <start: @L> <l: StringOrGlob> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(l))),
|
||||
JobStart Separator? <s: Signature> <l: JobListWithoutSeparator> JobEnd => Box::from(Node::Closure(s, l)),
|
||||
SubStart <j:Job> SubEnd => Box::from(Node::Substitution(j)),
|
||||
<l: @L>ExprModeStart <e:Expr> SubEnd <r: @R> => Box::from(
|
||||
Node::Substitution(
|
||||
JobNode{
|
||||
commands: vec![
|
||||
CommandNode{
|
||||
expressions: vec![*e],
|
||||
location: Location::new(l, r)
|
||||
}],
|
||||
location: Location::new(l, r)})),
|
||||
<l: @L>ExprModeStart <e:Expr> SubEnd <r: @R> => Box::from(Node::Substitution(e.expression_to_job())),
|
||||
}
|
||||
|
||||
AssignmentOperator: TrackedString = {
|
||||
|
@ -131,7 +131,7 @@ pub struct CommandContext {
|
||||
|
||||
impl CommandContext {
|
||||
/**
|
||||
Return a new Command context with the same scope and state, but empty I/O and arguments.
|
||||
Return an empty new Command context with the specified scope and state.
|
||||
*/
|
||||
pub fn new(scope: &Scope, state: &GlobalState) -> CommandContext {
|
||||
CommandContext {
|
||||
@ -147,6 +147,8 @@ impl CommandContext {
|
||||
|
||||
/**
|
||||
Clear the argument vector and return the original.
|
||||
|
||||
This is useful when you want to parse the argument vector without consuming the whole context.
|
||||
*/
|
||||
pub fn remove_arguments(&mut self) -> Vec<Argument> {
|
||||
let mut tmp = Vec::new(); // This does not cause a memory allocation
|
||||
|
10
todo
10
todo
@ -1,4 +1,8 @@
|
||||
Todo:
|
||||
The automatic argument parser seems to allow stray arguemnts, e.g. `val 1 2 3`
|
||||
Math commands for command mode
|
||||
Rewrite all tests for new syntax
|
||||
New regex literal syntax
|
||||
Add saner default prompt
|
||||
Add default syntax highlighting config
|
||||
Add some way to extract the definition from a closure
|
||||
@ -19,8 +23,6 @@ Avoid infinite loops when printing structs that reference each other
|
||||
__getattr__ support?
|
||||
fix dynamic loading deadlocks
|
||||
tab completions for external commands
|
||||
more file info: size, permissions, user, group, modified, created, type. Move to methods or add more to stat?
|
||||
Rename len to size in file:stat?
|
||||
Proper syntax for background jobs
|
||||
More shell-like syntax for background jobs
|
||||
Make IFS configurable for cmd command
|
||||
Fix ^C and ^Z
|
||||
Add signal handlers to fix ^C and ^Z during regular execution
|
||||
|
Loading…
Reference in New Issue
Block a user