11use std:: sync:: Arc ;
22
3- use leptos:: { either :: * , prelude:: * } ;
3+ use leptos:: prelude:: * ;
44use leptos_router:: components:: * ;
55
66use crate :: app:: terminal:: fs:: DirContentItem ;
@@ -27,18 +27,23 @@ impl Executable for LsCommand {
2727 is_output_tty : bool ,
2828 ) -> CommandRes {
2929 let mut all = false ;
30+ let mut long_format = false ;
3031 let ( options, mut target_paths) = parse_multitarget ( args) ;
31- let invalid = options. iter ( ) . find ( |c| * * c != 'a' ) ;
32+ let invalid = options. iter ( ) . find ( |c| * * c != 'a' && * * c != 'l' ) ;
3233 if let Some ( c) = invalid {
3334 let c = c. to_owned ( ) ;
3435 let error_msg = format ! (
3536 r#"ls: invalid option -- '{c}'
36- This version of ls only supports option 'a'"#
37+ This version of ls only supports options 'a' and 'l '"#
3738 ) ;
3839 return CommandRes :: new ( ) . with_error ( ) . with_stderr ( error_msg) ;
3940 }
40- if !options. is_empty ( ) {
41- all = true ;
41+ for option in & options {
42+ match option {
43+ 'a' => all = true ,
44+ 'l' => long_format = true ,
45+ _ => unreachable ! ( "Invalid options should be caught above" ) ,
46+ }
4247 }
4348 if target_paths. is_empty ( ) {
4449 target_paths = vec ! [ "" ] ;
@@ -84,6 +89,7 @@ This version of ls only supports option 'a'"#
8489 let is_multi =
8590 dir_targets. len ( ) > 1 || !dir_targets. is_empty ( ) && !file_targets. is_empty ( ) ;
8691 let all_captured = all;
92+ let long_format_captured = long_format;
8793 let path_owned = path. to_owned ( ) ;
8894 result = result. with_stdout_view ( Arc :: new ( move || {
8995 let mut all_views = Vec :: new ( ) ;
@@ -95,6 +101,8 @@ This version of ls only supports option 'a'"#
95101 . map ( |( s, t) | DirContentItem ( s. to_string ( ) , t. to_owned ( ) ) )
96102 . collect ( ) ,
97103 base : path_owned. clone ( ) ,
104+ long_format : long_format_captured,
105+ blog_post_count : posts. len ( ) ,
98106 } )
99107 . into_any ( ) ,
100108 ) ;
@@ -119,6 +127,8 @@ This version of ls only supports option 'a'"#
119127 LsView ( LsViewProps {
120128 items : d. contents ( & posts, all_captured) ,
121129 base : d. base ( ) ,
130+ long_format : long_format_captured,
131+ blog_post_count : posts. len ( ) ,
122132 } )
123133 . into_any ( ) ,
124134 ) ;
@@ -137,7 +147,19 @@ This version of ls only supports option 'a'"#
137147 if !stdout_text. is_empty ( ) {
138148 stdout_text. push ( '\n' ) ;
139149 }
140- stdout_text. push_str ( f. name ( ) ) ;
150+ if long_format {
151+ stdout_text. push_str ( & format ! (
152+ "{} {:2} {:8} {:8} {:>6} {}" ,
153+ target. full_permissions( ) ,
154+ target. link_count( self . blog_posts. len( ) ) ,
155+ target. owner( ) ,
156+ target. group( ) ,
157+ target. size( ) ,
158+ f. name( )
159+ ) ) ;
160+ } else {
161+ stdout_text. push_str ( f. name ( ) ) ;
162+ }
141163 }
142164 }
143165 }
@@ -156,7 +178,19 @@ This version of ls only supports option 'a'"#
156178 if i > 0 || ( !is_multi && !stdout_text. is_empty ( ) ) {
157179 stdout_text. push ( '\n' ) ;
158180 }
159- stdout_text. push_str ( item. text_content ( ) ) ;
181+ if long_format {
182+ stdout_text. push_str ( & format ! (
183+ "{} {:2} {:8} {:8} {:>6} {}" ,
184+ item. 1 . full_permissions( ) ,
185+ item. 1 . link_count( self . blog_posts. len( ) ) ,
186+ item. 1 . owner( ) ,
187+ item. 1 . group( ) ,
188+ item. 1 . size( ) ,
189+ item. text_content( )
190+ ) ) ;
191+ } else {
192+ stdout_text. push_str ( item. text_content ( ) ) ;
193+ }
160194 }
161195 }
162196
@@ -170,36 +204,110 @@ This version of ls only supports option 'a'"#
170204}
171205
172206#[ component]
173- fn LsView ( items : Vec < DirContentItem > , base : String ) -> impl IntoView {
207+ fn LsView (
208+ items : Vec < DirContentItem > ,
209+ base : String ,
210+ #[ prop( default = false ) ] long_format : bool ,
211+ #[ prop( default = 0 ) ] blog_post_count : usize ,
212+ ) -> impl IntoView {
174213 let dir_class = "text-blue" ;
175214 let ex_class = "text-green" ;
176- let render_func = {
177- move |s : DirContentItem | {
178- if matches ! ( s. 1 , Target :: Dir ( _) ) {
215+
216+ // Create modified items for long format display if needed
217+ let display_items = if long_format {
218+ items
219+ . into_iter ( )
220+ . map ( |item| {
221+ let long_info = format ! (
222+ "{} {:2} {:8} {:8} {:>6} {}" ,
223+ item. 1 . full_permissions( ) ,
224+ item. 1 . link_count( blog_post_count) ,
225+ item. 1 . owner( ) ,
226+ item. 1 . group( ) ,
227+ item. 1 . size( ) ,
228+ item. 0
229+ ) ;
230+ DirContentItem ( long_info, item. 1 )
231+ } )
232+ . collect :: < Vec < _ > > ( )
233+ } else {
234+ items
235+ } ;
236+
237+ if long_format {
238+ let long_render_func = move |s : DirContentItem | {
239+ // Find the last space before the filename to preserve original formatting
240+ let last_space_pos = s. 0 . rfind ( ' ' ) . unwrap ( ) ;
241+ let metadata_with_spaces = s. 0 [ ..last_space_pos] . to_string ( ) ;
242+ let filename = s. 0 [ last_space_pos + 1 ..] . to_string ( ) ;
243+
244+ // Create the styled filename part
245+ let styled_filename = if matches ! ( s. 1 , Target :: Dir ( _) ) {
179246 let base = if base == "/" { "" } else { & base } ;
180- let href = if s . 0 == "." {
247+ let href = if filename == "." {
181248 base. to_string ( )
182249 } else {
183- format ! ( "{}/{}" , base, s . 0 )
250+ format ! ( "{}/{}" , base, filename )
184251 } ;
185- // note - adding extra space because trimming trailing '/'
186- EitherOf3 :: A ( view ! {
252+ view ! {
187253 <A href=href attr: class=dir_class>
188- { s . text_content ( ) . to_string ( ) }
254+ { filename }
189255 </A >
190- } )
256+ } . into_any ( )
191257 } else if s. 1 . is_executable ( ) {
192- EitherOf3 :: B ( view ! { <span class=ex_class>{ s . text_content ( ) } </span> } )
258+ view ! { <span class=ex_class>{ filename } </span> } . into_any ( )
193259 } else {
194- EitherOf3 :: C ( view ! { <span>{ s. text_content( ) } </span> } )
195- }
260+ view ! { <span>{ filename} </span> } . into_any ( )
261+ } ;
262+
263+ view ! { <span>{ metadata_with_spaces} " " { styled_filename} </span> }
196264 . into_any ( )
265+ } ;
266+
267+ view ! {
268+ <div>
269+ { display_items
270+ . into_iter( )
271+ . map( |item| {
272+ view! {
273+ { long_render_func( item) }
274+ "\n "
275+ }
276+ } )
277+ . collect_view( ) }
278+ </div>
197279 }
198- } ;
199- view ! {
200- <div>
201- <ColumnarView items render_func />
202- </div>
280+ . into_any ( )
281+ } else {
282+ let short_render_func = {
283+ move |s : DirContentItem | {
284+ if matches ! ( s. 1 , Target :: Dir ( _) ) {
285+ let base = if base == "/" { "" } else { & base } ;
286+ let href = if s. 0 == "." {
287+ base. to_string ( )
288+ } else {
289+ format ! ( "{}/{}" , base, s. 0 )
290+ } ;
291+ view ! {
292+ <A href=href attr: class=dir_class>
293+ { s. text_content( ) . to_string( ) }
294+ </A >
295+ }
296+ . into_any ( )
297+ } else if s. 1 . is_executable ( ) {
298+ view ! { <span class=ex_class>{ s. text_content( ) } </span> } . into_any ( )
299+ } else {
300+ view ! { <span>{ s. text_content( ) } </span> } . into_any ( )
301+ }
302+ }
303+ } ;
304+
305+ view ! {
306+ <div>
307+ <ColumnarView items=display_items render_func=short_render_func />
308+ </div>
309+ }
310+ . into_any ( )
203311 }
204312}
205313
0 commit comments