@@ -7,7 +7,9 @@ use crate::util::restricted_names::is_glob_pattern;
7
7
use cargo:: core:: Verbosity ;
8
8
use cargo:: core:: Workspace ;
9
9
use cargo:: ops:: { self , CompileFilter , Packages } ;
10
+ use cargo:: util:: closest;
10
11
use cargo_util:: ProcessError ;
12
+ use itertools:: Itertools as _;
11
13
12
14
pub fn cli ( ) -> Command {
13
15
subcommand ( "run" )
@@ -97,11 +99,81 @@ pub fn is_manifest_command(arg: &str) -> bool {
97
99
}
98
100
99
101
pub fn exec_manifest_command ( config : & mut Config , cmd : & str , args : & [ OsString ] ) -> CliResult {
100
- if !config. cli_unstable ( ) . script {
101
- return Err ( anyhow:: anyhow!( "running `{cmd}` requires `-Zscript`" ) . into ( ) ) ;
102
+ let manifest_path = Path :: new ( cmd) ;
103
+ match ( manifest_path. is_file ( ) , config. cli_unstable ( ) . script ) {
104
+ ( true , true ) => { }
105
+ ( true , false ) => {
106
+ return Err ( anyhow:: anyhow!( "running the file `{cmd}` requires `-Zscript`" ) . into ( ) ) ;
107
+ }
108
+ ( false , true ) => {
109
+ let possible_commands = crate :: list_commands ( config) ;
110
+ let is_dir = if manifest_path. is_dir ( ) {
111
+ format ! ( "\n \t `{cmd}` is a directory" )
112
+ } else {
113
+ "" . to_owned ( )
114
+ } ;
115
+ let suggested_command = if let Some ( suggested_command) = possible_commands
116
+ . keys ( )
117
+ . filter ( |c| cmd. starts_with ( c. as_str ( ) ) )
118
+ . max_by_key ( |c| c. len ( ) )
119
+ {
120
+ let actual_args = cmd. strip_prefix ( suggested_command) . unwrap ( ) ;
121
+ let args = if args. is_empty ( ) {
122
+ "" . to_owned ( )
123
+ } else {
124
+ format ! (
125
+ " {}" ,
126
+ args. into_iter( ) . map( |os| os. to_string_lossy( ) ) . join( " " )
127
+ )
128
+ } ;
129
+ format ! ( "\n \t Did you mean the command `{suggested_command} {actual_args}{args}`" )
130
+ } else {
131
+ "" . to_owned ( )
132
+ } ;
133
+ let suggested_script = if let Some ( suggested_script) = suggested_script ( cmd) {
134
+ format ! ( "\n \t Did you mean the file `{suggested_script}`" )
135
+ } else {
136
+ "" . to_owned ( )
137
+ } ;
138
+ return Err ( anyhow:: anyhow!(
139
+ "no such file or subcommand `{cmd}`{is_dir}{suggested_command}{suggested_script}"
140
+ )
141
+ . into ( ) ) ;
142
+ }
143
+ ( false , false ) => {
144
+ // HACK: duplicating the above for minor tweaks but this will all go away on
145
+ // stabilization
146
+ let possible_commands = crate :: list_commands ( config) ;
147
+ let suggested_command = if let Some ( suggested_command) = possible_commands
148
+ . keys ( )
149
+ . filter ( |c| cmd. starts_with ( c. as_str ( ) ) )
150
+ . max_by_key ( |c| c. len ( ) )
151
+ {
152
+ let actual_args = cmd. strip_prefix ( suggested_command) . unwrap ( ) ;
153
+ let args = if args. is_empty ( ) {
154
+ "" . to_owned ( )
155
+ } else {
156
+ format ! (
157
+ " {}" ,
158
+ args. into_iter( ) . map( |os| os. to_string_lossy( ) ) . join( " " )
159
+ )
160
+ } ;
161
+ format ! ( "\n \t Did you mean the command `{suggested_command} {actual_args}{args}`" )
162
+ } else {
163
+ "" . to_owned ( )
164
+ } ;
165
+ let suggested_script = if let Some ( suggested_script) = suggested_script ( cmd) {
166
+ format ! ( "\n \t Did you mean the file `{suggested_script}` with `-Zscript`" )
167
+ } else {
168
+ "" . to_owned ( )
169
+ } ;
170
+ return Err ( anyhow:: anyhow!(
171
+ "no such subcommand `{cmd}`{suggested_command}{suggested_script}"
172
+ )
173
+ . into ( ) ) ;
174
+ }
102
175
}
103
176
104
- let manifest_path = Path :: new ( cmd) ;
105
177
let manifest_path = root_manifest ( Some ( manifest_path) , config) ?;
106
178
107
179
// Treat `cargo foo.rs` like `cargo install --path foo` and re-evaluate the config based on the
@@ -123,6 +195,39 @@ pub fn exec_manifest_command(config: &mut Config, cmd: &str, args: &[OsString])
123
195
cargo:: ops:: run ( & ws, & compile_opts, args) . map_err ( |err| to_run_error ( config, err) )
124
196
}
125
197
198
+ fn suggested_script ( cmd : & str ) -> Option < String > {
199
+ let cmd_path = Path :: new ( cmd) ;
200
+ let mut suggestion = Path :: new ( "." ) . to_owned ( ) ;
201
+ for cmd_part in cmd_path. components ( ) {
202
+ let exact_match = suggestion. join ( cmd_part) ;
203
+ suggestion = if exact_match. exists ( ) {
204
+ exact_match
205
+ } else {
206
+ let possible: Vec < _ > = std:: fs:: read_dir ( suggestion)
207
+ . into_iter ( )
208
+ . flatten ( )
209
+ . filter_map ( |e| e. ok ( ) )
210
+ . map ( |e| e. path ( ) )
211
+ . filter ( |p| p. to_str ( ) . is_some ( ) )
212
+ . collect ( ) ;
213
+ if let Some ( possible) = closest (
214
+ cmd_part. as_os_str ( ) . to_str ( ) . unwrap ( ) ,
215
+ possible. iter ( ) ,
216
+ |p| p. file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ,
217
+ ) {
218
+ possible. to_owned ( )
219
+ } else {
220
+ return None ;
221
+ }
222
+ } ;
223
+ }
224
+ if suggestion. is_dir ( ) {
225
+ None
226
+ } else {
227
+ suggestion. into_os_string ( ) . into_string ( ) . ok ( )
228
+ }
229
+ }
230
+
126
231
fn to_run_error ( config : & cargo:: util:: Config , err : anyhow:: Error ) -> CliError {
127
232
let proc_err = match err. downcast_ref :: < ProcessError > ( ) {
128
233
Some ( e) => e,
0 commit comments