11use clap:: Parser ;
22use std:: collections:: HashMap ;
3+ use std:: ffi:: OsStr ;
34use std:: fmt:: Debug ;
45use std:: path:: Path ;
56use std:: { fs, process} ;
@@ -23,12 +24,6 @@ pub enum Error {
2324 #[ error( transparent) ]
2425 Network ( #[ from] network:: Error ) ,
2526
26- #[ error( transparent) ]
27- PatternError ( #[ from] glob:: PatternError ) ,
28-
29- #[ error( transparent) ]
30- GlobError ( #[ from] glob:: GlobError ) ,
31-
3227 #[ error( transparent) ]
3328 IoError ( #[ from] std:: io:: Error ) ,
3429}
@@ -57,57 +52,57 @@ impl Cmd {
5752 Ok ( ( ) )
5853 }
5954
60- fn read_from_config_dir ( config_dir : & Path ) -> Result < ( ) , Error > {
61- let pattern = config_dir
62- . join ( "contract-ids" )
63- . join ( "*.json" )
64- . to_string_lossy ( )
65- . into_owned ( ) ;
66-
67- let paths = glob:: glob ( & pattern) ?;
68- let mut found = false ;
55+ fn collect_aliases ( config_dir : & Path ) -> Result < HashMap < String , Vec < AliasEntry > > , Error > {
56+ let contract_ids_dir = config_dir. join ( "contract-ids" ) ;
6957 let mut map: HashMap < String , Vec < AliasEntry > > = HashMap :: new ( ) ;
7058
71- for path in paths {
72- let path = path?;
59+ if !contract_ids_dir. is_dir ( ) {
60+ return Ok ( map) ;
61+ }
62+
63+ for entry in fs:: read_dir ( & contract_ids_dir) ? {
64+ let path = entry?. path ( ) ;
65+
66+ if path. extension ( ) != Some ( OsStr :: new ( "json" ) ) {
67+ continue ;
68+ }
7369
7470 if let Some ( alias) = path. file_stem ( ) {
7571 let alias = alias. to_string_lossy ( ) . into_owned ( ) ;
76- let content = fs:: read_to_string ( path) ?;
72+ let content = fs:: read_to_string ( & path) ?;
7773 let data: alias:: Data = serde_json:: from_str ( & content) . unwrap_or_default ( ) ;
7874
79- for network_passphrase in data. ids . keys ( ) {
80- let network_passphrase = network_passphrase. clone ( ) ;
81- let contract = data
82- . ids
83- . get ( & network_passphrase)
84- . map ( ToString :: to_string)
85- . unwrap_or_default ( ) ;
75+ for ( network_passphrase, contract_id) in & data. ids {
8676 let entry = AliasEntry {
8777 alias : alias. clone ( ) ,
88- contract,
78+ contract : contract_id . clone ( ) ,
8979 } ;
9080
91- let list = map. entry ( network_passphrase. clone ( ) ) . or_default ( ) ;
92-
93- list . push ( entry. clone ( ) ) ;
81+ map. entry ( network_passphrase. clone ( ) )
82+ . or_default ( )
83+ . push ( entry) ;
9484 }
9585 }
9686 }
9787
98- for network_passphrase in map. keys ( ) {
99- if let Some ( list) = map. clone ( ) . get_mut ( network_passphrase) {
100- println ! ( "ℹ️ Aliases available for network '{network_passphrase}'" ) ;
88+ Ok ( map)
89+ }
90+
91+ fn read_from_config_dir ( config_dir : & Path ) -> Result < ( ) , Error > {
92+ let mut map = Self :: collect_aliases ( config_dir) ?;
93+ let mut found = false ;
10194
102- list. sort_by ( |a, b| a. alias . cmp ( & b. alias ) ) ;
95+ for ( network_passphrase, list) in & mut map {
96+ println ! ( "ℹ️ Aliases available for network '{network_passphrase}'" ) ;
10397
104- for entry in list {
105- found = true ;
106- println ! ( "{}: {}" , entry. alias, entry. contract) ;
107- }
98+ list. sort_by ( |a, b| a. alias . cmp ( & b. alias ) ) ;
10899
109- println ! ( ) ;
100+ for entry in list. iter ( ) {
101+ found = true ;
102+ println ! ( "{}: {}" , entry. alias, entry. contract) ;
110103 }
104+
105+ println ! ( ) ;
111106 }
112107
113108 if !found {
@@ -119,3 +114,49 @@ impl Cmd {
119114 Ok ( ( ) )
120115 }
121116}
117+
118+ #[ cfg( test) ]
119+ mod tests {
120+ use super :: * ;
121+ use std:: fs;
122+
123+ fn write_alias ( dir : & Path , name : & str , network : & str , contract : & str ) {
124+ let contract_ids_dir = dir. join ( "contract-ids" ) ;
125+ fs:: create_dir_all ( & contract_ids_dir) . unwrap ( ) ;
126+ let content = format ! ( r#"{{"ids":{{"{network}":"{contract}"}}}}"# ) ;
127+ fs:: write ( contract_ids_dir. join ( format ! ( "{name}.json" ) ) , content) . unwrap ( ) ;
128+ }
129+
130+ #[ test]
131+ fn glob_metacharacters_in_config_dir_are_treated_as_literal ( ) {
132+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
133+ let base = tmp. path ( ) ;
134+
135+ // Sibling directories that would match the glob `[12]` if unescaped.
136+ write_alias ( & base. join ( "cfg1" ) , "alpha" , "testnet" , "CAAAA" ) ;
137+ write_alias ( & base. join ( "cfg2" ) , "beta" , "testnet" , "CBBBB" ) ;
138+
139+ // The literal directory whose name contains bracket metacharacters.
140+ write_alias ( & base. join ( "cfg[12]" ) , "gamma" , "testnet" , "CCCCC" ) ;
141+
142+ let map = Cmd :: collect_aliases ( & base. join ( "cfg[12]" ) ) . unwrap ( ) ;
143+
144+ let aliases: Vec < & str > = map
145+ . values ( )
146+ . flat_map ( |entries| entries. iter ( ) . map ( |e| e. alias . as_str ( ) ) )
147+ . collect ( ) ;
148+
149+ assert ! (
150+ aliases. contains( & "gamma" ) ,
151+ "should read alias from the literal directory"
152+ ) ;
153+ assert ! (
154+ !aliases. contains( & "alpha" ) ,
155+ "should not read from sibling cfg1"
156+ ) ;
157+ assert ! (
158+ !aliases. contains( & "beta" ) ,
159+ "should not read from sibling cfg2"
160+ ) ;
161+ }
162+ }
0 commit comments