Skip to content

Commit 3a62b22

Browse files
Add candid assist feature (#83)
* add candid assist * use console * fix * fix * fix * readme
1 parent 3f5be4d commit 3a62b22

File tree

9 files changed

+301
-165
lines changed

9 files changed

+301
-165
lines changed

Cargo.lock

Lines changed: 233 additions & 128 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ic-repl"
3-
version = "0.6.1"
3+
version = "0.6.2"
44
authors = ["DFINITY Team"]
55
edition = "2021"
66
default-run = "ic-repl"
@@ -18,9 +18,8 @@ candid = { version = "0.10", features = ["all"] }
1818
candid_parser = { version = "0.1", features = ["all"] }
1919
rustyline = "13.0"
2020
rustyline-derive = "0.10"
21-
ansi_term = "0.12"
21+
console = "0.15"
2222
pretty_assertions = "1.4"
23-
terminal_size = "0.3"
2423
codespan-reporting = "0.11"
2524
pretty = "0.12"
2625
pem = "3.0"
@@ -47,4 +46,3 @@ qrcode = "0.13"
4746
image = { version = "0.24", default-features = false, features = ["png"] }
4847
libflate = "2.0"
4948
base64 = "0.21"
50-

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ ic-repl [--replica [local|ic|url] | --offline [--format [json|ascii|png]]] --con
1818
| identity <id> (<text> | record { slot_index = <nat>; key_id = <text> })? // switch to identity <id>, with optional pem file or HSM config
1919
| function <id> ( <id>,* ) { <command>;* } // define a function
2020
<exp> :=
21-
| <candid val> // any candid value
22-
| <var> <transformer>* // variable with optional transformers
23-
| fail <exp> // convert error message as text
24-
| call (as <name>)? <name> . <name> ( <exp>,* ) // call a canister method, and store the result as a single value
25-
| encode (<name> . <name>)? ( <exp>,* ) // encode candid arguments as a blob value. canister.__init_args represents init args
26-
| decode (as <name> . <name>)? <exp> // decode blob as candid values
27-
| <id> ( <exp>,* ) // function application
21+
| <candid val> // any candid value
22+
| <var> <transformer>* // variable with optional transformers
23+
| fail <exp> // convert error message as text
24+
| call (as <name>)? <name> . <name> (( <exp>,* ))? // call a canister method, and store the result as a single value
25+
| encode (<name> . <name>)? (( <exp>,* ))? // encode candid arguments as a blob value. canister.__init_args represents init args
26+
| decode (as <name> . <name>)? <exp> // decode blob as candid values
27+
| <id> ( <exp>,* ) // function application
2828
<var> :=
2929
| <id> // variable name
3030
| _ // previous eval of exp is bind to `_`

src/command.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use pretty_assertions::{assert_eq, assert_ne};
1010
use std::ops::Range;
1111
use std::sync::Arc;
1212
use std::time::Instant;
13-
use terminal_size::{terminal_size, Width};
1413

1514
#[derive(Debug, Clone)]
1615
pub struct Commands(pub Vec<(Command, Range<usize>)>);
@@ -95,11 +94,7 @@ impl Command {
9594
let v = val.eval(helper)?;
9695
let duration = time.elapsed();
9796
bind_value(helper, "_".to_string(), v, is_call, true);
98-
let width = if let Some((Width(w), _)) = terminal_size() {
99-
w as usize
100-
} else {
101-
80
102-
};
97+
let width = console::Term::stdout().size().1 as usize;
10398
println!("{:>width$}", format!("({duration:.2?})"), width = width);
10499
}
105100
Command::Identity(id, config) => {
@@ -157,9 +152,7 @@ impl Command {
157152
let path = resolve_path(&std::env::current_dir()?, &file);
158153
let file = std::fs::File::create(path)?;
159154
let mut writer = BufWriter::new(&file);
160-
//for item in helper.history.iter() {
161155
for (id, val) in helper.env.0.iter() {
162-
//writeln!(&mut writer, "{};", item)?;
163156
writeln!(&mut writer, "let {id} = {val};")?;
164157
}
165158
}

src/exp.rs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub enum Exp {
2121
AnnVal(Box<Exp>, Type),
2222
Call {
2323
method: Option<Method>,
24-
args: Vec<Exp>,
24+
args: Option<Vec<Exp>>,
2525
mode: CallMode,
2626
},
2727
Decode {
@@ -403,11 +403,15 @@ impl Exp {
403403
args_to_value(args)
404404
}
405405
Exp::Call { method, args, mode } => {
406-
let mut res = Vec::with_capacity(args.len());
407-
for arg in args.into_iter() {
408-
res.push(arg.eval(helper)?);
409-
}
410-
let args = IDLArgs { args: res };
406+
let args = if let Some(args) = args {
407+
let mut res = Vec::with_capacity(args.len());
408+
for arg in args.into_iter() {
409+
res.push(arg.eval(helper)?);
410+
}
411+
Some(IDLArgs { args: res })
412+
} else {
413+
None
414+
};
411415
let opt_info = if let Some(method) = &method {
412416
let is_encode = matches!(mode, CallMode::Encode);
413417
Some(method.get_info(helper, is_encode)?)
@@ -419,9 +423,33 @@ impl Exp {
419423
..
420424
}) = &opt_info
421425
{
426+
let args = if let Some(args) = args {
427+
args
428+
} else {
429+
use candid_parser::assist::{input_args, Context};
430+
let mut ctx = Context::new(env.clone());
431+
let principals = helper.env.dump_principals();
432+
let mut completion = BTreeMap::new();
433+
completion.insert("principal".to_string(), principals);
434+
ctx.set_completion(completion);
435+
let args = input_args(&ctx, &func.args)?;
436+
// Ideally, we should store the args in helper and call editor.readline_with_initial to display
437+
// the full command in the editor. The tricky part is to know where to insert the args in text.
438+
eprintln!("Generated arguments: {}", args);
439+
eprintln!("Do you want to send this message? [y/N]");
440+
let mut input = String::new();
441+
std::io::stdin().read_line(&mut input)?;
442+
if !["y", "yes"].contains(&input.to_lowercase().trim()) {
443+
return Err(anyhow!("Abort"));
444+
}
445+
args
446+
};
422447
args.to_bytes_with_types(env, &func.args)?
423448
} else {
424-
args.to_bytes()?
449+
if args.is_none() {
450+
return Err(anyhow!("cannot get method type, please provide arguments"));
451+
}
452+
args.unwrap().to_bytes()?
425453
};
426454
match mode {
427455
CallMode::Encode => IDLValue::Blob(bytes),

src/grammar.lalrpop

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extern {
2727
"service" => Token::Service,
2828
"oneway" => Token::Oneway,
2929
"query" => Token::Query,
30+
"composite_query" => Token::CompositeQuery,
3031
"blob" => Token::Blob,
3132
"type" => Token::Type,
3233
"import" => Token::Import,
@@ -96,9 +97,9 @@ pub Exp: Exp = {
9697
Arg => <>,
9798
Variable => <>,
9899
"fail" <Exp> => Exp::Fail(Box::new(<>)),
99-
"call" <method:Method> <args:Exps> => Exp::Call{method:Some(method), args, mode: CallMode::Call},
100-
"call" "as" <proxy:Name> <method:Method> <args:Exps> => Exp::Call{method:Some(method), args, mode: CallMode::Proxy(proxy)},
101-
"encode" <method:Method?> <args:Exps> => Exp::Call{method, args, mode: CallMode::Encode},
100+
"call" <method:Method> <args:Exps?> => Exp::Call{method:Some(method), args, mode: CallMode::Call},
101+
"call" "as" <proxy:Name> <method:Method> <args:Exps?> => Exp::Call{method:Some(method), args, mode: CallMode::Proxy(proxy)},
102+
"encode" <method:Method?> <args:Exps?> => Exp::Call{method, args, mode: CallMode::Encode},
102103
"decode" <method:("as" <Method>)?> <blob:Exp> => Exp::Decode{method, blob:Box::new(blob)},
103104
<func:"id"> "(" <args:SepBy<Exp, ",">> ")" => Exp::Apply(func, args),
104105
}
@@ -329,6 +330,7 @@ MethTyp: Binding = {
329330
FuncMode: FuncMode = {
330331
"oneway" => FuncMode::Oneway,
331332
"query" => FuncMode::Query,
333+
"composite_query" => FuncMode::CompositeQuery,
332334
}
333335

334336
// Also allows trailing separator

src/helper.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::exp::Exp;
22
use crate::token::{Token, Tokenizer};
33
use crate::utils::{random_value, str_to_principal};
4-
use ansi_term::Color;
54
use candid::{
65
types::value::{IDLField, IDLValue, VariantValue},
76
types::{Function, Label, Type, TypeInner},
@@ -90,7 +89,6 @@ pub struct MyHelper {
9089
pub env: Env,
9190
pub func_env: FuncEnv,
9291
pub base_path: std::path::PathBuf,
93-
pub history: Vec<String>,
9492
pub messages: RefCell<Vec<crate::offline::IngressWithStatus>>,
9593
}
9694

@@ -102,7 +100,6 @@ impl MyHelper {
102100
hinter: HistoryHinter {},
103101
colored_prompt: "".to_owned(),
104102
validator: MatchingBracketValidator::new(),
105-
history: Vec::new(),
106103
config: Configs::from_dhall("{=}").unwrap(),
107104
canister_map: self.canister_map.clone(),
108105
identity_map: self.identity_map.clone(),
@@ -130,7 +127,6 @@ impl MyHelper {
130127
env: Env::default(),
131128
func_env: FuncEnv::default(),
132129
base_path: std::env::current_dir().unwrap(),
133-
history: Vec::new(),
134130
messages: Vec::new().into(),
135131
agent,
136132
agent_url,
@@ -448,7 +444,7 @@ impl Highlighter for MyHelper {
448444
}
449445

450446
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
451-
let s = format!("{}", Color::White.dimmed().paint(hint));
447+
let s = format!("{}", console::style(hint).black().bright());
452448
Owned(s)
453449
}
454450

@@ -577,6 +573,19 @@ pub fn find_init_args(env: &TypeEnv, actor: &Type) -> Option<Vec<Type>> {
577573
}
578574
}
579575

576+
impl Env {
577+
pub fn dump_principals(&self) -> BTreeMap<String, String> {
578+
self.0
579+
.iter()
580+
.filter_map(|(name, value)| match value {
581+
IDLValue::Principal(id) => Some((name, id)),
582+
_ => None,
583+
})
584+
.map(|(name, id)| (name.clone(), id.to_text()))
585+
.collect()
586+
}
587+
}
588+
580589
#[test]
581590
fn test_partial_parse() -> anyhow::Result<()> {
582591
use candid_parser::parse_idl_value;

src/main.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use ansi_term::Color;
21
use clap::Parser;
32
use ic_agent::agent::http_transport::ReqwestTransport;
43
use ic_agent::Agent;
@@ -93,14 +92,14 @@ fn repl(opts: Opts) -> anyhow::Result<()> {
9392
loop {
9493
let identity = &rl.helper().unwrap().current_identity;
9594
let p = format!("{identity}@{replica} {count}> ");
96-
rl.helper_mut().unwrap().colored_prompt = format!("{}", Color::Green.bold().paint(&p));
95+
rl.helper_mut().unwrap().colored_prompt =
96+
format!("{}", console::style(&p).green().bold());
9797
let input = rl.readline(&p);
9898
match input {
9999
Ok(line) => {
100100
rl.add_history_entry(&line)?;
101101
unwrap(pretty_parse::<Command>("stdin", &line), |cmd| {
102102
let helper = rl.helper_mut().unwrap();
103-
helper.history.push(line.clone());
104103
unwrap(cmd.run(helper), |_| {});
105104
});
106105
}

src/token.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub enum Token {
4949
Oneway,
5050
#[token("query")]
5151
Query,
52+
#[token("composite_query")]
53+
CompositeQuery,
5254
#[token("blob")]
5355
Blob,
5456
#[token("type")]

0 commit comments

Comments
 (0)