@@ -14,27 +14,20 @@ use clap::{
1414 builder:: { PossibleValue , PossibleValuesParser , TypedValueParser } ,
1515} ;
1616use 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 } ;
2324use strum:: VariantArray ;
2425
2526#[ derive( Args , Clone ) ]
2627#[ cfg_attr( test, derive( Default ) ) ]
2728pub 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.
10276async 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" )
0 commit comments