44// file that was distributed with this source code.
55
66use clap:: builder:: ValueParser ;
7- use clap:: { Arg , ArgAction , ArgMatches , Command } ;
7+ use clap:: { Arg , ArgAction , Command } ;
88use std:: env;
99use std:: ffi:: { OsStr , OsString } ;
1010use std:: io:: { self , StdoutLock , Write } ;
@@ -23,63 +23,104 @@ mod options {
2323 pub const DISABLE_BACKSLASH_ESCAPE : & str = "disable_backslash_escape" ;
2424}
2525
26- fn is_echo_flag ( arg : & OsString ) -> bool {
27- matches ! ( arg. to_str( ) , Some ( "-e" | "-E" | "-n" ) )
26+ /// Holds the options for echo command:
27+ /// -n (disable newline)
28+ /// -e/-E (escape handling),
29+ struct EchoOptions {
30+ /// -n flag option: if true, output a trailing newline (-n disables it)
31+ /// Default: true
32+ pub trailing_newline : bool ,
33+
34+ /// -e enables escape interpretation, -E disables it
35+ /// Default: false (escape interpretation disabled)
36+ pub escape : bool ,
2837}
2938
30- // A workaround because clap interprets the first '--' as a marker that a value
31- // follows. In order to use '--' as a value, we have to inject an additional '--'
32- fn handle_double_hyphens ( args : impl uucore :: Args ) -> impl uucore :: Args {
33- let mut result = Vec :: new ( ) ;
34- let mut is_first_argument = true ;
35- let mut args_iter = args . into_iter ( ) ;
36-
37- if let Some ( first_val ) = args_iter . next ( ) {
38- // the first argument ('echo') gets pushed before we start with the checks for flags/'--'
39- result . push ( first_val ) ;
40- // We need to skip any possible Flag arguments until we find the first argument to echo that
41- // is not a flag. If the first argument is double hyphen we inject an additional '--'
42- // otherwise we switch is_first_argument boolean to skip the checks for any further arguments
43- for arg in args_iter {
44- if is_first_argument && ! is_echo_flag ( & arg ) {
45- is_first_argument = false ;
46- if arg == "--" {
47- result . push ( OsString :: from ( "--" ) ) ;
48- }
39+ /// Checks if an argument is a valid echo flag
40+ /// Returns true if valid echo flag found
41+ fn is_echo_flag ( arg : & OsString , echo_options : & mut EchoOptions ) -> bool {
42+ let bytes = arg . as_encoded_bytes ( ) ;
43+ if bytes . first ( ) == Some ( & b'-' ) && arg != "-" {
44+ // we initialize our local variables to the "current" options so we don't override
45+ // previous found flags
46+ let mut escape = echo_options . escape ;
47+ let mut trailing_newline = echo_options . trailing_newline ;
48+
49+ // Process characters after the '-'
50+ for c in & bytes [ 1 .. ] {
51+ match c {
52+ b'e' => escape = true ,
53+ b'E' => escape = false ,
54+ b'n' => trailing_newline = false ,
55+ // if there is any char in an argument starting with '-' that doesn't match e/E/n
56+ // present means that this argument is not a flag
57+ _ => return false ,
4958 }
50- result. push ( arg) ;
5159 }
60+
61+ // we only override the options with flags being found once we parsed the whole argument
62+ echo_options. escape = escape;
63+ echo_options. trailing_newline = trailing_newline;
64+ return true ;
5265 }
5366
54- result. into_iter ( )
67+ // argument doesn't start with '-' or is "-" => no flag
68+ false
5569}
5670
57- fn collect_args ( matches : & ArgMatches ) -> Vec < OsString > {
58- matches
59- . get_many :: < OsString > ( options:: STRING )
60- . map_or_else ( Vec :: new, |values| values. cloned ( ) . collect ( ) )
71+ /// Processes command line arguments, separating flags from normal arguments
72+ /// Returns:
73+ /// - Vector of non-flag arguments
74+ /// - trailing_newline: whether to print a trailing newline
75+ /// - escape: whether to process escape sequences
76+ fn filter_echo_flags ( args : impl uucore:: Args ) -> ( Vec < OsString > , bool , bool ) {
77+ let mut result = Vec :: new ( ) ;
78+ let mut echo_options = EchoOptions {
79+ trailing_newline : true ,
80+ escape : false ,
81+ } ;
82+ let mut args_iter = args. into_iter ( ) ;
83+
84+ // Process arguments until first non-flag is found
85+ for arg in & mut args_iter {
86+ // we parse flags and store options found in "echo_option". First is_echo_flag
87+ // call to return false will break the loop and we will collect the remaining arguments
88+ if !is_echo_flag ( & arg, & mut echo_options) {
89+ // First non-flag argument stops flag processing
90+ result. push ( arg) ;
91+ break ;
92+ }
93+ }
94+ // Collect remaining arguments
95+ for arg in args_iter {
96+ result. push ( arg) ;
97+ }
98+ ( result, echo_options. trailing_newline , echo_options. escape )
6199}
62100
63101#[ uucore:: main]
64102pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
65- let is_posixly_correct = env:: var ( "POSIXLY_CORRECT" ) . is_ok ( ) ;
103+ // Check POSIX compatibility mode
104+ let is_posixly_correct = env:: var_os ( "POSIXLY_CORRECT" ) . is_some ( ) ;
66105
106+ let args_iter = args. skip ( 1 ) ;
67107 let ( args, trailing_newline, escaped) = if is_posixly_correct {
68- let mut args_iter = args . skip ( 1 ) . peekable ( ) ;
108+ let mut args_iter = args_iter . peekable ( ) ;
69109
70110 if args_iter. peek ( ) == Some ( & OsString :: from ( "-n" ) ) {
71- let matches = uu_app ( ) . get_matches_from ( handle_double_hyphens ( args_iter) ) ;
72- let args = collect_args ( & matches) ;
111+ // if POSIXLY_CORRECT is set and the first argument is the "-n" flag
112+ // we filter flags normally but 'escaped' is activated nonetheless
113+ let ( args, _, _) = filter_echo_flags ( args_iter) ;
73114 ( args, false , true )
74115 } else {
75- let args: Vec < _ > = args_iter. collect ( ) ;
116+ // if POSIXLY_CORRECT is set and the first argument is not the "-n" flag
117+ // we just collect all arguments as every argument is considered an argument
118+ let args: Vec < OsString > = args_iter. collect ( ) ;
76119 ( args, true , true )
77120 }
78121 } else {
79- let matches = uu_app ( ) . get_matches_from ( handle_double_hyphens ( args. into_iter ( ) ) ) ;
80- let trailing_newline = !matches. get_flag ( options:: NO_NEWLINE ) ;
81- let escaped = matches. get_flag ( options:: ENABLE_BACKSLASH_ESCAPE ) ;
82- let args = collect_args ( & matches) ;
122+ // if POSIXLY_CORRECT is not set we filter the flags normally
123+ let ( args, trailing_newline, escaped) = filter_echo_flags ( args_iter) ;
83124 ( args, trailing_newline, escaped)
84125 } ;
85126
0 commit comments