Skip to content

Commit 80a4c31

Browse files
authored
refactor: remove psp example and improve pop new contract devex (#700)
* refactor: remove psp example and improve pop new contract devex * fix: CI * fmt * refactor: minimise changes * fix: remove ContractType * fix: remove agents file * fix: ci * fix: test
1 parent 6df01a9 commit 80a4c31

File tree

6 files changed

+66
-276
lines changed

6 files changed

+66
-276
lines changed

crates/pop-cli/src/commands/new/contract.rs

Lines changed: 52 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,20 @@ use clap::{
1414
builder::{PossibleValue, PossibleValuesParser, TypedValueParser},
1515
};
1616
use console::style;
17-
use pop_common::{
18-
enum_variants, get_project_name_from_path,
19-
templates::{Template, Type},
17+
use pop_common::{enum_variants, get_project_name_from_path, templates::Template};
18+
use pop_contracts::{Contract, create_smart_contract, is_valid_contract_name};
19+
use std::{
20+
fs,
21+
path::{Path, PathBuf},
22+
str::FromStr,
2023
};
21-
use pop_contracts::{Contract, ContractType, create_smart_contract, is_valid_contract_name};
22-
use std::{fs, path::Path, str::FromStr};
2324
use strum::VariantArray;
2425

2526
#[derive(Args, Clone)]
2627
#[cfg_attr(test, derive(Default))]
2728
pub struct NewContractCommand {
2829
/// The name of the contract.
2930
pub(crate) name: Option<String>,
30-
/// The type of contract.
31-
#[arg(
32-
default_value = ContractType::Examples.as_ref(),
33-
short,
34-
long,
35-
value_parser = enum_variants!(ContractType)
36-
)]
37-
pub(crate) contract_type: Option<ContractType>,
3831
/// The template to use.
3932
#[arg(short, long, value_parser = enum_variants!(Contract))]
4033
pub(crate) template: Option<Contract>,
@@ -44,17 +37,15 @@ impl NewContractCommand {
4437
/// Executes the command.
4538
pub(crate) async fn execute(self) -> Result<Contract> {
4639
let mut cli = Cli;
47-
// If the user doesn't provide a name, guide them in generating a contract.
48-
let contract_config = if self.name.is_none() {
49-
guide_user_to_generate_contract(&mut cli).await?
50-
} else {
51-
self.clone()
52-
};
5340

54-
let path_project = &contract_config
55-
.name
56-
.clone()
57-
.expect("name can not be none as fallback above is interactive input; qed");
41+
let mut command = self;
42+
43+
// Prompt for missing fields interactively
44+
if command.name.is_none() || command.template.is_none() {
45+
command = guide_user_to_generate_contract(&mut cli, command).await?;
46+
}
47+
48+
let path_project = command.name.as_ref().expect("name can not be none; qed");
5849
let path = Path::new(path_project);
5950
let name = get_project_name_from_path(path, "my_contract");
6051

@@ -64,86 +55,50 @@ impl NewContractCommand {
6455
return Ok(Contract::Standard);
6556
}
6657

67-
let contract_type = &contract_config.contract_type.clone().unwrap_or_default();
68-
let template = match &contract_config.template {
69-
Some(template) => template.clone(),
70-
None => contract_type.default_template().expect("contract types have defaults; qed."), /* Default contract type */
71-
};
72-
73-
is_template_supported(contract_type, &template)?;
74-
generate_contract_from_template(name, path, &template, &mut cli)?;
58+
let template = command.template.unwrap_or_default();
59+
let contract_path = generate_contract_from_template(name, path, &template, &mut cli)?;
7560

7661
// If the contract is part of a workspace, add it to that workspace
7762
if let Some(workspace_toml) = rustilities::manifest::find_workspace_manifest(path) {
7863
// Canonicalize paths before passing to rustilities to avoid strip_prefix errors
7964
// This ensures paths are absolute and consistent, especially when using simple names
8065
rustilities::manifest::add_crate_to_workspace(
8166
&workspace_toml.canonicalize()?,
82-
&path.canonicalize()?,
67+
&contract_path.canonicalize()?,
8368
)?;
8469
}
8570

8671
Ok(template)
8772
}
8873
}
8974

90-
/// Determines whether the specified template is supported by the type.
91-
fn is_template_supported(contract_type: &ContractType, template: &Contract) -> Result<()> {
92-
if !contract_type.provides(template) {
93-
return Err(anyhow::anyhow!(format!(
94-
"The contract type \"{:?}\" doesn't support the {:?} template.",
95-
contract_type, template
96-
)));
97-
};
98-
Ok(())
99-
}
100-
101-
/// Guide the user to generate a contract from available templates.
75+
/// Guide the user to provide any missing fields for contract generation.
10276
async fn guide_user_to_generate_contract(
10377
cli: &mut impl cli::traits::Cli,
78+
mut command: NewContractCommand,
10479
) -> Result<NewContractCommand> {
10580
cli.intro("Generate a contract")?;
10681

107-
let contract_type = {
108-
let mut contract_type_prompt = cli.select("Select a template type: ".to_string());
109-
for (i, contract_type) in ContractType::types().iter().enumerate() {
110-
if i == 0 {
111-
contract_type_prompt = contract_type_prompt.initial_value(contract_type);
112-
}
113-
contract_type_prompt = contract_type_prompt.item(
114-
contract_type,
115-
contract_type.name(),
116-
format!(
117-
"{} {} available option(s)",
118-
contract_type.description(),
119-
contract_type.templates().len(),
120-
),
121-
);
122-
}
123-
contract_type_prompt.interact()?
124-
};
125-
let template = display_select_options(contract_type, cli)?;
82+
if command.template.is_none() {
83+
let template = display_select_options(cli)?;
84+
command.template = Some(template);
85+
}
12686

127-
// Prompt for location.
128-
let name: String = cli
129-
.input("Where should your project be created?")
130-
.placeholder("./my_contract")
131-
.default_input("./my_contract")
132-
.interact()?;
87+
if command.name.is_none() {
88+
let name: String = cli
89+
.input("Where should your project be created?")
90+
.placeholder("./my_contract")
91+
.default_input("./my_contract")
92+
.interact()?;
93+
command.name = Some(name);
94+
}
13395

134-
Ok(NewContractCommand {
135-
name: Some(name),
136-
contract_type: Some(contract_type.clone()),
137-
template: Some(template),
138-
})
96+
Ok(command)
13997
}
14098

141-
fn display_select_options(
142-
contract_type: &ContractType,
143-
cli: &mut impl cli::traits::Cli,
144-
) -> Result<Contract> {
145-
let mut prompt = cli.select("Select the contract:".to_string());
146-
for (i, template) in contract_type.templates().into_iter().enumerate() {
99+
fn display_select_options(cli: &mut impl cli::traits::Cli) -> Result<Contract> {
100+
let mut prompt = cli.select("Select a template:".to_string());
101+
for (i, template) in Contract::templates().iter().enumerate() {
147102
if i == 0 {
148103
prompt = prompt.initial_value(template);
149104
}
@@ -157,7 +112,7 @@ fn generate_contract_from_template(
157112
path: &Path,
158113
template: &Contract,
159114
cli: &mut impl cli::traits::Cli,
160-
) -> anyhow::Result<()> {
115+
) -> anyhow::Result<PathBuf> {
161116
cli.intro(format!("Generating \"{}\" using {}!", name, template.name(),))?;
162117

163118
let contract_path = check_destination_path(path, cli)?;
@@ -193,7 +148,7 @@ fn generate_contract_from_template(
193148
"Need help? Learn more at {}\n",
194149
style("https://learn.onpop.io").magenta().underlined()
195150
))?;
196-
Ok(())
151+
Ok(contract_path)
197152
}
198153

199154
#[cfg(test)]
@@ -204,21 +159,20 @@ mod tests {
204159
Command::New,
205160
cli::MockCli,
206161
commands::new::{Command::Contract, NewArgs},
207-
new::contract::{guide_user_to_generate_contract, is_template_supported},
208162
};
209163
use anyhow::Result;
210164
use clap::Parser;
211165
use console::style;
212-
use pop_common::templates::{Template, Type};
213-
use pop_contracts::{Contract as ContractTemplate, ContractType};
166+
use pop_common::templates::Template;
167+
use pop_contracts::Contract as ContractTemplate;
214168
use strum::VariantArray;
215169
use tempfile::tempdir;
216170

217171
#[tokio::test]
218172
async fn test_new_contract_command_execute_with_defaults_executes() -> Result<()> {
219173
let dir = tempdir()?;
220174
let dir_path = format!("{}/test_contract", dir.path().display());
221-
let cli = Cli::parse_from(["pop", "new", "contract", &dir_path]);
175+
let cli = Cli::parse_from(["pop", "new", "contract", &dir_path, "--template", "standard"]);
222176

223177
let New(NewArgs { command: Some(Contract(command)) }) = cli.command else {
224178
panic!("unable to parse command")
@@ -230,47 +184,27 @@ mod tests {
230184

231185
#[tokio::test]
232186
async fn guide_user_to_generate_contract_works() -> anyhow::Result<()> {
233-
let mut items_select_contract_type: Vec<(String, String)> = Vec::new();
234-
for contract_type in ContractType::VARIANTS {
235-
items_select_contract_type.push((
236-
contract_type.name().to_string(),
237-
format!(
238-
"{} {} available option(s)",
239-
contract_type.description(),
240-
contract_type.templates().len(),
241-
),
242-
));
243-
}
244187
let mut items_select_contract: Vec<(String, String)> = Vec::new();
245-
for contract_template in ContractType::Erc.templates() {
188+
for contract_template in ContractTemplate::VARIANTS {
246189
items_select_contract.push((
247190
contract_template.name().to_string(),
248191
contract_template.description().to_string(),
249192
));
250193
}
251194
let mut cli = MockCli::new()
252195
.expect_intro("Generate a contract")
253-
.expect_input("Where should your project be created?", "./erc20".into())
254-
.expect_select(
255-
"Select a template type: ",
256-
Some(false),
257-
true,
258-
Some(items_select_contract_type),
259-
1, // "ERC",
260-
None,
261-
)
262196
.expect_select(
263-
"Select the contract:",
197+
"Select a template:",
264198
Some(false),
265199
true,
266200
Some(items_select_contract),
267-
0, // "ERC20"
201+
1, // "erc20"
268202
None,
269-
);
203+
)
204+
.expect_input("Where should your project be created?", "./erc20".into());
270205

271-
let user_input = guide_user_to_generate_contract(&mut cli).await?;
206+
let user_input = guide_user_to_generate_contract(&mut cli, Default::default()).await?;
272207
assert_eq!(user_input.name, Some("./erc20".to_string()));
273-
assert_eq!(user_input.contract_type, Some(ContractType::Erc));
274208
assert_eq!(user_input.template, Some(ContractTemplate::ERC20));
275209

276210
cli.verify()
@@ -307,25 +241,6 @@ mod tests {
307241
cli.verify()
308242
}
309243

310-
#[test]
311-
fn is_template_supported_works() -> Result<()> {
312-
is_template_supported(&ContractType::Erc, &ContractTemplate::ERC20)?;
313-
is_template_supported(&ContractType::Erc, &ContractTemplate::ERC721)?;
314-
assert!(
315-
is_template_supported(&ContractType::Erc, &ContractTemplate::CrossContract).is_err()
316-
);
317-
assert!(is_template_supported(&ContractType::Erc, &ContractTemplate::PSP22).is_err());
318-
is_template_supported(&ContractType::Examples, &ContractTemplate::Standard)?;
319-
is_template_supported(&ContractType::Examples, &ContractTemplate::CrossContract)?;
320-
assert!(is_template_supported(&ContractType::Examples, &ContractTemplate::ERC20).is_err());
321-
assert!(is_template_supported(&ContractType::Examples, &ContractTemplate::PSP22).is_err());
322-
is_template_supported(&ContractType::Psp, &ContractTemplate::PSP22)?;
323-
is_template_supported(&ContractType::Psp, &ContractTemplate::PSP34)?;
324-
assert!(is_template_supported(&ContractType::Psp, &ContractTemplate::ERC20).is_err());
325-
assert!(is_template_supported(&ContractType::Psp, &ContractTemplate::Standard).is_err());
326-
Ok(())
327-
}
328-
329244
#[tokio::test]
330245
async fn test_contract_in_workspace_with_simple_name() -> Result<()> {
331246
// The bug occurs when you pass a simple name like "flipper" instead of "./flipper"
@@ -349,7 +264,12 @@ edition = "2024"
349264
// User runs: pop new contract flipper -t standard
350265
// They pass just "flipper", not "./flipper"
351266
let cli = Cli::parse_from([
352-
"pop", "new", "contract", "flipper", // Just the name, not a path like "./flipper"
267+
"pop",
268+
"new",
269+
"contract",
270+
"flipper",
271+
"--template",
272+
"standard", // Just the name, not a path like "./flipper"
353273
]);
354274
let New(NewArgs { command: Some(Contract(command)) }) = cli.command else {
355275
panic!("unable to parse command")

crates/pop-cli/src/commands/new/mod.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,7 @@ pub fn guide_user_to_select_command(cli: &mut impl cli::traits::Cli) -> AnyhowRe
114114
mode: None,
115115
})),
116116
#[cfg(feature = "contract")]
117-
"contract" => Ok(Command::Contract(contract::NewContractCommand {
118-
name: None,
119-
contract_type: None,
120-
template: None,
121-
})),
117+
"contract" => Ok(Command::Contract(contract::NewContractCommand { name: None, template: None })),
122118
_ => Err(anyhow::anyhow!("Invalid selection")),
123119
}
124120
}

crates/pop-cli/tests/contract.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#![cfg(all(feature = "contract", feature = "integration-tests"))]
66

77
use anyhow::Result;
8-
use pop_common::{find_free_port, pop, set_executable_permission, templates::Template};
8+
use pop_common::{find_free_port, pop, set_executable_permission};
99
use pop_contracts::{
1010
CallOpts, Contract, UpOpts, Weight, dry_run_call, dry_run_gas_estimate_call,
1111
dry_run_gas_estimate_instantiate, ink_node_generator, instantiate_smart_contract, run_ink_node,
@@ -80,7 +80,8 @@ async fn contract_lifecycle() -> Result<()> {
8080
// Test that all templates are generated correctly
8181
generate_all_the_templates(&temp_dir)?;
8282
// pop new contract test_contract (default)
83-
let mut command = pop(&temp_dir, ["new", "contract", "test_contract"]);
83+
let mut command =
84+
pop(&temp_dir, ["new", "contract", "test_contract", "--template", "standard"]);
8485
assert!(command.spawn()?.wait()?.success());
8586
assert!(temp_dir.join("test_contract").exists());
8687

@@ -275,22 +276,12 @@ async fn contract_lifecycle() -> Result<()> {
275276
fn generate_all_the_templates(temp_dir: &Path) -> Result<()> {
276277
for template in Contract::VARIANTS {
277278
let contract_name = format!("test_contract_{}", template).replace("-", "_");
278-
let contract_type = template.template_type()?.to_lowercase();
279-
// pop new chain test_parachain
279+
// pop new contract test_contract
280280
let mut command = pop(
281281
&temp_dir,
282-
[
283-
"new",
284-
"contract",
285-
&contract_name,
286-
"--contract-type",
287-
&contract_type,
288-
"--template",
289-
&template.to_string(),
290-
],
282+
["new", "contract", &contract_name, "--template", &template.to_string()],
291283
);
292284
assert!(command.spawn()?.wait()?.success());
293-
assert!(temp_dir.join(contract_name).exists());
294285
}
295286
Ok(())
296287
}

crates/pop-contracts/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use new::{create_smart_contract, is_valid_contract_name};
2323
pub use node::{
2424
eth_rpc_generator, ink_node_generator, is_chain_alive, run_eth_rpc_node, run_ink_node,
2525
};
26-
pub use templates::{Contract, ContractType};
26+
pub use templates::Contract;
2727
pub use test::test_e2e_smart_contract;
2828
pub use testing::{mock_build_process, new_environment};
2929
pub use up::{

crates/pop-contracts/src/new.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,12 @@ fn create_template_contract(
6767
let temp_dir = ::tempfile::TempDir::new_in(std::env::temp_dir())?;
6868
Git::clone(&Url::parse(template_repository)?, temp_dir.path(), None)?;
6969
// Retrieve only the template contract files.
70-
if template == &Contract::PSP22 || template == &Contract::PSP34 {
71-
// Different template structure requires extracting different path
72-
extract_template_files("", temp_dir.path(), canonicalized_path.as_path(), None)?;
73-
} else {
74-
extract_template_files(
75-
template.as_ref(),
76-
temp_dir.path(),
77-
canonicalized_path.as_path(),
78-
Some(vec!["frontend".to_string()]),
79-
)?;
80-
}
70+
extract_template_files(
71+
template.as_ref(),
72+
temp_dir.path(),
73+
canonicalized_path.as_path(),
74+
Some(vec!["frontend".to_string()]),
75+
)?;
8176

8277
// Replace name of the contract.
8378
rename_contract(name, canonicalized_path, template)?;

0 commit comments

Comments
 (0)