@@ -70,6 +70,12 @@ pub(crate) struct ListCommand {
7070 help = "Display ACLs in a table (unstable)"
7171 ) ]
7272 show_acl : bool ,
73+ #[ arg(
74+ short = 'O' ,
75+ long = "show-fflags" ,
76+ help = "Display file flags (uchg, nodump, hidden, etc.)"
77+ ) ]
78+ show_fflags : bool ,
7379 #[ arg(
7480 long = "private" ,
7581 requires = "unstable" ,
@@ -310,6 +316,7 @@ struct TableRow {
310316 xattrs : Vec < ExtendedAttribute > ,
311317 acl : HashMap < chunk:: AcePlatform , Vec < chunk:: Ace > > ,
312318 privates : Vec < RawChunk > ,
319+ fflags : Vec < String > ,
313320}
314321
315322impl TableRow {
@@ -383,6 +390,7 @@ where
383390 . filter ( |it| it. ty ( ) != chunk:: faCe && it. ty ( ) != chunk:: faCl)
384391 . map ( |it| ( * it) . clone ( ) . into ( ) )
385392 . collect :: < Vec < _ > > ( ) ,
393+ fflags : entry. fflags ( ) ,
386394 } )
387395 }
388396}
@@ -408,6 +416,7 @@ fn list_archive(args: ListCommand, color: ColorChoice) -> anyhow::Result<()> {
408416 solid : args. solid ,
409417 show_xattr : args. show_xattr ,
410418 show_acl : args. show_acl ,
419+ show_fflags : args. show_fflags ,
411420 show_private : args. show_private ,
412421 time_format : if args. long_time {
413422 TimeFormat :: Long
@@ -494,6 +503,7 @@ pub(crate) struct ListOptions {
494503 pub ( crate ) solid : bool ,
495504 pub ( crate ) show_xattr : bool ,
496505 pub ( crate ) show_acl : bool ,
506+ pub ( crate ) show_fflags : bool ,
497507 pub ( crate ) show_private : bool ,
498508 pub ( crate ) time_format : TimeFormat ,
499509 pub ( crate ) time_field : TimeField ,
@@ -601,7 +611,7 @@ fn print_formatted_entries(
601611) -> anyhow:: Result < ( ) > {
602612 match options. format {
603613 Some ( Format :: Line ) => simple_list_entries_to ( entries, options, out) ?,
604- Some ( Format :: JsonL ) => json_line_entries_to ( entries, out) ?,
614+ Some ( Format :: JsonL ) => json_line_entries_to ( entries, options , out) ?,
605615 Some ( Format :: Table ) => detail_list_entries_to ( entries, options, out) ?,
606616 Some ( Format :: Tree ) => tree_entries_to ( entries, options, out) ?,
607617 Some ( Format :: BsdTar ) => bsd_tar_list_entries_to ( entries, options, out) ?,
@@ -712,23 +722,24 @@ fn detail_list_entries_to(
712722) -> io:: Result < ( ) > {
713723 let underline = Color :: new ( "\x1B [4m" , "\x1B [0m" ) ;
714724 let reset = Color :: new ( "\x1B [8m" , "\x1B [0m" ) ;
715- let header = [
716- "Encryption" ,
717- "Compression" ,
718- "Permissions" ,
719- "Raw Size" ,
720- "Compressed Size" ,
721- "User" ,
722- "Group" ,
723- options. time_field . as_str ( ) ,
724- "Name" ,
725- ] ;
726725 let mut acl_rows = Vec :: new ( ) ;
727726 let mut xattr_rows = Vec :: new ( ) ;
728727 let mut builder = TableBuilder :: new ( ) ;
729728 builder. set_empty ( String :: new ( ) ) ;
730729 if options. header {
731- builder. push_record ( header) ;
730+ let header = [
731+ Some ( "Encryption" ) ,
732+ Some ( "Compression" ) ,
733+ Some ( "Permissions" ) ,
734+ options. show_fflags . then_some ( "Fflags" ) ,
735+ Some ( "Raw Size" ) ,
736+ Some ( "Compressed Size" ) ,
737+ Some ( "User" ) ,
738+ Some ( "Group" ) ,
739+ Some ( options. time_field . as_str ( ) ) ,
740+ Some ( "Name" ) ,
741+ ] ;
742+ builder. push_record ( header. into_iter ( ) . flatten ( ) ) ;
732743 }
733744 for content in entries {
734745 let has_acl = !content. acl . is_empty ( ) ;
@@ -742,40 +753,44 @@ fn detail_list_entries_to(
742753 || "-" . into ( ) ,
743754 |it| it. group_display ( options. numeric_owner ) . to_string ( ) ,
744755 ) ;
745- builder. push_record ( [
746- content. encryption ,
747- content. compression ,
748- paint_permission ( & content. entry_type , permission_mode, has_xattr, has_acl) ,
749- content
750- . raw_size
751- . map_or_else ( || "-" . into ( ) , |size| size. to_string ( ) ) ,
752- content. compressed_size . to_string ( ) ,
753- user,
754- group,
755- match options. time_field {
756- TimeField :: Created => content. created ,
757- TimeField :: Modified => content. modified ,
758- TimeField :: Accessed => content. accessed ,
759- }
760- . map_or_else ( || "-" . into ( ) , |d| datetime ( options. time_format , d) ) ,
761- {
762- let name = match content. entry_type {
763- EntryType :: Directory ( path) if options. classify => format ! ( "{path}/" ) ,
764- EntryType :: SymbolicLink ( name, link_to) if options. classify => {
765- format ! ( "{name}@ -> {link_to}" )
756+ builder. push_record (
757+ [
758+ Some ( content. encryption ) ,
759+ Some ( content. compression ) ,
760+ Some ( paint_permission (
761+ & content. entry_type ,
762+ permission_mode,
763+ has_xattr,
764+ has_acl,
765+ ) ) ,
766+ options. show_fflags . then ( || {
767+ if content. fflags . is_empty ( ) {
768+ "-" . into ( )
769+ } else {
770+ content. fflags . join ( "," )
766771 }
767- EntryType :: File ( path) | EntryType :: Directory ( path) => path,
768- EntryType :: SymbolicLink ( path, link_to) | EntryType :: HardLink ( path, link_to) => {
769- format ! ( "{path} -> {link_to}" )
772+ } ) ,
773+ Some (
774+ content
775+ . raw_size
776+ . map_or_else ( || "-" . into ( ) , |size| size. to_string ( ) ) ,
777+ ) ,
778+ Some ( content. compressed_size . to_string ( ) ) ,
779+ Some ( user) ,
780+ Some ( group) ,
781+ Some (
782+ match options. time_field {
783+ TimeField :: Created => content. created ,
784+ TimeField :: Modified => content. modified ,
785+ TimeField :: Accessed => content. accessed ,
770786 }
771- } ;
772- if options. hide_control_chars {
773- hide_control_chars ( & name) . to_string ( )
774- } else {
775- name
776- }
777- } ,
778- ] ) ;
787+ . map_or_else ( || "-" . into ( ) , |d| datetime ( options. time_format , d) ) ,
788+ ) ,
789+ Some ( detailed_format_name ( content. entry_type , options) ) ,
790+ ]
791+ . into_iter ( )
792+ . flatten ( ) ,
793+ ) ;
779794 if options. show_acl {
780795 let acl = content. acl . into_iter ( ) . flat_map ( |( platform, ace) | {
781796 ace. into_iter ( ) . map ( move |it| chunk:: AceWithPlatform {
@@ -811,21 +826,30 @@ fn detail_list_entries_to(
811826 }
812827 }
813828 let mut table = builder. build ( ) ;
829+ // Determine size columns for right alignment
830+ let size_cols_start = if options. show_fflags { 4 } else { 3 } ;
831+ let size_cols_end = size_cols_start + 1 ;
814832 table
815833 . with ( TableStyle :: empty ( ) )
816- . with ( Colorization :: columns ( [
817- Color :: FG_MAGENTA ,
818- Color :: FG_BLUE ,
819- Color :: empty ( ) ,
820- Color :: FG_GREEN ,
821- Color :: FG_GREEN ,
822- Color :: FG_CYAN ,
823- Color :: FG_CYAN ,
824- Color :: FG_CYAN ,
825- Color :: FG_CYAN ,
826- Color :: empty ( ) ,
827- ] ) )
828- . with ( Modify :: new ( Segment :: new ( .., 3 ..=4 ) ) . with ( Alignment :: right ( ) ) ) ;
834+ . with ( Colorization :: columns (
835+ [
836+ Some ( Color :: FG_MAGENTA ) , // Encryption
837+ Some ( Color :: FG_BLUE ) , // Compression
838+ Some ( Color :: empty ( ) ) , // Permissions
839+ options. show_fflags . then_some ( Color :: FG_YELLOW ) , // Fflags
840+ Some ( Color :: FG_GREEN ) , // Raw Size
841+ Some ( Color :: FG_GREEN ) , // Compressed Size
842+ Some ( Color :: FG_CYAN ) , // User
843+ Some ( Color :: FG_CYAN ) , // Group
844+ Some ( Color :: FG_CYAN ) , // Time
845+ Some ( Color :: empty ( ) ) , // Name
846+ ]
847+ . into_iter ( )
848+ . flatten ( ) ,
849+ ) )
850+ . with (
851+ Modify :: new ( Segment :: new ( .., size_cols_start..=size_cols_end) ) . with ( Alignment :: right ( ) ) ,
852+ ) ;
829853 if options. header {
830854 table. with ( Colorization :: exact ( [ underline] , Rows :: first ( ) ) ) ;
831855 }
@@ -838,6 +862,24 @@ fn detail_list_entries_to(
838862 writeln ! ( out, "{table}" )
839863}
840864
865+ fn detailed_format_name ( entry : EntryType , options : & ListOptions ) -> String {
866+ let name = match entry {
867+ EntryType :: Directory ( path) if options. classify => format ! ( "{path}/" ) ,
868+ EntryType :: SymbolicLink ( name, link_to) if options. classify => {
869+ format ! ( "{name}@ -> {link_to}" )
870+ }
871+ EntryType :: File ( path) | EntryType :: Directory ( path) => path,
872+ EntryType :: SymbolicLink ( path, link_to) | EntryType :: HardLink ( path, link_to) => {
873+ format ! ( "{path} -> {link_to}" )
874+ }
875+ } ;
876+ if options. hide_control_chars {
877+ hide_control_chars ( & name) . to_string ( )
878+ } else {
879+ name
880+ }
881+ }
882+
841883const DURATION_SIX_MONTH : Duration = Duration :: from_secs ( 60 * 60 * 24 * 30 * 6 ) ;
842884
843885fn within_six_months ( now : SystemTime , x : SystemTime ) -> bool {
@@ -999,6 +1041,8 @@ struct FileInfo<'a> {
9991041 created : String ,
10001042 modified : String ,
10011043 accessed : String ,
1044+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
1045+ fflags : Option < & ' a [ String ] > ,
10021046 acl : Vec < AclEntry > ,
10031047 xattr : Vec < XAttr < ' a > > ,
10041048}
@@ -1015,7 +1059,12 @@ struct XAttr<'a> {
10151059 value : String ,
10161060}
10171061
1018- fn json_line_entries_to ( entries : Vec < TableRow > , mut out : impl Write ) -> anyhow:: Result < ( ) > {
1062+ fn json_line_entries_to (
1063+ entries : Vec < TableRow > ,
1064+ options : & ListOptions ,
1065+ mut out : impl Write ,
1066+ ) -> anyhow:: Result < ( ) > {
1067+ let show_fflags = options. show_fflags ;
10191068 let entries = entries
10201069 . par_iter ( )
10211070 . map ( |it| {
@@ -1051,6 +1100,11 @@ fn json_line_entries_to(entries: Vec<TableRow>, mut out: impl Write) -> anyhow::
10511100 accessed : it
10521101 . accessed
10531102 . map_or_else ( String :: new, |d| datetime ( TimeFormat :: Long , d) ) ,
1103+ fflags : if show_fflags {
1104+ Some ( it. fflags . as_slice ( ) )
1105+ } else {
1106+ None
1107+ } ,
10541108 acl : it
10551109 . acl
10561110 . iter ( )
@@ -1103,17 +1157,22 @@ fn delimited_entries_to(
11031157 options : & ListOptions ,
11041158 mut wtr : csv:: Writer < impl Write > ,
11051159) -> io:: Result < ( ) > {
1106- wtr. write_record ( [
1107- "filename" ,
1108- "permissions" ,
1109- "owner" ,
1110- "group" ,
1111- "raw_size" ,
1112- "compressed_size" ,
1113- "encryption" ,
1114- "compression" ,
1115- options. time_field . as_str ( ) ,
1116- ] ) ?;
1160+ wtr. write_record (
1161+ [
1162+ Some ( "filename" ) ,
1163+ Some ( "permissions" ) ,
1164+ Some ( "owner" ) ,
1165+ Some ( "group" ) ,
1166+ Some ( "raw_size" ) ,
1167+ Some ( "compressed_size" ) ,
1168+ Some ( "encryption" ) ,
1169+ Some ( "compression" ) ,
1170+ options. show_fflags . then_some ( "fflags" ) ,
1171+ Some ( options. time_field . as_str ( ) ) ,
1172+ ]
1173+ . into_iter ( )
1174+ . flatten ( ) ,
1175+ ) ?;
11171176
11181177 let rows = entries
11191178 . par_iter ( )
@@ -1133,21 +1192,24 @@ fn delimited_entries_to(
11331192 . map_or_else ( String :: new, |d| datetime ( TimeFormat :: Long , d) ) ;
11341193
11351194 [
1136- row. entry_type . name ( ) . to_string ( ) ,
1137- permission_string (
1195+ Some ( row. entry_type . name ( ) . to_string ( ) ) ,
1196+ Some ( permission_string (
11381197 & row. entry_type ,
11391198 permission_mode,
11401199 !row. xattrs . is_empty ( ) ,
11411200 !row. acl . is_empty ( ) ,
1142- ) ,
1143- owner,
1144- group,
1145- row. raw_size . unwrap_or ( 0 ) . to_string ( ) ,
1146- row. compressed_size . to_string ( ) ,
1147- row. encryption . clone ( ) ,
1148- row. compression . clone ( ) ,
1149- time,
1201+ ) ) ,
1202+ Some ( owner) ,
1203+ Some ( group) ,
1204+ Some ( row. raw_size . unwrap_or ( 0 ) . to_string ( ) ) ,
1205+ Some ( row. compressed_size . to_string ( ) ) ,
1206+ Some ( row. encryption . clone ( ) ) ,
1207+ Some ( row. compression . clone ( ) ) ,
1208+ options. show_fflags . then ( || row. fflags . join ( "," ) ) ,
1209+ Some ( time) ,
11501210 ]
1211+ . into_iter ( )
1212+ . flatten ( )
11511213 } )
11521214 . collect :: < Vec < _ > > ( ) ;
11531215
0 commit comments