@@ -11,6 +11,7 @@ use std::io::{self, StdoutLock, Write};
11
11
use uucore:: error:: { UResult , USimpleError } ;
12
12
use uucore:: format:: { FormatChar , OctalParsing , parse_escape_only} ;
13
13
use uucore:: format_usage;
14
+ use uucore:: os_str_as_bytes;
14
15
15
16
use uucore:: locale:: get_message;
16
17
@@ -21,109 +22,157 @@ mod options {
21
22
pub const DISABLE_BACKSLASH_ESCAPE : & str ="disable_backslash_escape" ;
22
23
}
23
24
24
- ///Holds the options for echo command:
25
- /// -n (disable newline)
26
- /// -e/-E (escape handling),
27
- struct EchoOptions {
28
- /// -n flag option: if true, output a trailing newline (-n disables it)
29
- ///Default: true
25
+ ///Options forthe echo command.
26
+ # [ derive ( Debug , Clone , Copy ) ]
27
+ struct Options {
28
+ /// Whether the output should have a trailing newline.
29
+ ///
30
+ ///True by default. `-n` disables it.
30
31
pub trailing_newline : bool ,
31
32
32
- /// -e enables escape interpretation, -E disables it
33
- /// Default: false (escape interpretation disabled)
33
+ /// Whether given string literals should be parsed for
34
+ /// escape characters.
35
+ ///
36
+ /// False by default, can be enabled with `-e`. Always true if
37
+ /// `POSIXLY_CORRECT` (cannot be disabled with `-E`).
34
38
pub escape : bool ,
35
39
}
36
40
37
- /// Checks if an argument is a valid echo flag
38
- /// Returns true if valid echo flag found
39
- fn is_echo_flag ( arg : & OsString , echo_options : & mut EchoOptions ) ->bool {
40
- let bytes = arg. as_encoded_bytes ( ) ;
41
- if bytes. first ( ) ==Some ( & b'-' ) && arg !="-" {
42
- // we initialize our local variables to the "current" options so we don't override
43
- // previous found flags
44
- let mut escape = echo_options. escape ;
45
- let mut trailing_newline = echo_options. trailing_newline ;
46
-
47
- // Process characters after the '-'
48
- for cin & bytes[ 1 ..] {
49
- match c{
50
- b'e' => escape =true ,
51
- b'E' => escape =false ,
52
- b'n' => trailing_newline =false ,
53
- // if there is any char in an argument starting with '-' that doesn't match e/E/n
54
- // present means that this argument is not a flag
55
- _ =>return false ,
56
- }
41
+ impl Default for Options {
42
+ fn default ( ) ->Self {
43
+ Self {
44
+ trailing_newline : true ,
45
+ escape : false ,
46
+ }
47
+ }
48
+ }
49
+
50
+ impl Options {
51
+ fn posixly_correct_default ( ) ->Self {
52
+ Self {
53
+ trailing_newline : true ,
54
+ escape : true ,
57
55
}
56
+ }
57
+ }
58
+
59
+ /// Checks if an argument is a valid echo flag, and if
60
+ /// it is records the changes in [`Options`].
61
+ fn is_flag ( arg : & OsStr , options : & mut Options ) ->bool {
62
+ let arg = arg. as_encoded_bytes ( ) ;
63
+
64
+ if arg. first ( ) !=Some ( & b'-' ) || arg ==b"-" {
65
+ // Argument doesn't start with '-' or is '-' => not a flag.
66
+ return false ;
67
+ }
58
68
59
- // we only override the options with flags being found once we parsed the whole argument
60
- echo_options. escape = escape;
61
- echo_options. trailing_newline = trailing_newline;
62
- return true ;
69
+ // We don't modify the given options until after
70
+ // the loop because there is a chance the flag isn't
71
+ // valid after all & shouldn't affect the options.
72
+ let mut options_: Options =* options;
73
+
74
+ // Skip the '-' when processing characters.
75
+ for cin & arg[ 1 ..] {
76
+ match c{
77
+ b'e' => options_. escape =true ,
78
+ b'E' => options_. escape =false ,
79
+ b'n' => options_. trailing_newline =false ,
80
+
81
+ // If there is any character in an supposed flag
82
+ // that is not a valid flag character, it is not
83
+ // a flag.
84
+ //
85
+ // "-eeEnEe" => is a flag.
86
+ // "-eeBne" => not a flag, short circuit at the B.
87
+ _ =>return false ,
88
+ }
63
89
}
64
90
65
- // argument doesn't start with '-' or is "-" => no flag
66
- false
91
+ // We are now sure that the argument is a
92
+ // flag, and can apply the modified options.
93
+ * options = options_;
94
+ true
67
95
}
68
96
69
- /// Processes command line arguments, separating flags from normal arguments
70
- /// Returns:
71
- /// - Vector of non-flag arguments
72
- /// - `trailing_newline`: whether to print a trailing newline
73
- /// - `escape`: whether to process escape sequences
74
- fn filter_echo_flags ( args : impl uucore:: Args ) ->( Vec < OsString > , bool , bool ) {
75
- let mut result =Vec :: new ( ) ;
76
- let mut echo_options =EchoOptions {
77
- trailing_newline : true ,
78
- escape : false ,
79
- } ;
80
- let mut args_iter = args. into_iter ( ) ;
81
-
82
- // Process arguments until first non-flag is found
83
- for argin & mut args_iter{
84
- // we parse flags and store options found in "echo_option". First is_echo_flag
85
- // call to return false will break the loop and we will collect the remaining arguments
86
- if !is_echo_flag ( & arg, & mut echo_options) {
87
- // First non-flag argument stops flag processing
88
- result. push ( arg) ;
97
+ /// Processes command line arguments, separating flags from normal arguments.
98
+ ///
99
+ /// # Returns
100
+ ///
101
+ /// - Vector of non-flag arguments.
102
+ /// - [`Options`], describing how teh arguments should be interpreted.
103
+ fn filter_flags ( mut args : impl Iterator < Item =OsString > ) ->( Vec < OsString > , Options ) {
104
+ let mut arguments =Vec :: with_capacity ( args. size_hint ( ) . 0 ) ;
105
+ let mut options =Options :: default ( ) ;
106
+
107
+ // Process arguments until first non-flag is found.
108
+ for argin & mut args{
109
+ // We parse flags and aggregate the options in `options`.
110
+ // First call to `is_echo_flag` to return false will break the loop.
111
+ if !is_flag ( & arg, & mut options) {
112
+ // Not a flag. Can break out of flag-processing loop.
113
+ // Don't forget to push it to the arguments too.
114
+ arguments. push ( arg) ;
89
115
break ;
90
116
}
91
117
}
92
- // Collect remaining arguments
93
- for arg in args_iter {
94
- result . push ( arg ) ;
95
- }
96
- ( result , echo_options . trailing_newline , echo_options . escape )
118
+
119
+ // Collect remaining non-flag arguments.
120
+ arguments . extend ( args ) ;
121
+
122
+ ( arguments , options )
97
123
}
98
124
99
125
#[ uucore:: main]
100
126
pub fn uumain ( args : impl uucore:: Args ) ->UResult < ( ) > {
127
+ // args[0] is the name of the binary.
128
+ let args: Vec < OsString > = args. skip ( 1 ) . collect ( ) ;
129
+
101
130
// Check POSIX compatibility mode
131
+ //
132
+ // From the GNU manual, on what it should do:
133
+ //
134
+ // > If the POSIXLY_CORRECT environment variable is set, then when
135
+ // > echo’s first argument is not -n it outputs option-like arguments
136
+ // > instead of treating them as options. For example, echo -ne hello
137
+ // > outputs ‘-ne hello’ instead of plain ‘hello’. Also backslash
138
+ // > escapes are always enabled. To echo the string ‘-n’, one of the
139
+ // > characters can be escaped in either octal or hexadecimal
140
+ // > representation. For example, echo -e '\x2dn'.
102
141
let is_posixly_correct = env:: var_os ( "POSIXLY_CORRECT" ) . is_some ( ) ;
103
142
104
- let args_iter = args. skip ( 1 ) ;
105
- let ( args, trailing_newline, escaped) =if is_posixly_correct{
106
- let mut args_iter = args_iter. peekable ( ) ;
107
-
108
- if args_iter. peek ( ) ==Some ( & OsString :: from ( "-n" ) ) {
143
+ let ( args, options) =if is_posixly_correct{
144
+ if args. first ( ) . is_some_and ( |arg| arg =="-n" ) {
109
145
// if POSIXLY_CORRECT is set and the first argument is the "-n" flag
110
- // we filter flags normally but 'escaped' is activated nonetheless
111
- let ( args, _, _) =filter_echo_flags ( args_iter) ;
112
- ( args, false , true )
146
+ // we filter flags normally but 'escaped' is activated nonetheless.
147
+ let ( args, _) =filter_flags ( args. into_iter ( ) ) ;
148
+ (
149
+ args,
150
+ Options {
151
+ trailing_newline : false ,
152
+ ..Options :: posixly_correct_default ( )
153
+ } ,
154
+ )
113
155
} else {
114
156
// if POSIXLY_CORRECT is set and the first argument is not the "-n" flag
115
- // we just collect all arguments as every argument is considered an argument
116
- let args: Vec < OsString > = args_iter. collect ( ) ;
117
- ( args, true , true )
157
+ // we just collect all arguments as no arguments are interpreted as flags.
158
+ ( args, Options :: posixly_correct_default ( ) )
118
159
}
160
+ } else if args. len ( ) ==1 && args[ 0 ] =="--help" {
161
+ // If POSIXLY_CORRECT is not set and the first argument
162
+ // is `--help`, GNU coreutils prints the help message.
163
+ //
164
+ // Verify this using:
165
+ //
166
+ // POSIXLY_CORRECT=1 echo --help
167
+ // echo --help
168
+ uu_app ( ) . print_help ( ) ?;
169
+ return Ok ( ( ) ) ;
119
170
} else {
120
171
// if POSIXLY_CORRECT is not set we filter the flags normally
121
- let ( args, trailing_newline, escaped) =filter_echo_flags ( args_iter) ;
122
- ( args, trailing_newline, escaped)
172
+ filter_flags ( args. into_iter ( ) )
123
173
} ;
124
174
125
- let mut stdout_lock = io:: stdout ( ) . lock ( ) ;
126
- execute ( & mut stdout_lock, args, trailing_newline, escaped) ?;
175
+ execute ( & mut io:: stdout ( ) . lock ( ) , args, options) ?;
127
176
128
177
Ok ( ( ) )
129
178
}
@@ -169,51 +218,29 @@ pub fn uu_app() -> Command {
169
218
)
170
219
}
171
220
172
- fn execute (
173
- stdout_lock : & mut StdoutLock ,
174
- arguments_after_options : Vec < OsString > ,
175
- trailing_newline : bool ,
176
- escaped : bool ,
177
- ) ->UResult < ( ) > {
178
- for ( i, input) in arguments_after_options. into_iter ( ) . enumerate ( ) {
179
- let Some ( bytes) =bytes_from_os_string ( input. as_os_str ( ) ) else {
180
- return Err ( USimpleError :: new ( 1 , get_message ( "echo-error-non-utf8" ) ) ) ;
181
- } ;
221
+ fn execute ( stdout : & mut StdoutLock , args : Vec < OsString > , options : Options ) ->UResult < ( ) > {
222
+ for ( i, arg) in args. into_iter ( ) . enumerate ( ) {
223
+ let bytes =os_str_as_bytes ( arg. as_os_str ( ) )
224
+ . map_err ( |_|USimpleError :: new ( 1 , get_message ( "echo-error-non-utf8" ) ) ) ?;
182
225
183
226
if i >0 {
184
- stdout_lock . write_all ( b" " ) ?;
227
+ stdout . write_all ( b" " ) ?;
185
228
}
186
229
187
- if escaped {
230
+ if options . escape {
188
231
for itemin parse_escape_only ( bytes, OctalParsing :: ThreeDigits ) {
189
- if item. write ( & mut * stdout_lock ) ?. is_break ( ) {
232
+ if item. write ( & mut * stdout ) ?. is_break ( ) {
190
233
return Ok ( ( ) ) ;
191
234
}
192
235
}
193
236
} else {
194
- stdout_lock . write_all ( bytes) ?;
237
+ stdout . write_all ( bytes) ?;
195
238
}
196
239
}
197
240
198
- if trailing_newline{
199
- stdout_lock . write_all ( b"\n " ) ?;
241
+ if options . trailing_newline {
242
+ stdout . write_all ( b"\n " ) ?;
200
243
}
201
244
202
245
Ok ( ( ) )
203
246
}
204
-
205
- fn bytes_from_os_string ( input : & OsStr ) ->Option < & [ u8 ] > {
206
- #[ cfg( target_family ="unix" ) ]
207
- {
208
- use std:: os:: unix:: ffi:: OsStrExt ;
209
-
210
- Some ( input. as_bytes ( ) )
211
- }
212
-
213
- #[ cfg( not( target_family ="unix" ) ) ]
214
- {
215
- // TODO
216
- // Verify that this works correctly on these platforms
217
- input. to_str ( ) . map ( |st| st. as_bytes ( ) )
218
- }
219
- }