diff --git a/flecmd/adif.go b/flecmd/adif.go index 367ef16..c629809 100644 --- a/flecmd/adif.go +++ b/flecmd/adif.go @@ -19,6 +19,7 @@ limitations under the License. import ( "FLEcli/fleprocess" "fmt" + "os" "github.com/spf13/cobra" ) @@ -49,13 +50,18 @@ var adifCmd = &cobra.Command{ return fmt.Errorf("Too many arguments.%s", "") } - fleprocess.ProcessAdifCommand( + err := fleprocess.ProcessAdifCommand( inputFilename, outputFilename, isInterpolateTime, isWWFFcli, isSOTAcli, isOverwrite) + if err != nil { + fmt.Println("\nUnable to generate ADIF file:") + fmt.Println(err) + os.Exit(1) + } return nil }, diff --git a/fleprocess/adif_process.go b/fleprocess/adif_process.go index c1b4f40..74f7b9b 100644 --- a/fleprocess/adif_process.go +++ b/fleprocess/adif_process.go @@ -18,43 +18,114 @@ limitations under the License. import ( "fmt" + "strings" ) -//ProcessAdifCommand FIXME -func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime, isWWFFcli, isSOTAcli, isOverwrite bool) { +//ProcessAdifCommand loads an FLE input to produce an adif file (eventually in WWFF format). It is called from the COBRA interface +func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime, isWWFFcli, isSOTAcli, isOverwrite bool) error { - verifiedOutputFilename, err := buildOutputFilename(outputFilename, inputFilename, isOverwrite, ".adi") + //Validate of build the output filenaem + var verifiedOutputFilename string + var err error - // if the output file could not be parsed correctly do noting - if err == nil { - loadedLogFile, isLoadedOK := LoadFile(inputFilename, isInterpolateTime) + if verifiedOutputFilename, err = buildOutputFilename(outputFilename, inputFilename, isOverwrite, ".adi"); err != nil { + return err + } - if isLoadedOK { - if len(loadedLogFile) == 0 { - fmt.Println("No useful data read. Aborting...") - return - } + //Load the input file + var loadedLogFile []LogLine + var isLoadedOK bool + + if loadedLogFile, isLoadedOK = LoadFile(inputFilename, isInterpolateTime); isLoadedOK == false { + return fmt.Errorf("There were input file parsing errors. Could not generate ADIF file") + } + + //Check if we have all the necessary data + if err := validateDataforAdif(loadedLogFile, isWWFFcli, isSOTAcli); err != nil { + return err + } + + //Write the output file with the checked data + OutputAdif(verifiedOutputFilename, loadedLogFile, isWWFFcli, isSOTAcli) + + //If we reached this point, everything was processed OK and the file generated + return nil +} + +//validateDataforAdif checks whether all the required data is present +//The details of the mandatory files can be found at http://wwff.co/rules-faq/confirming-and-sending-log/ +func validateDataforAdif(loadedLogFile []LogLine, isWWFFcli, isSOTAcli bool) error { + + //do we have QSOs at all? + if len(loadedLogFile) == 0 { + return fmt.Errorf("No QSO found") + } + + //MySOTA, MyWWFF and MyCall are header values. If missing on the first line, it will be missing at every line + if loadedLogFile[0].MyCall == "" { + return fmt.Errorf("Missing MyCall") + } + if isSOTAcli { + if loadedLogFile[0].MySOTA == "" { + return fmt.Errorf("Missing MY-SOTA reference") + } + } + if isWWFFcli { + if loadedLogFile[0].MyWWFF == "" { + return fmt.Errorf("Missing MY-WWFF reference") + } + if loadedLogFile[0].Operator == "" { + return fmt.Errorf("Missing Operator call sign") + } + } + + var errorsBuffer strings.Builder + //We accumulate the errors messages + for i := 0; i < len(loadedLogFile); i++ { - //TODO: There are more tests required here - //check if we have the necessary information for the type - if isWWFFcli { - if loadedLogFile[0].MyWWFF == "" { - fmt.Println("Missing MY-WWFF reference. Aborting...") - return - } - if loadedLogFile[0].Operator == "" { - fmt.Println("Missing Operator. Aborting...") - return - } + //Compute the error location for a meaning full error + var errorLocation string + if loadedLogFile[i].Time == "" { + errorLocation = fmt.Sprintf("for log entry #%d", i+1) + } else { + errorLocation = fmt.Sprintf("for log entry at %s (#%d)", loadedLogFile[i].Time, i+1) + } + + if loadedLogFile[i].Date == "" { + if errorsBuffer.String() != "" { + errorsBuffer.WriteString(fmt.Sprintf(", ")) } - if isSOTAcli { - if loadedLogFile[0].MySOTA == "" { - fmt.Println("Missing MY-SOTA reference. Aborting...") - return - } + errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation)) + } + if loadedLogFile[i].Band == "" { + if errorsBuffer.String() != "" { + errorsBuffer.WriteString(fmt.Sprintf(", ")) } - - OutputAdif(verifiedOutputFilename, loadedLogFile, isWWFFcli, isSOTAcli) + errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation)) + } + if loadedLogFile[i].Mode == "" { + if errorsBuffer.String() != "" { + errorsBuffer.WriteString(fmt.Sprintf(", ")) + } + errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation)) + } + if loadedLogFile[i].Call == "" { + if errorsBuffer.String() != "" { + errorsBuffer.WriteString(fmt.Sprintf(", ")) + } + errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation)) + } + if loadedLogFile[i].Time == "" { + if errorsBuffer.String() != "" { + errorsBuffer.WriteString(fmt.Sprintf(", ")) + } + errorsBuffer.WriteString(fmt.Sprintf("missing QSO time %s", errorLocation)) } } + if errorsBuffer.String() != "" { + return fmt.Errorf(errorsBuffer.String()) + } + + //If we reached here, all is ok + return nil } diff --git a/fleprocess/adif_process_test.go b/fleprocess/adif_process_test.go new file mode 100644 index 0000000..08df0c9 --- /dev/null +++ b/fleprocess/adif_process_test.go @@ -0,0 +1,149 @@ +package fleprocess + +import ( + "fmt" + "testing" +) + +func Test_validateDataforAdif(t *testing.T) { + type args struct { + loadedLogFile []LogLine + isWWFFcli bool + isSOTAcli bool + } + tests := []struct { + name string + args args + want error + }{ + { + "Happy Case (no sota or wwff)", + args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{ + {Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "time", Call: "call"}}, + }, + nil, + }, + { + "No data", + args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{}}, + fmt.Errorf("No QSO found"), + }, + { + "Missing Date", + args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{ + {Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:01", Call: "call"}, + {Date: "", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:02", Call: "call"}, + {Date: "", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", Call: "call"}}, + }, + fmt.Errorf("missing date for log entry at 12:02 (#2), missing date for log entry at 12:03 (#3)"), + }, + { + "Missing MyCall", + args{isWWFFcli: true, isSOTAcli: true, loadedLogFile: []LogLine{ + {Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:01", Call: "call"}, + {Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:02", Call: "call"}, + {Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", Call: "call"}}, + }, + fmt.Errorf("Missing MyCall"), + }, + { + "Missing MySota", + args{isWWFFcli: false, isSOTAcli: true, loadedLogFile: []LogLine{ + {Date: "date", MyCall: "myCall", MySOTA: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}}, + }, + fmt.Errorf("Missing MY-SOTA reference"), + }, + { + "Misc. missing data (Band, Time, Mode, Call)", + args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{ + {Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "", Time: "", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "", Band: "band", Time: "12:02", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", Call: ""}}, + }, + fmt.Errorf("missing band for log entry #1, missing QSO time for log entry #1, missing mode for log entry at 12:02 (#2), missing call for log entry at 12:03 (#3)"), + }, + { + "Missing MY-WWFF", + args{isWWFFcli: true, isSOTAcli: false, loadedLogFile: []LogLine{ + {Date: "date", MyCall: "myCall", MySOTA: "mySota", MyWWFF: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "mySota", MyWWFF: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MySOTA: "mySota", MyWWFF: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}}, + }, + fmt.Errorf("Missing MY-WWFF reference"), + }, + { + "Missing MY-WWFF", + args{isWWFFcli: true, isSOTAcli: false, loadedLogFile: []LogLine{ + {Date: "date", MyCall: "myCall", MyWWFF: "myWwff", Operator: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MyWWFF: "myWwff", Operator: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}, + {Date: "date", MyCall: "myCall", MyWWFF: "myWwff", Operator: "", Mode: "mode", Band: "band", Time: "time", Call: "call"}}, + }, + fmt.Errorf("Missing Operator call sign"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validateDataforAdif(tt.args.loadedLogFile, tt.args.isWWFFcli, tt.args.isSOTAcli) + + //Test the error message, if any + if got != nil && tt.want != nil { + if got.Error() != tt.want.Error() { + t.Errorf("validateDataforAdif() = %v, want %v", got, tt.want) + } + } else { + if !(got == nil && tt.want == nil) { + t.Errorf("validateDataforAdif() = %v, want %v", got, tt.want) + } + } + }) + } +} + +func TestProcessAdifCommand(t *testing.T) { + type args struct { + inputFilename string + outputFilename string + isInterpolateTime bool + isWWFFcli bool + isSOTAcli bool + isOverwrite bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + "Bad output filename (directory)", + args{inputFilename: "../test/data/fle-4-no-qso.txt", outputFilename: "../test/data", isInterpolateTime: false, isOverwrite: false}, + true, + }, + { + "input file parsing errors (missing band)", + args{inputFilename: "../test/data/fle-3-error.txt", outputFilename: "", isInterpolateTime: false, isOverwrite: false}, + true, + }, + { + "input file parsing errors (wrong call)", + args{inputFilename: "../test/data/fle-5-wrong-call.txt", outputFilename: "", isInterpolateTime: false, isOverwrite: false}, + true, + }, + { + "No QSO in loaded file", + args{inputFilename: "../test/data/fle-4-no-qso.txt", outputFilename: "", isInterpolateTime: false, isOverwrite: false}, + true, + }, + + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ProcessAdifCommand(tt.args.inputFilename, tt.args.outputFilename, tt.args.isInterpolateTime, tt.args.isWWFFcli, tt.args.isSOTAcli, tt.args.isOverwrite); (err != nil) != tt.wantErr { + t.Errorf("ProcessCsvCommand() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/fleprocess/csv_process.go b/fleprocess/csv_process.go index 15161c4..935ef48 100644 --- a/fleprocess/csv_process.go +++ b/fleprocess/csv_process.go @@ -24,13 +24,13 @@ import ( ) //ProcessCsvCommand loads an FLE input to produce a SOTA CSV -func ProcessCsvCommand(inputFilename, outputCsvFilename string, isInterpolateTime, isOverwriteCsv bool) error { +func ProcessCsvCommand(inputFilename, outputFilename string, isInterpolateTime, isOverwriteCsv bool) error { //Validate of build the output filenaem var verifiedOutputFilename string var err error - if verifiedOutputFilename, err = buildOutputFilename(outputCsvFilename, inputFilename, isOverwriteCsv, ".csv"); err != nil { + if verifiedOutputFilename, err = buildOutputFilename(outputFilename, inputFilename, isOverwriteCsv, ".csv"); err != nil { return err } @@ -50,11 +50,10 @@ func ProcessCsvCommand(inputFilename, outputCsvFilename string, isInterpolateTim outputCsv(verifiedOutputFilename, loadedLogFile) return nil - - } +//validateDataForSotaCsv checks whether all the requiered data is present in the supplied data func validateDataForSotaCsv(loadedLogFile []LogLine) error { if len(loadedLogFile) == 0 { return fmt.Errorf("No QSO found") diff --git a/fleprocess/csv_process_test.go b/fleprocess/csv_process_test.go index 48715d9..c6d992f 100644 --- a/fleprocess/csv_process_test.go +++ b/fleprocess/csv_process_test.go @@ -63,18 +63,54 @@ func Test_validateDataForSotaCsv(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := validateDataForSotaCsv(tt.args.loadedLogFile) - if tt.want == nil || got == nil { - if tt.want == nil && got != nil { - t.Errorf("validateDataForSotaCsv() = %v, want %v", got, nil) - } - if tt.want != nil && got == nil { - t.Errorf("validateDataForSotaCsv() = %v, want %v", nil, tt.want) + + //Test the error message, if any + if got != nil && tt.want != nil { + if got.Error() != tt.want.Error() { + t.Errorf("validateDataForSotaCsv() = %v, want %v", got, tt.want) } } else { - if got.Error() != tt.want.Error() { + if !(got == nil && tt.want == nil) { t.Errorf("validateDataForSotaCsv() = %v, want %v", got, tt.want) } } }) } } + +func TestProcessCsvCommand(t *testing.T) { + type args struct { + inputFilename string + outputCsvFilename string + isInterpolateTime bool + isOverwriteCsv bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + "Bad output filename (directory)", + args{inputFilename: "../test/data/fle-4-no-qso.txt", outputCsvFilename: "../test/data", isInterpolateTime: false, isOverwriteCsv: false}, + true, + }, + { + "input file parsing errors", + args{inputFilename: "../test/data/fle-3-error.txt", outputCsvFilename: "", isInterpolateTime: false, isOverwriteCsv: false}, + true, + }, + { + "No QSO in loaded file", + args{inputFilename: "../test/data/fle-4-no-qso.txt", outputCsvFilename: "", isInterpolateTime: false, isOverwriteCsv: false}, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ProcessCsvCommand(tt.args.inputFilename, tt.args.outputCsvFilename, tt.args.isInterpolateTime, tt.args.isOverwriteCsv); (err != nil) != tt.wantErr { + t.Errorf("ProcessCsvCommand() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/fleprocess/output_filename.go b/fleprocess/output_filename.go index a6bff67..4795f36 100644 --- a/fleprocess/output_filename.go +++ b/fleprocess/output_filename.go @@ -55,4 +55,3 @@ func buildOutputFilename(output string, input string, overwrite bool, newExtensi return "", fmt.Errorf("File already exists. Use --overwrite flag if necessary") } - diff --git a/fleprocess/output_filename_test.go b/fleprocess/output_filename_test.go index 4ccc856..e8ac8c7 100644 --- a/fleprocess/output_filename_test.go +++ b/fleprocess/output_filename_test.go @@ -103,15 +103,18 @@ func Test_buildOutputFilename(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotOutputFilename, gotErr := buildOutputFilename(tt.args.output, tt.args.input, tt.args.overwrite, tt.args.extension) + if gotOutputFilename != tt.wantOutputFilename { t.Errorf("buildOutputFilename() gotOutputFilename = %v, want %v", gotOutputFilename, tt.wantOutputFilename) } + + //test the error message, if any if gotErr != nil && tt.wantError != nil { if gotErr.Error() != tt.wantError.Error() { t.Errorf("buildOutputFilename() error = %v, want %v", gotErr, tt.wantError) } } else { - if!(gotErr == nil && tt.wantError == nil) { + if !(gotErr == nil && tt.wantError == nil) { t.Errorf("buildOutputFilename() error = %v, want %v", gotErr, tt.wantError) } } diff --git a/test/data/fle-5-wrong-call.txt b/test/data/fle-5-wrong-call.txt new file mode 100644 index 0000000..462c501 --- /dev/null +++ b/test/data/fle-5-wrong-call.txt @@ -0,0 +1,12 @@ +date 2020-07-19 +mycall ON6ZQ/P +mysota ON/ON-018 + +#wrong call +cw +30m +1150 + +55 ix1ihr +onyz4/f8dgf +56 ea2dt \ No newline at end of file