@@ -56,7 +56,14 @@ pub fn edit(
5656 let default_content = boat_data. to_csv_str ( include_instructions, include_activity_definitions) ;
5757 let edit_file_path = create_tmp_edit_file ( & default_content) ?;
5858
59- let editor = env:: var ( "EDITOR" ) . unwrap_or_else ( |_| "vim" . to_string ( ) ) ;
59+ let editor = args
60+ . editor
61+ . clone ( )
62+ . or_else ( || env:: var ( "EDITOR" ) . ok ( ) )
63+ . context ( "No editor specified and EDITOR is not set" ) ?;
64+
65+ resolve_editor ( & editor) ?;
66+ info ! ( "launching editor: {editor}" ) ;
6067 let status = Command :: new ( editor) . arg ( & edit_file_path) . status ( ) ?;
6168
6269 ensure ! (
@@ -137,6 +144,17 @@ struct EditLogDiff {
137144 ends_at_new : Option < Option < DateTime < Utc > > > ,
138145}
139146
147+ fn resolve_editor ( editor : & str ) -> Result < ( ) > {
148+ if editor. is_empty ( ) {
149+ bail ! (
150+ "Default text editor not found. Please refer to your shell documentation to set your EDITOR environment variable."
151+ ) ;
152+ }
153+
154+ which:: which ( editor) . with_context ( || format ! ( "EDITOR command not found: {editor}" ) ) ?;
155+ Ok ( ( ) )
156+ }
157+
140158fn pretty_print_edit_diffs ( diffs : & [ EditLogDiff ] ) {
141159 println ! ( "Detected changes:" ) ;
142160 for diff in diffs {
@@ -391,10 +409,13 @@ fn convert_modified_content_to_log_lines(content: &str) -> Result<Vec<DatabaseLo
391409}
392410
393411fn create_tmp_edit_file ( content : & str ) -> Result < PathBuf > {
394- info ! ( "creating temporary file for editing" ) ;
395412 let tmp_dir = env:: temp_dir ( ) ;
396413 let file_path = tmp_dir. join ( "boat_edit_logs_tmp.csv" ) ;
397414 fs:: write ( & file_path, content) ?;
415+ info ! (
416+ "created temporary file for editing: {}" ,
417+ file_path. display( )
418+ ) ;
398419 Ok ( file_path)
399420}
400421
@@ -478,4 +499,120 @@ mod tests {
478499 let csv = "1, 10, not-a-date, 2024-06-01 11:00" ;
479500 assert ! ( convert_modified_content_to_log_lines( csv) . is_err( ) ) ;
480501 }
502+
503+ // --- resolve_editor ---
504+
505+ #[ test]
506+ fn resolve_editor_empty_string_fails ( ) {
507+ assert ! ( resolve_editor( "" ) . is_err( ) ) ;
508+ }
509+
510+ #[ test]
511+ fn resolve_editor_nonexistent_command_fails ( ) {
512+ assert ! ( resolve_editor( "definitely-not-a-real-editor-xyz" ) . is_err( ) ) ;
513+ }
514+
515+ // --- try_generate_edit_diffs error paths ---
516+
517+ #[ test]
518+ fn try_generate_edit_diffs_length_mismatch_fails ( ) {
519+ let orig = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ] ;
520+ let edited = vec ! [
521+ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ,
522+ make_log( 2 , 1 , "2024-06-01 12:00" , Some ( "2024-06-01 13:00" ) ) ,
523+ ] ;
524+ assert ! ( try_generate_edit_diffs( & edited, & orig) . is_err( ) ) ;
525+ }
526+
527+ #[ test]
528+ fn try_generate_edit_diffs_multiple_open_ended_logs_fails ( ) {
529+ let orig = vec ! [
530+ make_log( 1 , 1 , "2024-06-01 10:00" , None ) ,
531+ make_log( 2 , 1 , "2024-06-01 12:00" , None ) ,
532+ ] ;
533+ assert ! ( try_generate_edit_diffs( & orig, & orig) . is_err( ) ) ;
534+ }
535+
536+ #[ test]
537+ fn try_generate_edit_diffs_unknown_log_id_fails ( ) {
538+ let orig = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ] ;
539+ let edited = vec ! [ make_log(
540+ 99 ,
541+ 1 ,
542+ "2024-06-01 10:00" ,
543+ Some ( "2024-06-01 11:00" ) ,
544+ ) ] ;
545+ assert ! ( try_generate_edit_diffs( & edited, & orig) . is_err( ) ) ;
546+ }
547+
548+ #[ test]
549+ fn try_generate_edit_diffs_activity_id_changed_fails ( ) {
550+ let orig = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ] ;
551+ let edited = vec ! [ make_log(
552+ 1 ,
553+ 999 ,
554+ "2024-06-01 10:00" ,
555+ Some ( "2024-06-01 11:00" ) ,
556+ ) ] ;
557+ assert ! ( try_generate_edit_diffs( & edited, & orig) . is_err( ) ) ;
558+ }
559+
560+ #[ test]
561+ fn try_generate_edit_diffs_starts_at_after_ends_at_fails ( ) {
562+ let orig = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ] ;
563+ let edited = vec ! [ make_log( 1 , 1 , "2024-06-01 12:00" , Some ( "2024-06-01 11:00" ) ) ] ;
564+ assert ! ( try_generate_edit_diffs( & edited, & orig) . is_err( ) ) ;
565+ }
566+
567+ // --- try_generate_edit_diffs happy paths ---
568+
569+ #[ test]
570+ fn try_generate_edit_diffs_no_change_returns_empty ( ) {
571+ let logs = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ] ;
572+ let diffs = try_generate_edit_diffs ( & logs, & logs) . unwrap ( ) ;
573+ assert ! ( diffs. is_empty( ) ) ;
574+ }
575+
576+ #[ test]
577+ fn try_generate_edit_diffs_detects_ends_at_change ( ) {
578+ let orig = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ] ;
579+ let edited = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 12:00" ) ) ] ;
580+ let diffs = try_generate_edit_diffs ( & edited, & orig) . unwrap ( ) ;
581+ assert_eq ! ( diffs. len( ) , 1 ) ;
582+ assert ! ( diffs[ 0 ] . ends_at_new. is_some( ) ) ;
583+ assert ! ( diffs[ 0 ] . starts_at_new. is_none( ) ) ;
584+ }
585+
586+ #[ test]
587+ fn try_generate_edit_diffs_detects_log_closed_to_open ( ) {
588+ let orig = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , Some ( "2024-06-01 11:00" ) ) ] ;
589+ let edited = vec ! [ make_log( 1 , 1 , "2024-06-01 10:00" , None ) ] ;
590+ let diffs = try_generate_edit_diffs ( & edited, & orig) . unwrap ( ) ;
591+ assert_eq ! ( diffs. len( ) , 1 ) ;
592+ // ends_at_new is Some(None) — the log was re-opened
593+ assert_eq ! ( diffs[ 0 ] . ends_at_new, Some ( None ) ) ;
594+ }
595+
596+ // --- date_time_opt_loose_eq ---
597+
598+ #[ test]
599+ fn date_time_opt_loose_eq_both_none_are_equal ( ) {
600+ let none: Option < DateTime < Utc > > = None ;
601+ assert ! ( date_time_opt_loose_eq( & none, & none) . unwrap( ) ) ;
602+ }
603+
604+ #[ test]
605+ fn date_time_opt_loose_eq_some_and_none_are_not_equal ( ) {
606+ let some = Some ( Utc . with_ymd_and_hms ( 2024 , 6 , 1 , 10 , 0 , 0 ) . unwrap ( ) ) ;
607+ let none = None ;
608+ assert ! ( !date_time_opt_loose_eq( & some, & none) . unwrap( ) ) ;
609+ assert ! ( !date_time_opt_loose_eq( & none, & some) . unwrap( ) ) ;
610+ }
611+
612+ #[ test]
613+ fn date_time_opt_loose_eq_different_minutes_are_not_equal ( ) {
614+ let dt1 = Some ( Utc . with_ymd_and_hms ( 2024 , 6 , 1 , 10 , 0 , 0 ) . unwrap ( ) ) ;
615+ let dt2 = Some ( Utc . with_ymd_and_hms ( 2024 , 6 , 1 , 10 , 1 , 0 ) . unwrap ( ) ) ;
616+ assert ! ( !date_time_opt_loose_eq( & dt1, & dt2) . unwrap( ) ) ;
617+ }
481618}
0 commit comments