Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: guide user to call a contract #306

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3c9b261
feat: guide user for calling a contract
AlexD10S Sep 5, 2024
f9e305a
feat: get metadata contract from the contract path
AlexD10S Sep 5, 2024
5327bf9
refactor: refactor test and validate address input
AlexD10S Sep 5, 2024
83e236c
fix: apply feedback
AlexD10S Sep 6, 2024
7cef633
feat: prompt to have another call and skip questions for queries
AlexD10S Sep 6, 2024
62eefcf
refactor: use Cli module instead of cliclack
AlexD10S Sep 8, 2024
fee1e26
test: unit test pop-cli crate
AlexD10S Sep 9, 2024
6c9aa77
test: unit contracts crate
AlexD10S Sep 9, 2024
20360e8
chore: merge main
AlexD10S Sep 9, 2024
cd4c20f
chore: format
AlexD10S Sep 9, 2024
d59259c
test: refactor and improve test cases
AlexD10S Sep 10, 2024
c422192
fix: fix todos and refactor
AlexD10S Sep 10, 2024
22dd4f6
test: fix unit test
AlexD10S Sep 10, 2024
c8fedef
feat: parse types of parameters and display it to the user in the pla…
AlexD10S Sep 11, 2024
b0b3573
refactor: error handling for pop call
AlexD10S Sep 12, 2024
8e60da8
refactor: display call to be executed after guide and reorder
AlexD10S Sep 12, 2024
08f2454
refactor: when repeat call use same contract values and dont clean sc…
AlexD10S Sep 12, 2024
68848be
test: add dry-run test
AlexD10S Sep 13, 2024
1aaea92
test: refactor and add more test coverage
AlexD10S Sep 19, 2024
c3e0cc0
chore: merge main
AlexD10S Sep 19, 2024
05db96f
test: more coverage
AlexD10S Sep 20, 2024
8df2058
fix: unit test
AlexD10S Sep 20, 2024
96ed5f1
feat: dev mode to skip certain user prompts
AlexD10S Nov 5, 2024
0e5bf32
refactor: test functions, renaming and fix clippy
AlexD10S Nov 5, 2024
29848b4
refactor: improve devex of pop call contract
AlexD10S Nov 5, 2024
5f479fa
test: adjust tests to refactor
AlexD10S Nov 5, 2024
5be201b
chore: reset_for_new_call fields
AlexD10S Nov 6, 2024
e55d1a6
fix: build contract if has not been built
AlexD10S Nov 6, 2024
eaad4be
refactor: use command state (#338)
evilrobot-01 Nov 6, 2024
5998a7e
fix: parse user inputs for Option arguments (#332)
AlexD10S Nov 22, 2024
8e04a88
Merge branch 'main' into feat-call-ui-contracts
AlexD10S Nov 22, 2024
a6f8247
test: fix unit test
AlexD10S Nov 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ sp-core = "31"
sp-weights = "30"
contract-build = "5.0.0-alpha"
contract-extrinsics = "5.0.0-alpha"
contract-transcode = "5.0.0-alpha"
scale-info = { version = "2.11.3", default-features = false, features = ["derive"] }
heck = "0.5.0"

# parachains
Expand Down
232 changes: 226 additions & 6 deletions crates/pop-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub(crate) mod traits {
fn confirm(&mut self, prompt: impl Display) -> impl Confirm;
/// Prints an info message.
fn info(&mut self, text: impl Display) -> Result<()>;
/// Constructs a new [`Input`] prompt.
fn input(&mut self, prompt: impl Display) -> impl Input;
/// Prints a header of the prompt sequence.
fn intro(&mut self, title: impl Display) -> Result<()>;
/// Constructs a new [`MultiSelect`] prompt.
Expand All @@ -21,6 +23,8 @@ pub(crate) mod traits {
fn outro(&mut self, message: impl Display) -> Result<()>;
/// Prints a footer of the prompt sequence with a failure style.
fn outro_cancel(&mut self, message: impl Display) -> Result<()>;
/// Constructs a new [`Select`] prompt.
fn select<T: Clone + Eq>(&mut self, prompt: impl Display) -> impl Select<T>;
/// Prints a success message.
fn success(&mut self, message: impl Display) -> Result<()>;
/// Prints a warning message.
Expand All @@ -29,10 +33,29 @@ pub(crate) mod traits {

/// A confirmation prompt.
pub trait Confirm {
/// Sets the initially selected value.
fn initial_value(self, initial_value: bool) -> Self;
/// Starts the prompt interaction.
fn interact(&mut self) -> Result<bool>;
}

/// A text input prompt.
pub trait Input {
/// Sets the default value for the input.
fn default_input(self, value: &str) -> Self;
/// Starts the prompt interaction.
fn interact(&mut self) -> Result<String>;
/// Sets the placeholder (hint) text for the input.
fn placeholder(self, value: &str) -> Self;
/// Sets whether the input is required.
fn required(self, required: bool) -> Self;
/// Sets a validation callback for the input that is called when the user submits.
fn validate(
self,
validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static,
) -> Self;
}

/// A multi-select prompt.
pub trait MultiSelect<T> {
/// Starts the prompt interaction.
Expand All @@ -42,6 +65,14 @@ pub(crate) mod traits {
/// Sets whether the input is required.
fn required(self, required: bool) -> Self;
}

/// A select prompt.
pub trait Select<T> {
/// Starts the prompt interaction.
fn interact(&mut self) -> Result<T>;
/// Adds an item to the selection prompt.
fn item(self, value: T, label: impl Display, hint: impl Display) -> Self;
}
}

/// A command line interface using cliclack.
Expand All @@ -57,6 +88,11 @@ impl traits::Cli for Cli {
cliclack::log::info(text)
}

/// Constructs a new [`Input`] prompt.
fn input(&mut self, prompt: impl Display) -> impl traits::Input {
Input(cliclack::input(prompt))
}

/// Prints a header of the prompt sequence.
fn intro(&mut self, title: impl Display) -> Result<()> {
cliclack::clear_screen()?;
Expand All @@ -79,6 +115,11 @@ impl traits::Cli for Cli {
cliclack::outro_cancel(message)
}

/// Constructs a new [`Select`] prompt.
fn select<T: Clone + Eq>(&mut self, prompt: impl Display) -> impl traits::Select<T> {
Select::<T>(cliclack::select(prompt))
}

/// Prints a success message.
fn success(&mut self, message: impl Display) -> Result<()> {
cliclack::log::success(message)
Expand All @@ -97,6 +138,43 @@ impl traits::Confirm for Confirm {
fn interact(&mut self) -> Result<bool> {
self.0.interact()
}
/// Sets the initially selected value.
fn initial_value(mut self, initial_value: bool) -> Self {
self.0 = self.0.initial_value(initial_value);
self
}
}

/// A input prompt using cliclack.
struct Input(cliclack::Input);
impl traits::Input for Input {
/// Sets the default value for the input.
fn default_input(mut self, value: &str) -> Self {
self.0 = self.0.default_input(value);
self
}
/// Starts the prompt interaction.
fn interact(&mut self) -> Result<String> {
self.0.interact()
}
/// Sets the placeholder (hint) text for the input.
fn placeholder(mut self, placeholder: &str) -> Self {
self.0 = self.0.placeholder(placeholder);
self
}
/// Sets whether the input is required.
fn required(mut self, required: bool) -> Self {
self.0 = self.0.required(required);
self
}
/// Sets a validation callback for the input that is called when the user submits.
fn validate(
mut self,
validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static,
) -> Self {
self.0 = self.0.validate(validator);
self
}
}

/// A multi-select prompt using cliclack.
Expand All @@ -121,21 +199,40 @@ impl<T: Clone + Eq> traits::MultiSelect<T> for MultiSelect<T> {
}
}

/// A select prompt using cliclack.
struct Select<T: Clone + Eq>(cliclack::Select<T>);

impl<T: Clone + Eq> traits::Select<T> for Select<T> {
/// Starts the prompt interaction.
fn interact(&mut self) -> Result<T> {
self.0.interact()
}

/// Adds an item to the selection prompt.
fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self {
self.0 = self.0.item(value, label, hint);
self
}
}

#[cfg(test)]
pub(crate) mod tests {
use super::traits::*;
use std::{fmt::Display, io::Result};
use std::{fmt::Display, io::Result, usize};

/// Mock Cli with optional expectations
#[derive(Default)]
pub(crate) struct MockCli {
confirm_expectation: Option<(String, bool)>,
confirm_expectation: Vec<(String, bool)>,
info_expectations: Vec<String>,
input_expectations: Vec<(String, String)>,
intro_expectation: Option<String>,
outro_expectation: Option<String>,
multiselect_expectation:
Option<(String, Option<bool>, bool, Option<Vec<(String, String)>>)>,
outro_cancel_expectation: Option<String>,
select_expectation:
Option<(String, Option<bool>, bool, Option<Vec<(String, String)>>, usize)>,
success_expectations: Vec<String>,
warning_expectations: Vec<String>,
}
Expand All @@ -146,7 +243,12 @@ pub(crate) mod tests {
}

pub(crate) fn expect_confirm(mut self, prompt: impl Display, confirm: bool) -> Self {
self.confirm_expectation = Some((prompt.to_string(), confirm));
self.confirm_expectation.push((prompt.to_string(), confirm));
self
}

pub(crate) fn expect_input(mut self, prompt: impl Display, input: String) -> Self {
self.input_expectations.push((prompt.to_string(), input));
self
}

Expand Down Expand Up @@ -181,6 +283,18 @@ pub(crate) mod tests {
self
}

pub(crate) fn expect_select<T>(
mut self,
prompt: impl Display,
required: Option<bool>,
collect: bool,
items: Option<Vec<(String, String)>>,
item: usize,
) -> Self {
self.select_expectation = Some((prompt.to_string(), required, collect, items, item));
self
}

pub(crate) fn expect_success(mut self, message: impl Display) -> Self {
self.success_expectations.push(message.to_string());
self
Expand All @@ -192,12 +306,15 @@ pub(crate) mod tests {
}

pub(crate) fn verify(self) -> anyhow::Result<()> {
if let Some((expectation, _)) = self.confirm_expectation {
panic!("`{expectation}` confirm expectation not satisfied")
if !self.confirm_expectation.is_empty() {
panic!("`{:?}` confirm expectations not satisfied", self.confirm_expectation)
}
if !self.info_expectations.is_empty() {
panic!("`{}` info log expectations not satisfied", self.info_expectations.join(","))
}
if !self.input_expectations.is_empty() {
panic!("`{:?}` input expectation not satisfied", self.input_expectations)
}
if let Some(expectation) = self.intro_expectation {
panic!("`{expectation}` intro expectation not satisfied")
}
Expand All @@ -210,6 +327,9 @@ pub(crate) mod tests {
if let Some(expectation) = self.outro_cancel_expectation {
panic!("`{expectation}` outro cancel expectation not satisfied")
}
if let Some((prompt, _, _, _, _)) = self.select_expectation {
panic!("`{prompt}` select prompt expectation not satisfied")
}
if !self.success_expectations.is_empty() {
panic!(
"`{}` success log expectations not satisfied",
Expand All @@ -229,7 +349,7 @@ pub(crate) mod tests {
impl Cli for MockCli {
fn confirm(&mut self, prompt: impl Display) -> impl Confirm {
let prompt = prompt.to_string();
if let Some((expectation, confirm)) = self.confirm_expectation.take() {
if let Some((expectation, confirm)) = self.confirm_expectation.pop() {
assert_eq!(expectation, prompt, "prompt does not satisfy expectation");
return MockConfirm { confirm };
}
Expand All @@ -242,6 +362,20 @@ pub(crate) mod tests {
Ok(())
}

fn input(&mut self, prompt: impl Display) -> impl Input {
let prompt = prompt.to_string();
if let Some((expectation, input)) = self.input_expectations.pop() {
assert_eq!(expectation, prompt, "prompt does not satisfy expectation");
return MockInput {
prompt: input.clone(),
input,
placeholder: "".to_string(),
required: false,
};
}
MockInput::default()
}

fn intro(&mut self, title: impl Display) -> Result<()> {
if let Some(expectation) = self.intro_expectation.take() {
assert_eq!(expectation, title.to_string(), "intro does not satisfy expectation");
Expand Down Expand Up @@ -288,6 +422,18 @@ pub(crate) mod tests {
Ok(())
}

fn select<T: Clone + Eq>(&mut self, prompt: impl Display) -> impl Select<T> {
let prompt = prompt.to_string();
if let Some((expectation, _, collect, items_expectation, item)) =
self.select_expectation.take()
{
assert_eq!(expectation, prompt, "prompt does not satisfy expectation");
return MockSelect { items_expectation, collect, items: vec![], item };
}

MockSelect::default()
}

fn success(&mut self, message: impl Display) -> Result<()> {
let message = message.to_string();
self.success_expectations.retain(|x| *x != message);
Expand All @@ -308,11 +454,51 @@ pub(crate) mod tests {
}

impl Confirm for MockConfirm {
fn initial_value(mut self, _initial_value: bool) -> Self {
self.confirm = self.confirm; // Ignore initial value and always return mock value
self
}
fn interact(&mut self) -> Result<bool> {
Ok(self.confirm)
}
}

/// Mock input prompt
#[derive(Default)]
struct MockInput {
prompt: String,
input: String,
placeholder: String,
required: bool,
}

impl Input for MockInput {
fn interact(&mut self) -> Result<String> {
Ok(self.prompt.clone())
}
fn default_input(mut self, value: &str) -> Self {
self.input = value.to_string();
self
}

fn placeholder(mut self, value: &str) -> Self {
self.placeholder = value.to_string();
self
}

fn required(mut self, value: bool) -> Self {
self.required = value;
self
}

fn validate(
self,
_validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static,
) -> Self {
self
}
}

/// Mock multi-select prompt
pub(crate) struct MockMultiSelect<T> {
required_expectation: Option<bool>,
Expand Down Expand Up @@ -360,4 +546,38 @@ pub(crate) mod tests {
self
}
}

/// Mock select prompt
pub(crate) struct MockSelect<T> {
items_expectation: Option<Vec<(String, String)>>,
collect: bool,
items: Vec<T>,
item: usize,
}

impl<T> MockSelect<T> {
pub(crate) fn default() -> Self {
Self { items_expectation: None, collect: false, items: vec![], item: 0 }
}
}

impl<T: Clone + Eq> Select<T> for MockSelect<T> {
fn interact(&mut self) -> Result<T> {
Ok(self.items[self.item].clone())
}

fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self {
// Check expectations
if let Some(items) = self.items_expectation.as_mut() {
let item = (label.to_string(), hint.to_string());
assert!(items.contains(&item), "`{item:?}` item does not satisfy any expectations");
items.retain(|x| *x != item);
}
// Collect if specified
if self.collect {
self.items.push(value);
}
self
}
}
}
Loading
Loading