@@ -99,7 +99,7 @@ const CDWORK_W: usize = 7;
9999fn compact_table_width ( mode : WeekdayMode ) -> usize {
100100 let dw = date_col_width ( mode) ;
101101 // date + 3 + pos + 3 + triple + 3 + tgt + 3 + dwork
102- dw + 3 + CPOS_W + 3 + TRIPLE_W + 3 + CTGT_W + 3 + CDWORK_W + 3
102+ dw + 3 + CPOS_W + 3 + TRIPLE_W + 3 + CTGT_W + 3 + CDWORK_W + 7
103103}
104104
105105fn format_date_with_weekday ( date : & NaiveDate , mode : WeekdayMode ) -> String {
@@ -112,6 +112,36 @@ fn format_date_with_weekday(date: &NaiveDate, mode: WeekdayMode) -> String {
112112 }
113113}
114114
115+ fn get_meta_string ( events : & [ Event ] , max_chars : usize ) -> String {
116+ if max_chars == 0 {
117+ return String :: new ( ) ;
118+ }
119+
120+ let joined = events
121+ . iter ( )
122+ . filter_map ( |e| e. meta . as_deref ( ) )
123+ . filter ( |s| !s. trim ( ) . is_empty ( ) )
124+ . collect :: < Vec < _ > > ( )
125+ . join ( ", " ) ;
126+
127+ let count = joined. chars ( ) . count ( ) ;
128+ if count <= max_chars {
129+ return joined;
130+ }
131+
132+ if max_chars == 1 {
133+ return "…" . to_string ( ) ;
134+ }
135+
136+ let mut out: String = joined. chars ( ) . take ( max_chars - 1 ) . collect ( ) ;
137+ out. push ( '…' ) ;
138+ out
139+ }
140+
141+ fn remaining_width ( total_width : usize , plain_prefix : & str ) -> usize {
142+ total_width. saturating_sub ( plain_prefix. len ( ) )
143+ }
144+
115145//
116146// ───────────────────────────────────────────────────────────────────────────────
117147// Public entry
@@ -529,19 +559,42 @@ fn print_daily_row(
529559 }
530560 }
531561
532- println ! (
533- " {:<dw$} | {}{}\x1b [0m | {:^5} | {:^5} | {:^5} | {:^5} | {}{:>7}\x1b [0m" ,
534- date_str,
535- pos_color,
536- pos_fmt,
537- first_in_str,
538- lunch_c,
539- end_c,
540- expected_exit_str,
541- surplus_color,
542- surplus_display,
543- dw = dw
544- ) ;
562+ if day_position == Location :: NationalHoliday {
563+ let twidth = daily_table_width ( wd_mode) ;
564+
565+ // prefisso “plain” (senza colori) uguale a ciò che stampi prima del meta
566+ let plain_prefix = format ! ( " {:<dw$} | {:<16} | " , date_str, pos_label, dw = dw) ;
567+ let meta_w = remaining_width ( twidth, & plain_prefix) ;
568+
569+ let meta = get_meta_string ( events, meta_w) ;
570+
571+ println ! (
572+ " {:<dw$} | {}{:<16}{}\x1b [0m | {}{:<meta_w$}{}" ,
573+ date_str,
574+ pos_color,
575+ pos_label,
576+ colors:: RESET ,
577+ pos_color,
578+ meta,
579+ colors:: RESET ,
580+ dw = dw,
581+ meta_w = meta_w,
582+ ) ;
583+ } else {
584+ println ! (
585+ " {:<dw$} | {}{}\x1b [0m | {:^5} | {:^5} | {:^5} | {:^5} | {}{:>7}\x1b [0m" ,
586+ date_str,
587+ pos_color,
588+ pos_fmt,
589+ first_in_str,
590+ lunch_c,
591+ end_c,
592+ expected_exit_str,
593+ surplus_color,
594+ surplus_display,
595+ dw = dw
596+ ) ;
597+ }
545598
546599 surplus_opt
547600}
@@ -616,7 +669,7 @@ fn print_compact_header(wd_mode: WeekdayMode) {
616669 let twidth = compact_table_width ( wd_mode) ;
617670
618671 println ! (
619- "{:^dw$} | {:^12 } | {:^21} | {:^5} | {:^7}" ,
672+ "{:^dw$} | {:^16 } | {:^21} | {:^5} | {:^7}" ,
620673 "DATE" ,
621674 "POSITION" ,
622675 "IN / LNCH / OUT" ,
@@ -652,9 +705,9 @@ fn print_daily_row_compact(
652705 let pos_label = day_position. label ( ) ;
653706 let pos_color = day_position. color ( ) ;
654707
655- if day_position == Location :: Holiday || day_position == Location :: NationalHoliday {
708+ if day_position == Location :: Holiday {
656709 println ! (
657- "{:<dw$} | {}{:<12 }{}\x1b [0m | {:<21} | {:^5} | {}Δ -{}\x1b [0m" ,
710+ "{:<dw$} | {}{:<16 }{}\x1b [0m | {:<21} | {:^5} | {}Δ -{}\x1b [0m" ,
658711 date_str,
659712 pos_color,
660713 pos_label,
@@ -666,6 +719,27 @@ fn print_daily_row_compact(
666719 dw = dw
667720 ) ;
668721 return Some ( 0 ) ;
722+ } else if day_position == Location :: NationalHoliday {
723+ let twidth = compact_table_width ( wd_mode) ;
724+
725+ let plain_prefix = format ! ( "{:<dw$} | {:<16} | " , date_str, pos_label, dw = dw) ;
726+ let meta_w = remaining_width ( twidth, & plain_prefix) ;
727+
728+ let meta = get_meta_string ( events, meta_w) ;
729+
730+ println ! (
731+ "{:<dw$} | {}{:<16}{}\x1b [0m | {}{:<meta_w$}{}" ,
732+ date_str,
733+ pos_color,
734+ pos_label,
735+ colors:: RESET ,
736+ pos_color,
737+ meta,
738+ colors:: RESET ,
739+ dw = dw,
740+ meta_w = meta_w
741+ ) ;
742+ return Some ( 0 ) ;
669743 }
670744
671745 let first_in = timeline. pairs [ 0 ] . in_event . timestamp ( ) ;
@@ -718,7 +792,7 @@ fn print_daily_row_compact(
718792 let times_string = format ! ( "{} / {} / {}" , first_in_str, lunch_str, end_str) ;
719793 let delta_value = format ! ( "Δ {}" , delta_str) ;
720794 println ! (
721- "{:<dw$} | {}{:<12 }{}\x1b [0m | {:<21} | {:^5} | {}{}{}\x1b [0m" ,
795+ "{:<dw$} | {}{:<16 }{}\x1b [0m | {:<21} | {:^5} | {}{}{}\x1b [0m" ,
722796 date_str,
723797 pos_color,
724798 pos_label,
@@ -733,3 +807,67 @@ fn print_daily_row_compact(
733807
734808 surplus_opt
735809}
810+
811+ #[ cfg( test) ]
812+ mod tests {
813+ use super :: * ;
814+
815+ // Helper per creare Event con meta valorizzato.
816+ // Variante A: se Event implementa Default.
817+ fn ev ( meta : Option < & str > ) -> Event {
818+ Event :: test_with_meta ( meta)
819+ }
820+
821+ // Se Event NON implementa Default, commenta la funzione sopra e crea qui un costruttore
822+ // coerente col tuo modello (es. Event::new(...) o struct literal con tutti i campi richiesti).
823+
824+ #[ test]
825+ fn meta_string_returns_empty_when_max_is_zero ( ) {
826+ let events = vec ! [ ev( Some ( "Epiphany" ) ) ] ;
827+ assert_eq ! ( get_meta_string( & events, 0 ) , "" ) ;
828+ }
829+
830+ #[ test]
831+ fn meta_string_filters_empty_and_whitespace ( ) {
832+ let events = vec ! [ ev( Some ( "" ) ) , ev( Some ( " " ) ) , ev( Some ( "Epiphany" ) ) ] ;
833+ assert_eq ! ( get_meta_string( & events, 100 ) , "Epiphany" ) ;
834+ }
835+
836+ #[ test]
837+ fn meta_string_joins_multiple_meta_with_comma_space ( ) {
838+ let events = vec ! [ ev( Some ( "Epiphany" ) ) , ev( Some ( "Republic Day" ) ) ] ;
839+ assert_eq ! ( get_meta_string( & events, 100 ) , "Epiphany, Republic Day" ) ;
840+ }
841+
842+ #[ test]
843+ fn meta_string_truncates_unicode_safely_by_chars ( ) {
844+ let events = vec ! [ ev( Some ( "caffè 漢字" ) ) , ev( Some ( "fine" ) ) ] ;
845+
846+ let full = get_meta_string ( & events, 1_000 ) ;
847+ assert_eq ! ( full, "caffè 漢字, fine" ) ;
848+
849+ let n = 7 ;
850+
851+ // Atteso secondo policy ellissi: max_chars include il carattere '…'
852+ let expected = if n == 0 {
853+ String :: new ( )
854+ } else if full. chars ( ) . count ( ) <= n {
855+ full. clone ( )
856+ } else if n == 1 {
857+ "…" . to_string ( )
858+ } else {
859+ let mut s: String = full. chars ( ) . take ( n - 1 ) . collect ( ) ;
860+ s. push ( '…' ) ;
861+ s
862+ } ;
863+
864+ let got = get_meta_string ( & events, n) ;
865+ assert_eq ! ( got, expected) ;
866+ }
867+
868+ #[ test]
869+ fn meta_string_does_not_truncate_when_within_limit ( ) {
870+ let events = vec ! [ ev( Some ( "Epiphany" ) ) ] ;
871+ assert_eq ! ( get_meta_string( & events, 10 ) , "Epiphany" ) ;
872+ }
873+ }
0 commit comments