mirror of
https://github.com/on4kjm/FLEcli.git
synced 2025-01-18 13:01:09 +01:00
Refactor ADIF processing and improve tests
This commit is contained in:
parent
fd7902bdfc
commit
e31e692c76
8 changed files with 319 additions and 44 deletions
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
||||
//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
|
||||
}
|
||||
}
|
||||
if isSOTAcli {
|
||||
if loadedLogFile[0].MySOTA == "" {
|
||||
fmt.Println("Missing MY-SOTA reference. Aborting...")
|
||||
return
|
||||
}
|
||||
}
|
||||
if loadedLogFile, isLoadedOK = LoadFile(inputFilename, isInterpolateTime); isLoadedOK == false {
|
||||
return fmt.Errorf("There were input file parsing errors. Could not generate ADIF file")
|
||||
}
|
||||
|
||||
OutputAdif(verifiedOutputFilename, loadedLogFile, isWWFFcli, isSOTAcli)
|
||||
//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++ {
|
||||
|
||||
//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(", "))
|
||||
}
|
||||
errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation))
|
||||
}
|
||||
if loadedLogFile[i].Band == "" {
|
||||
if errorsBuffer.String() != "" {
|
||||
errorsBuffer.WriteString(fmt.Sprintf(", "))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
149
fleprocess/adif_process_test.go
Normal file
149
fleprocess/adif_process_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,4 +55,3 @@ func buildOutputFilename(output string, input string, overwrite bool, newExtensi
|
|||
|
||||
return "", fmt.Errorf("File already exists. Use --overwrite flag if necessary")
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
12
test/data/fle-5-wrong-call.txt
Normal file
12
test/data/fle-5-wrong-call.txt
Normal file
|
@ -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
|
Loading…
Reference in a new issue