Skip to content

Commit

Permalink
Add candid assist feature (#83)
Browse files Browse the repository at this point in the history
* add candid assist

* use console

* fix

* fix

* fix

* readme
  • Loading branch information
chenyan-dfinity authored Jan 3, 2024
1 parent 3f5be4d commit 3a62b22
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 165 deletions.
361 changes: 233 additions & 128 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ic-repl"
version = "0.6.1"
version = "0.6.2"
authors = ["DFINITY Team"]
edition = "2021"
default-run = "ic-repl"
Expand All @@ -18,9 +18,8 @@ candid = { version = "0.10", features = ["all"] }
candid_parser = { version = "0.1", features = ["all"] }
rustyline = "13.0"
rustyline-derive = "0.10"
ansi_term = "0.12"
console = "0.15"
pretty_assertions = "1.4"
terminal_size = "0.3"
codespan-reporting = "0.11"
pretty = "0.12"
pem = "3.0"
Expand All @@ -47,4 +46,3 @@ qrcode = "0.13"
image = { version = "0.24", default-features = false, features = ["png"] }
libflate = "2.0"
base64 = "0.21"

14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ ic-repl [--replica [local|ic|url] | --offline [--format [json|ascii|png]]] --con
| identity <id> (<text> | record { slot_index = <nat>; key_id = <text> })? // switch to identity <id>, with optional pem file or HSM config
| function <id> ( <id>,* ) { <command>;* } // define a function
<exp> :=
| <candid val> // any candid value
| <var> <transformer>* // variable with optional transformers
| fail <exp> // convert error message as text
| call (as <name>)? <name> . <name> ( <exp>,* ) // call a canister method, and store the result as a single value
| encode (<name> . <name>)? ( <exp>,* ) // encode candid arguments as a blob value. canister.__init_args represents init args
| decode (as <name> . <name>)? <exp> // decode blob as candid values
| <id> ( <exp>,* ) // function application
| <candid val> // any candid value
| <var> <transformer>* // variable with optional transformers
| fail <exp> // convert error message as text
| call (as <name>)? <name> . <name> (( <exp>,* ))? // call a canister method, and store the result as a single value
| encode (<name> . <name>)? (( <exp>,* ))? // encode candid arguments as a blob value. canister.__init_args represents init args
| decode (as <name> . <name>)? <exp> // decode blob as candid values
| <id> ( <exp>,* ) // function application
<var> :=
| <id> // variable name
| _ // previous eval of exp is bind to `_`
Expand Down
9 changes: 1 addition & 8 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use pretty_assertions::{assert_eq, assert_ne};
use std::ops::Range;
use std::sync::Arc;
use std::time::Instant;
use terminal_size::{terminal_size, Width};

#[derive(Debug, Clone)]
pub struct Commands(pub Vec<(Command, Range<usize>)>);
Expand Down Expand Up @@ -95,11 +94,7 @@ impl Command {
let v = val.eval(helper)?;
let duration = time.elapsed();
bind_value(helper, "_".to_string(), v, is_call, true);
let width = if let Some((Width(w), _)) = terminal_size() {
w as usize
} else {
80
};
let width = console::Term::stdout().size().1 as usize;
println!("{:>width$}", format!("({duration:.2?})"), width = width);
}
Command::Identity(id, config) => {
Expand Down Expand Up @@ -157,9 +152,7 @@ impl Command {
let path = resolve_path(&std::env::current_dir()?, &file);
let file = std::fs::File::create(path)?;
let mut writer = BufWriter::new(&file);
//for item in helper.history.iter() {
for (id, val) in helper.env.0.iter() {
//writeln!(&mut writer, "{};", item)?;
writeln!(&mut writer, "let {id} = {val};")?;
}
}
Expand Down
42 changes: 35 additions & 7 deletions src/exp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum Exp {
AnnVal(Box<Exp>, Type),
Call {
method: Option<Method>,
args: Vec<Exp>,
args: Option<Vec<Exp>>,
mode: CallMode,
},
Decode {
Expand Down Expand Up @@ -403,11 +403,15 @@ impl Exp {
args_to_value(args)
}
Exp::Call { method, args, mode } => {
let mut res = Vec::with_capacity(args.len());
for arg in args.into_iter() {
res.push(arg.eval(helper)?);
}
let args = IDLArgs { args: res };
let args = if let Some(args) = args {
let mut res = Vec::with_capacity(args.len());
for arg in args.into_iter() {
res.push(arg.eval(helper)?);
}
Some(IDLArgs { args: res })
} else {
None
};
let opt_info = if let Some(method) = &method {
let is_encode = matches!(mode, CallMode::Encode);
Some(method.get_info(helper, is_encode)?)
Expand All @@ -419,9 +423,33 @@ impl Exp {
..
}) = &opt_info
{
let args = if let Some(args) = args {
args
} else {
use candid_parser::assist::{input_args, Context};
let mut ctx = Context::new(env.clone());
let principals = helper.env.dump_principals();
let mut completion = BTreeMap::new();
completion.insert("principal".to_string(), principals);
ctx.set_completion(completion);
let args = input_args(&ctx, &func.args)?;
// Ideally, we should store the args in helper and call editor.readline_with_initial to display
// the full command in the editor. The tricky part is to know where to insert the args in text.
eprintln!("Generated arguments: {}", args);
eprintln!("Do you want to send this message? [y/N]");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !["y", "yes"].contains(&input.to_lowercase().trim()) {
return Err(anyhow!("Abort"));
}
args
};
args.to_bytes_with_types(env, &func.args)?
} else {
args.to_bytes()?
if args.is_none() {
return Err(anyhow!("cannot get method type, please provide arguments"));
}
args.unwrap().to_bytes()?
};
match mode {
CallMode::Encode => IDLValue::Blob(bytes),
Expand Down
8 changes: 5 additions & 3 deletions src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern {
"service" => Token::Service,
"oneway" => Token::Oneway,
"query" => Token::Query,
"composite_query" => Token::CompositeQuery,
"blob" => Token::Blob,
"type" => Token::Type,
"import" => Token::Import,
Expand Down Expand Up @@ -96,9 +97,9 @@ pub Exp: Exp = {
Arg => <>,
Variable => <>,
"fail" <Exp> => Exp::Fail(Box::new(<>)),
"call" <method:Method> <args:Exps> => Exp::Call{method:Some(method), args, mode: CallMode::Call},
"call" "as" <proxy:Name> <method:Method> <args:Exps> => Exp::Call{method:Some(method), args, mode: CallMode::Proxy(proxy)},
"encode" <method:Method?> <args:Exps> => Exp::Call{method, args, mode: CallMode::Encode},
"call" <method:Method> <args:Exps?> => Exp::Call{method:Some(method), args, mode: CallMode::Call},
"call" "as" <proxy:Name> <method:Method> <args:Exps?> => Exp::Call{method:Some(method), args, mode: CallMode::Proxy(proxy)},
"encode" <method:Method?> <args:Exps?> => Exp::Call{method, args, mode: CallMode::Encode},
"decode" <method:("as" <Method>)?> <blob:Exp> => Exp::Decode{method, blob:Box::new(blob)},
<func:"id"> "(" <args:SepBy<Exp, ",">> ")" => Exp::Apply(func, args),
}
Expand Down Expand Up @@ -329,6 +330,7 @@ MethTyp: Binding = {
FuncMode: FuncMode = {
"oneway" => FuncMode::Oneway,
"query" => FuncMode::Query,
"composite_query" => FuncMode::CompositeQuery,
}

// Also allows trailing separator
Expand Down
19 changes: 14 additions & 5 deletions src/helper.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::exp::Exp;
use crate::token::{Token, Tokenizer};
use crate::utils::{random_value, str_to_principal};
use ansi_term::Color;
use candid::{
types::value::{IDLField, IDLValue, VariantValue},
types::{Function, Label, Type, TypeInner},
Expand Down Expand Up @@ -90,7 +89,6 @@ pub struct MyHelper {
pub env: Env,
pub func_env: FuncEnv,
pub base_path: std::path::PathBuf,
pub history: Vec<String>,
pub messages: RefCell<Vec<crate::offline::IngressWithStatus>>,
}

Expand All @@ -102,7 +100,6 @@ impl MyHelper {
hinter: HistoryHinter {},
colored_prompt: "".to_owned(),
validator: MatchingBracketValidator::new(),
history: Vec::new(),
config: Configs::from_dhall("{=}").unwrap(),
canister_map: self.canister_map.clone(),
identity_map: self.identity_map.clone(),
Expand Down Expand Up @@ -130,7 +127,6 @@ impl MyHelper {
env: Env::default(),
func_env: FuncEnv::default(),
base_path: std::env::current_dir().unwrap(),
history: Vec::new(),
messages: Vec::new().into(),
agent,
agent_url,
Expand Down Expand Up @@ -448,7 +444,7 @@ impl Highlighter for MyHelper {
}

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

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

impl Env {
pub fn dump_principals(&self) -> BTreeMap<String, String> {
self.0
.iter()
.filter_map(|(name, value)| match value {
IDLValue::Principal(id) => Some((name, id)),
_ => None,
})
.map(|(name, id)| (name.clone(), id.to_text()))
.collect()
}
}

#[test]
fn test_partial_parse() -> anyhow::Result<()> {
use candid_parser::parse_idl_value;
Expand Down
5 changes: 2 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use ansi_term::Color;
use clap::Parser;
use ic_agent::agent::http_transport::ReqwestTransport;
use ic_agent::Agent;
Expand Down Expand Up @@ -93,14 +92,14 @@ fn repl(opts: Opts) -> anyhow::Result<()> {
loop {
let identity = &rl.helper().unwrap().current_identity;
let p = format!("{identity}@{replica} {count}> ");
rl.helper_mut().unwrap().colored_prompt = format!("{}", Color::Green.bold().paint(&p));
rl.helper_mut().unwrap().colored_prompt =
format!("{}", console::style(&p).green().bold());
let input = rl.readline(&p);
match input {
Ok(line) => {
rl.add_history_entry(&line)?;
unwrap(pretty_parse::<Command>("stdin", &line), |cmd| {
let helper = rl.helper_mut().unwrap();
helper.history.push(line.clone());
unwrap(cmd.run(helper), |_| {});
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub enum Token {
Oneway,
#[token("query")]
Query,
#[token("composite_query")]
CompositeQuery,
#[token("blob")]
Blob,
#[token("type")]
Expand Down

0 comments on commit 3a62b22

Please sign in to comment.