@@ -3,7 +3,10 @@ use clap::{Parser, Subcommand, ValueEnum};
33use include_dir:: { Dir , include_dir} ;
44use inquire:: { Select , Text } ;
55use log:: debug;
6- use std:: { fs, path:: PathBuf } ;
6+ use std:: {
7+ fs,
8+ path:: { Path , PathBuf } ,
9+ } ;
710
811mod scaffold;
912
@@ -27,8 +30,8 @@ enum Commands {
2730struct PvmContractArgs {
2831 #[ arg( long, value_enum, requires = "non_interactive" ) ]
2932 init_type : Option < InitType > ,
30- #[ arg( long, value_enum , requires = "non_interactive" ) ]
31- example : Option < ExampleChoice > ,
33+ #[ arg( long, requires = "non_interactive" ) ]
34+ example : Option < String > ,
3235 #[ arg( long, value_enum, requires = "non_interactive" ) ]
3336 memory_model : Option < MemoryModel > ,
3437 #[ arg( long, requires = "non_interactive" ) ]
@@ -73,31 +76,61 @@ impl std::fmt::Display for MemoryModel {
7376 }
7477}
7578
76- #[ derive( Debug , Clone , Copy , PartialEq , ValueEnum ) ]
77- enum ExampleChoice {
78- MyToken ,
79+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
80+ struct ExampleContract {
81+ name : String ,
82+ filename : String ,
7983}
8084
81- impl std :: fmt :: Display for ExampleChoice {
82- fn fmt ( & self , f : & mut std :: fmt :: Formatter < ' _ > ) -> std :: fmt :: Result {
83- match self {
84- ExampleChoice :: MyToken => write ! ( f , "MyToken (ERC20-like token)" ) ,
85+ impl ExampleContract {
86+ fn from_path ( path : & Path ) -> Option < Self > {
87+ if path . extension ( ) . and_then ( |ext| ext . to_str ( ) ) != Some ( "sol" ) {
88+ return None ;
8589 }
90+
91+ let filename = path. file_name ( ) ?. to_str ( ) ?. to_string ( ) ;
92+ let name = path. file_stem ( ) ?. to_str ( ) ?. to_string ( ) ;
93+ Some ( Self { name, filename } )
94+ }
95+
96+ fn matches ( & self , query : & str ) -> bool {
97+ let query = query. trim ( ) . to_ascii_lowercase ( ) ;
98+ let name = self . name . to_ascii_lowercase ( ) ;
99+ let filename = self . filename . to_ascii_lowercase ( ) ;
100+ query == name || query == filename
86101 }
87102}
88103
89- impl ExampleChoice {
90- fn sol_filename ( & self ) -> & ' static str {
91- match self {
92- ExampleChoice :: MyToken => "MyToken.sol" ,
93- }
104+ impl std:: fmt:: Display for ExampleContract {
105+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
106+ write ! ( f, "{}" , self . name)
94107 }
108+ }
95109
96- fn default_name ( & self ) -> & ' static str {
97- match self {
98- ExampleChoice :: MyToken => "MyToken" ,
99- }
110+ fn load_examples ( ) -> Result < Vec < ExampleContract > > {
111+ let examples_dir = TEMPLATES_DIR
112+ . get_dir ( "examples" )
113+ . ok_or_else ( || anyhow:: anyhow!( "Examples directory not found in templates" ) ) ?;
114+ let mut examples: Vec < ExampleContract > = examples_dir
115+ . files ( )
116+ . filter_map ( |file| ExampleContract :: from_path ( file. path ( ) ) )
117+ . collect ( ) ;
118+
119+ examples. sort_by ( |left, right| left. name . cmp ( & right. name ) ) ;
120+
121+ if examples. is_empty ( ) {
122+ anyhow:: bail!( "No example contracts found in templates/examples" ) ;
100123 }
124+
125+ Ok ( examples)
126+ }
127+
128+ fn find_example ( examples : & [ ExampleContract ] , query : & str ) -> Result < ExampleContract > {
129+ examples
130+ . iter ( )
131+ . find ( |example| example. matches ( query) )
132+ . cloned ( )
133+ . ok_or_else ( || anyhow:: anyhow!( "Unknown example: {query}" ) )
101134}
102135
103136fn main ( ) -> Result < ( ) > {
@@ -152,8 +185,7 @@ fn init_command_interactive(builder_path: Option<&std::path::Path>) -> Result<()
152185 scaffold:: init_blank_contract ( & contract_name, builder_path)
153186 }
154187 InitType :: Example => {
155- // Prompt for example choice
156- let examples = vec ! [ ExampleChoice :: MyToken ] ;
188+ let examples = load_examples ( ) ?;
157189 let example = Select :: new ( "Select an example:" , examples)
158190 . prompt ( )
159191 . context ( "Failed to get example choice" ) ?;
@@ -166,7 +198,7 @@ fn init_command_interactive(builder_path: Option<&std::path::Path>) -> Result<()
166198
167199 // Ask for name with example name as default
168200 let contract_name = Text :: new ( "What is your contract name?" )
169- . with_default ( example. default_name ( ) )
201+ . with_default ( & example. name )
170202 . with_help_message ( "This will be the name of the project directory" )
171203 . prompt ( )
172204 . context ( "Failed to get contract name" ) ?;
@@ -178,11 +210,10 @@ fn init_command_interactive(builder_path: Option<&std::path::Path>) -> Result<()
178210 check_dir_exists ( & contract_name) ?;
179211 debug ! (
180212 "Initializing from example: {} with memory model: {:?}" ,
181- example. sol_filename( ) ,
182- memory_model
213+ example. filename, memory_model
183214 ) ;
184215
185- init_from_example ( example, & contract_name, memory_model, builder_path)
216+ init_from_example ( & example, & contract_name, memory_model, builder_path)
186217 }
187218 InitType :: SolidityFile => {
188219 // Prompt for .sol file path
@@ -257,15 +288,15 @@ fn init_command_non_interactive(
257288 scaffold:: init_blank_contract ( & contract_name, builder_path)
258289 }
259290 InitType :: Example => {
260- let example = args. example . ok_or_else ( || {
291+ let examples = load_examples ( ) ?;
292+ let example_name = args. example . ok_or_else ( || {
261293 anyhow:: anyhow!( "--example is required for example initialization" )
262294 } ) ?;
295+ let example = find_example ( & examples, & example_name) ?;
263296 let memory_model = args. memory_model . ok_or_else ( || {
264297 anyhow:: anyhow!( "--memory-model is required for example initialization" )
265298 } ) ?;
266- let contract_name = args
267- . name
268- . unwrap_or_else ( || example. default_name ( ) . to_string ( ) ) ;
299+ let contract_name = args. name . unwrap_or_else ( || example. name . clone ( ) ) ;
269300
270301 if contract_name. is_empty ( ) {
271302 anyhow:: bail!( "Contract name cannot be empty" ) ;
@@ -274,11 +305,10 @@ fn init_command_non_interactive(
274305 check_dir_exists ( & contract_name) ?;
275306 debug ! (
276307 "Initializing from example: {} with memory model: {:?}" ,
277- example. sol_filename( ) ,
278- memory_model
308+ example. filename, memory_model
279309 ) ;
280310
281- init_from_example ( example, & contract_name, memory_model, builder_path)
311+ init_from_example ( & example, & contract_name, memory_model, builder_path)
282312 }
283313 InitType :: SolidityFile => {
284314 let sol_path = args. sol_file . ok_or_else ( || {
@@ -321,13 +351,13 @@ fn init_command_non_interactive(
321351}
322352
323353fn init_from_example (
324- example : ExampleChoice ,
354+ example : & ExampleContract ,
325355 contract_name : & str ,
326356 memory_model : MemoryModel ,
327357 builder_path : Option < & std:: path:: Path > ,
328358) -> Result < ( ) > {
329359 // Get the embedded example .sol file
330- let example_path = format ! ( "examples/{}" , example. sol_filename ( ) ) ;
360+ let example_path = format ! ( "examples/{}" , example. filename ) ;
331361 let example_file = TEMPLATES_DIR
332362 . get_file ( & example_path)
333363 . ok_or_else ( || anyhow:: anyhow!( "Example file not found: {}" , example_path) ) ?;
@@ -340,9 +370,9 @@ fn init_from_example(
340370 . as_nanos ( ) ;
341371 let example_temp_dir = temp_dir. join ( format ! ( "cargo-pvm-contract-{timestamp}" ) ) ;
342372 fs:: create_dir_all ( & example_temp_dir) . with_context ( || {
343- format ! ( "Failed to create temporary directory for example: {example_temp_dir:?}" , )
373+ format ! ( "Failed to create temporary directory for example: {example_temp_dir:?}" )
344374 } ) ?;
345- let temp_sol_path = example_temp_dir. join ( example. sol_filename ( ) ) ;
375+ let temp_sol_path = example_temp_dir. join ( example. filename . as_str ( ) ) ;
346376 fs:: write ( & temp_sol_path, example_file. contents ( ) )
347377 . with_context ( || format ! ( "Failed to write temporary .sol file: {:?}" , temp_sol_path) ) ?;
348378
0 commit comments