@@ -21,109 +21,149 @@ mod options {
2121 pub const DISABLE_BACKSLASH_ESCAPE : & str = "disable_backslash_escape" ;
2222}
2323
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
24+ /// Options for the echo command.
25+ # [ derive ( Debug , Clone , Copy ) ]
26+ struct Options {
27+ /// Whether the output should have a trailing newline.
28+ ///
29+ /// True by default. `-n` disables it.
3030 pub trailing_newline : bool ,
3131
32- /// -e enables escape interpretation, -E disables it
33- /// Default: false (escape interpretation disabled)
32+ /// Whether given string literals should be parsed for
33+ /// escape characters.
34+ ///
35+ /// False by default, can be enabled with `-e`. Always true if
36+ /// `POSIXLY_CORRECT` (cannot be disabled with `-E`).
3437 pub escape : bool ,
3538}
3639
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 c in & 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- }
40+ impl Default for Options {
41+ fn default ( ) -> Self {
42+ Self {
43+ trailing_newline : true ,
44+ escape : false ,
5745 }
46+ }
47+ }
5848
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 ;
49+ impl Options {
50+ fn posixly_correct_default ( ) -> Self {
51+ Self {
52+ trailing_newline : true ,
53+ escape : true ,
54+ }
55+ }
56+ }
57+
58+ /// Checks if an argument is a valid echo flag, and if
59+ /// it is records the changes in [`Options`].
60+ fn is_flag ( arg : & OsStr , options : & mut Options ) -> bool {
61+ let arg = arg. as_encoded_bytes ( ) ;
62+
63+ if arg. first ( ) != Some ( & b'-' ) || arg == b"-" {
64+ // Argument doesn't start with '-' or is '-' => not a flag.
65+ return false ;
66+ }
67+
68+ // We don't modify the given options until after
69+ // the loop because there is a chance the flag isn't
70+ // valid after all & shouldn't affect the options.
71+ let mut options_: Options = * options;
72+
73+ // Skip the '-' when processing characters.
74+ for c in & arg[ 1 ..] {
75+ match c {
76+ b'e' => options_. escape = true ,
77+ b'E' => options_. escape = false ,
78+ b'n' => options_. trailing_newline = false ,
79+
80+ // If there is any character in an supposed flag
81+ // that is not a valid flag character, it is not
82+ // a flag.
83+ //
84+ // "-eeEnEe" => is a flag.
85+ // "-eeBne" => not a flag, short circuit at the B.
86+ _ => return false ,
87+ }
6388 }
6489
65- // argument doesn't start with '-' or is "-" => no flag
66- false
90+ // We are now sure that the argument is a
91+ // flag, and can apply the modified options.
92+ * options = options_;
93+ true
6794}
6895
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 arg in & 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) ;
96+ /// Processes command line arguments, separating flags from normal arguments.
97+ ///
98+ /// # Returns
99+ ///
100+ /// - Vector of non-flag arguments.
101+ /// - [`Options`], describing how teh arguments should be interpreted.
102+ fn filter_flags ( mut args : impl uucore:: Args ) -> ( Vec < OsString > , Options ) {
103+ let mut arguments = Vec :: with_capacity ( args. size_hint ( ) . 0 ) ;
104+ let mut options = Options :: default ( ) ;
105+
106+ // Process arguments until first non-flag is found.
107+ for arg in & mut args {
108+ // We parse flags and aggregate the options in `options`.
109+ // First call to `is_echo_flag` to return false will break the loop.
110+ if !is_flag ( & arg, & mut options) {
111+ // Not a flag. Can break out of flag-processing loop.
112+ // Don't forget to push it to the arguments too.
113+ arguments. push ( arg) ;
89114 break ;
90115 }
91116 }
92- // Collect remaining arguments
93- for arg in args_iter {
94- result . push ( arg ) ;
95- }
96- ( result , echo_options . trailing_newline , echo_options . escape )
117+
118+ // Collect remaining non-flag arguments.
119+ arguments . extend ( args ) ;
120+
121+ ( arguments , options )
97122}
98123
99124#[ uucore:: main]
100125pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
126+ // args[0] is the name of the binary.
127+ let mut args = args. skip ( 1 ) . peekable ( ) ;
128+
101129 // Check POSIX compatibility mode
130+ //
131+ // From the GNU manual, on what it should do:
132+ //
133+ // > If the POSIXLY_CORRECT environment variable is set, then when
134+ // > echo’s first argument is not -n it outputs option-like arguments
135+ // > instead of treating them as options. For example, echo -ne hello
136+ // > outputs ‘-ne hello’ instead of plain ‘hello’. Also backslash
137+ // > escapes are always enabled. To echo the string ‘-n’, one of the
138+ // > characters can be escaped in either octal or hexadecimal
139+ // > representation. For example, echo -e '\x2dn'.
102140 let is_posixly_correct = env:: var_os ( "POSIXLY_CORRECT" ) . is_some ( ) ;
103141
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 ( ) ;
142+ let ( args , options ) = match is_posixly_correct {
143+ // if POSIXLY_CORRECT is not set we filter the flags normally
144+ false => filter_flags ( args ) ,
107145
108- if args_iter . peek ( ) == Some ( & OsString :: from ( "-n" ) ) {
146+ true if args . peek ( ) . is_some_and ( |arg| arg == "-n" ) => {
109147 // 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 )
113- } else {
148+ // we filter flags normally but 'escaped' is activated nonetheless.
149+ let ( args, _) = filter_flags ( args) ;
150+ (
151+ args,
152+ Options {
153+ trailing_newline : false ,
154+ ..Options :: posixly_correct_default ( )
155+ } ,
156+ )
157+ }
158+
159+ true => {
114160 // 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 )
161+ // we just collect all arguments as every argument is considered an argument.
162+ ( args. collect ( ) , Options :: posixly_correct_default ( ) )
118163 }
119- } else {
120- // 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)
123164 } ;
124165
125- let mut stdout_lock = io:: stdout ( ) . lock ( ) ;
126- execute ( & mut stdout_lock, args, trailing_newline, escaped) ?;
166+ execute ( & mut io:: stdout ( ) . lock ( ) , args, options) ?;
127167
128168 Ok ( ( ) )
129169}
@@ -169,34 +209,29 @@ pub fn uu_app() -> Command {
169209 )
170210}
171211
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 {
212+ fn execute ( stdout : & mut StdoutLock , args : Vec < OsString > , options : Options ) -> UResult < ( ) > {
213+ for ( i, arg) in args. into_iter ( ) . enumerate ( ) {
214+ let Some ( bytes) = bytes_from_os_string ( arg. as_os_str ( ) ) else {
180215 return Err ( USimpleError :: new ( 1 , get_message ( "echo-error-non-utf8" ) ) ) ;
181216 } ;
182217
183218 if i > 0 {
184- stdout_lock . write_all ( b" " ) ?;
219+ stdout . write_all ( b" " ) ?;
185220 }
186221
187- if escaped {
222+ if options . escape {
188223 for item in parse_escape_only ( bytes, OctalParsing :: ThreeDigits ) {
189- if item. write ( & mut * stdout_lock ) ?. is_break ( ) {
224+ if item. write ( & mut * stdout ) ?. is_break ( ) {
190225 return Ok ( ( ) ) ;
191226 }
192227 }
193228 } else {
194- stdout_lock . write_all ( bytes) ?;
229+ stdout . write_all ( bytes) ?;
195230 }
196231 }
197232
198- if trailing_newline {
199- stdout_lock . write_all ( b"\n " ) ?;
233+ if options . trailing_newline {
234+ stdout . write_all ( b"\n " ) ?;
200235 }
201236
202237 Ok ( ( ) )
@@ -212,8 +247,7 @@ fn bytes_from_os_string(input: &OsStr) -> Option<&[u8]> {
212247
213248 #[ cfg( not( target_family = "unix" ) ) ]
214249 {
215- // TODO
216- // Verify that this works correctly on these platforms
250+ // TODO: Verify that this works correctly on these platforms
217251 input. to_str ( ) . map ( |st| st. as_bytes ( ) )
218252 }
219253}
0 commit comments