From 61c42d1c758d2f08168ba3038e0252dd5ae01f6d Mon Sep 17 00:00:00 2001 From: Jean-Marc MEESSEN Date: Sat, 4 Dec 2021 22:36:53 +0100 Subject: [PATCH] Pota support (#73) * Add POTA processing * add end to end POTA test * allow tabs to be a valid separator in the header section --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- .goreleaser.yml | 24 ++-- Makefile | 2 +- flecmd/adif.go | 22 ++-- fleprocess/adif_process.go | 61 +++++---- fleprocess/adif_process_test.go | 191 +++++++++++++++++++---------- fleprocess/adif_write.go | 18 ++- fleprocess/adif_write_test.go | 66 ++++++++-- fleprocess/csv_process.go | 22 ++-- fleprocess/csv_process_test.go | 2 +- fleprocess/csv_write.go | 6 +- fleprocess/displayLog.go | 5 + fleprocess/displayLog_test.go | 6 +- fleprocess/inferTime.go | 20 +-- fleprocess/inferTime_test.go | 10 +- fleprocess/load_file.go | 50 ++++++-- fleprocess/load_file_test.go | 5 +- fleprocess/output_filename.go | 6 +- fleprocess/output_filename_test.go | 6 +- fleprocess/parse_line.go | 48 +++++--- fleprocess/parse_line_test.go | 10 ++ fleprocess/validate.go | 14 +++ fleprocess/validate_test.go | 59 +++++++++ fleprocess/write_file_test.go | 2 +- go.mod | 3 +- go.sum | 19 +++ test/bats-scripts/test.bats | 6 + test/data/fullFeatureHeader.txt | 1 + test/data/sample_pota.txt | 22 ++++ test/output/POTA/sample_pota.adif | 12 ++ 31 files changed, 529 insertions(+), 193 deletions(-) create mode 100644 test/data/sample_pota.txt create mode 100644 test/output/POTA/sample_pota.adif diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56fd4ca..4bf11cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: '^1.15.2' + go-version: '^1.17.2' - run: go mod download - name: Validates GO releaser config uses: goreleaser/goreleaser-action@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1bd031..17d013c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: run: git fetch --prune --unshallow - uses: actions/setup-go@v2 with: - go-version: '^1.15.2' + go-version: '^1.17.2' - name: Release via goreleaser uses: goreleaser/goreleaser-action@master with: diff --git a/.goreleaser.yml b/.goreleaser.yml index 0385b45..16f64fe 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,22 +1,26 @@ # This is an example goreleaser.yaml file with some sane defaults. # Make sure to check the documentation at http://goreleaser.com builds: -- goos: - - linux - - windows - - darwin +- binary: FLEcli + + goos: + - linux + - windows + - darwin goarch: - - 386 - - amd64 - - arm - - arm64 + - '386' + - amd64 + - arm + - arm64 goarm: - - 6 + - '6' + ignore: - goos: darwin goarch: 386 - goos: windows goarch: amd64 + ldflags: - -s -w -X FLEcli/flecmd.version={{.Version}} -X FLEcli/flecmd.commit={{.Commit}} -X FLEcli/flecmd.date={{.Date}} -X=FLEcli/flecmd.builtBy=goReleaser env: @@ -26,8 +30,6 @@ dockers: - goos: linux goarch: amd64 goarm: '' - binaries: - - FLEcli image_templates: - "on4kjm/flecli:latest" - "on4kjm/flecli:{{ .Tag }}" diff --git a/Makefile b/Makefile index 6b9ec52..efb40e2 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ dep: ## Get the dependencies @go mod download lint: ## Lint Golang files - @go get -u golang.org/x/lint/golint + @go install golang.org/x/lint/golint @golint -set_exit_status ./... vet: ## Run go vet diff --git a/flecmd/adif.go b/flecmd/adif.go index c629809..f295868 100644 --- a/flecmd/adif.go +++ b/flecmd/adif.go @@ -27,6 +27,7 @@ import ( var outputFilename string var isWWFFcli bool var isSOTAcli bool +var isPOTAcli bool var isOverwrite bool // adifCmd is executed when choosing the adif option (load and generate adif file) @@ -37,10 +38,11 @@ var adifCmd = &cobra.Command{ // and usage of using your command. For example: RunE: func(cmd *cobra.Command, args []string) error { + //if args is empty, throw an error if len(args) == 0 { //TODO: fix this ugly statement (because I am lazy) - return fmt.Errorf("Missing input file %s", "") + return fmt.Errorf("missing input file %s", "") } inputFilename = args[0] if len(args) == 2 { @@ -50,13 +52,16 @@ var adifCmd = &cobra.Command{ return fmt.Errorf("Too many arguments.%s", "") } - err := fleprocess.ProcessAdifCommand( - inputFilename, - outputFilename, - isInterpolateTime, - isWWFFcli, - isSOTAcli, - isOverwrite) + var adifParam = new(fleprocess.AdifParams) + adifParam.InputFilename = inputFilename + adifParam.OutputFilename = outputFilename + adifParam.IsInterpolateTime = isInterpolateTime + adifParam.IsSOTA = isSOTAcli + adifParam.IsPOTA = isPOTAcli + adifParam.IsWWFF = isWWFFcli + adifParam.IsOverwrite = isOverwrite + + err := fleprocess.ProcessAdifCommand(*adifParam) if err != nil { fmt.Println("\nUnable to generate ADIF file:") fmt.Println(err) @@ -73,5 +78,6 @@ func init() { adifCmd.PersistentFlags().BoolVarP(&isInterpolateTime, "interpolate", "i", false, "Interpolates the missing time entries.") adifCmd.PersistentFlags().BoolVarP(&isWWFFcli, "wwff", "w", false, "Generates a WWFF ready ADIF file.") adifCmd.PersistentFlags().BoolVarP(&isSOTAcli, "sota", "s", false, "Generates a SOTA ready ADIF file.") + adifCmd.PersistentFlags().BoolVarP(&isPOTAcli, "pota", "p", false, "Generates a POTA ready ADIF file.") adifCmd.PersistentFlags().BoolVarP(&isOverwrite, "overwrite", "o", false, "Overwrites the output file if it exisits") } diff --git a/fleprocess/adif_process.go b/fleprocess/adif_process.go index 74f7b9b..55db619 100644 --- a/fleprocess/adif_process.go +++ b/fleprocess/adif_process.go @@ -21,14 +21,26 @@ import ( "strings" ) +//AdifParams is holding all the parameters required to generate an ADIF file +type AdifParams struct { + InputFilename string + OutputFilename string + IsInterpolateTime bool + IsWWFF bool + IsSOTA bool + IsPOTA bool + 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 { +//inputFilename, outputFilename string, isInterpolateTime, isWWFFcli, IsSOTA, isPOTAcli, isOverwrite bool +func ProcessAdifCommand(adifParams AdifParams) error { //Validate of build the output filenaem var verifiedOutputFilename string var err error - if verifiedOutputFilename, err = buildOutputFilename(outputFilename, inputFilename, isOverwrite, ".adi"); err != nil { + if verifiedOutputFilename, err = buildOutputFilename(adifParams.OutputFilename, adifParams.InputFilename, adifParams.IsOverwrite, ".adi"); err != nil { return err } @@ -36,17 +48,17 @@ func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime, 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") + if loadedLogFile, isLoadedOK = LoadFile(adifParams.InputFilename, adifParams.IsInterpolateTime); !isLoadedOK { + 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 { + if err := validateDataforAdif(loadedLogFile, adifParams); err != nil { return err } //Write the output file with the checked data - OutputAdif(verifiedOutputFilename, loadedLogFile, isWWFFcli, isSOTAcli) + OutputAdif(verifiedOutputFilename, loadedLogFile, adifParams) //If we reached this point, everything was processed OK and the file generated return nil @@ -54,31 +66,38 @@ func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime, //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 { +func validateDataforAdif(loadedLogFile []LogLine, adifParams AdifParams) error { //do we have QSOs at all? if len(loadedLogFile) == 0 { - return fmt.Errorf("No QSO found") + 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 + //MySOTA, MyWWFF, MyPOTA 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") + return fmt.Errorf("missing MyCall") } - if isSOTAcli { + if adifParams.IsSOTA { if loadedLogFile[0].MySOTA == "" { - return fmt.Errorf("Missing MY-SOTA reference") + return fmt.Errorf("missing MY-SOTA reference") } } - if isWWFFcli { + if adifParams.IsWWFF { if loadedLogFile[0].MyWWFF == "" { - return fmt.Errorf("Missing MY-WWFF reference") + return fmt.Errorf("missing MY-WWFF reference") } if loadedLogFile[0].Operator == "" { - return fmt.Errorf("Missing Operator call sign") + return fmt.Errorf("missing Operator call sign") + } + } + if adifParams.IsPOTA { + if loadedLogFile[0].MyPOTA == "" { + return fmt.Errorf("missing MY-POTA 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++ { @@ -93,31 +112,31 @@ func validateDataforAdif(loadedLogFile []LogLine, isWWFFcli, isSOTAcli bool) err if loadedLogFile[i].Date == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation)) } if loadedLogFile[i].Band == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation)) } if loadedLogFile[i].Mode == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation)) } if loadedLogFile[i].Call == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation)) } if loadedLogFile[i].Time == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing QSO time %s", errorLocation)) } diff --git a/fleprocess/adif_process_test.go b/fleprocess/adif_process_test.go index 0b91c94..7d4609d 100644 --- a/fleprocess/adif_process_test.go +++ b/fleprocess/adif_process_test.go @@ -1,31 +1,14 @@ package fleprocess -/* -Copyright © 2020 Jean-Marc Meessen, ON4KJM - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - import ( "fmt" "testing" ) -func Test_validateDataforAdif(t *testing.T) { +func Test_validateDataforAdif2(t *testing.T) { type args struct { loadedLogFile []LogLine - isWWFFcli bool - isSOTAcli bool + adifParams AdifParams } tests := []struct { name string @@ -33,77 +16,125 @@ func Test_validateDataforAdif(t *testing.T) { 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"}}, + "Happy Case (no sota, pota or wwff)", + args{ + adifParams: AdifParams{IsWWFF: false, IsSOTA: false, IsPOTA: 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"), + args{ + adifParams: AdifParams{IsWWFF: false, IsSOTA: false, IsPOTA: 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"}}, + args{ + adifParams: AdifParams{IsWWFF: false, IsSOTA: false, IsPOTA: 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"}}, + args{ + adifParams: AdifParams{IsWWFF: true, IsSOTA: true, IsPOTA: 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"), + fmt.Errorf("missing MyCall"), }, + { + "Missing MyCall (POTA)", + args{ + adifParams: AdifParams{IsWWFF: false, IsSOTA: false, IsPOTA: 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"}}, + args{ + adifParams: AdifParams{IsWWFF: false, IsSOTA: true, IsPOTA: false}, + 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"), + }, + { + "Missing MyPota", + args{ + adifParams: AdifParams{IsWWFF: false, IsSOTA: false, IsPOTA: 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"), + fmt.Errorf("missing MY-POTA 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: ""}}, + args{ + adifParams: AdifParams{IsWWFF: false, IsSOTA: false, IsPOTA: 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"}}, + args{ + adifParams: AdifParams{IsWWFF: true, IsSOTA: false, IsPOTA: 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"), + 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"}}, + "Missing Operator with isWWFF", + args{ + adifParams: AdifParams{IsWWFF: true, IsSOTA: false, IsPOTA: 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"), + 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) + got := validateDataforAdif(tt.args.loadedLogFile, tt.args.adifParams) //Test the error message, if any if got != nil && tt.want != nil { @@ -119,14 +150,10 @@ func Test_validateDataforAdif(t *testing.T) { } } + func TestProcessAdifCommand(t *testing.T) { type args struct { - inputFilename string - outputFilename string - isInterpolateTime bool - isWWFFcli bool - isSOTAcli bool - isOverwrite bool + adifParams AdifParams } tests := []struct { name string @@ -135,29 +162,57 @@ func TestProcessAdifCommand(t *testing.T) { }{ { "Bad output filename (directory)", - args{inputFilename: "../test/data/fle-4-no-qso.txt", outputFilename: "../test/data", isInterpolateTime: false, isOverwrite: false}, + args{ + adifParams: AdifParams{ + 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}, + args{ + adifParams: AdifParams{ + 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}, + args{ + adifParams: AdifParams{ + 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}, + args{ + adifParams: AdifParams{ + 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) + if err := ProcessAdifCommand(tt.args.adifParams); (err != nil) != tt.wantErr { + t.Errorf("ProcessAdifCommand() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/fleprocess/adif_write.go b/fleprocess/adif_write.go index 9aa9901..517318f 100644 --- a/fleprocess/adif_write.go +++ b/fleprocess/adif_write.go @@ -23,17 +23,17 @@ import ( ) // OutputAdif generates and writes data in ADIF format -func OutputAdif(outputFile string, fullLog []LogLine, isWWFF bool, isSOTA bool) { +func OutputAdif(outputFile string, fullLog []LogLine, adifParams AdifParams) { //convert the log data to an in-memory ADIF file - adifData := buildAdif(fullLog, isWWFF, isSOTA) + adifData := buildAdif(fullLog, adifParams) //write to a file writeFile(outputFile, adifData) } // buildAdif creates the adif file in memory ready to be printed -func buildAdif(fullLog []LogLine, isWWFF bool, isSOTA bool) (adifList []string) { +func buildAdif(fullLog []LogLine, adifParams AdifParams) (adifList []string) { //Print the fixed header adifList = append(adifList, "ADIF Export for Fast Log Entry by DF3CB") adifList = append(adifList, "FLE") @@ -65,7 +65,7 @@ func buildAdif(fullLog []LogLine, isWWFF bool, isSOTA bool) (adifList []string) if logLine.QSLmsg != "" { adifLine.WriteString(adifElement("QSLMSG", logLine.QSLmsg)) } - if isWWFF { + if adifParams.IsWWFF { adifLine.WriteString(adifElement("MY_SIG", "WWFF")) adifLine.WriteString(adifElement("MY_SIG_INFO", logLine.MyWWFF)) if logLine.WWFF != "" { @@ -73,7 +73,15 @@ func buildAdif(fullLog []LogLine, isWWFF bool, isSOTA bool) (adifList []string) adifLine.WriteString(adifElement("SIG_INFO", logLine.WWFF)) } } - if isSOTA { + if adifParams.IsPOTA { + adifLine.WriteString(adifElement("MY_SIG", "POTA")) + adifLine.WriteString(adifElement("MY_SIG_INFO", logLine.MyPOTA)) + if logLine.POTA != "" { + adifLine.WriteString(adifElement("SIG", "POTA")) + adifLine.WriteString(adifElement("SIG_INFO", logLine.POTA)) + } + } + if adifParams.IsSOTA { adifLine.WriteString(adifElement("MY_SOTA_REF", logLine.MySOTA)) if logLine.SOTA != "" { adifLine.WriteString(adifElement("SOTA_REF", logLine.SOTA)) diff --git a/fleprocess/adif_write_test.go b/fleprocess/adif_write_test.go index 04c4041..193e721 100644 --- a/fleprocess/adif_write_test.go +++ b/fleprocess/adif_write_test.go @@ -94,10 +94,38 @@ func Test_buildAdif(t *testing.T) { "ON4KJM/P ON4LY 20200524 1312 20m CW 559 599 WWFF ONFF-0259 WWFF DLFF-0001 ON4KJM JO40eu ", } + sampleFilledLogPOTA := []LogLine{ + {MyCall: "ON4KJM/P", Call: "S57LC", Date: "2020-05-24", Time: "1310", Band: "20m", Frequency: "14.045", Mode: "CW", RSTsent: "599", RSTrcvd: "599", MyPOTA: "ON-00259", Operator: "ON4KJM", Nickname: "ON-00259-1"}, + {MyCall: "ON4KJM/P", Call: "ON4LY", Date: "2020-05-24", Time: "1312", Band: "20m", Mode: "CW", RSTsent: "559", RSTrcvd: "599", MyPOTA: "ON-00259", Operator: "ON4KJM"}, + } + + expectedOutputPOTA := []string{ + "ADIF Export for Fast Log Entry by DF3CB", + "FLE", + "3.1.0", + "", + "ON4KJM/P S57LC 20200524 1310 20m CW 14.045 599 599 POTA ON-00259 ON4KJM ON-00259-1 ", + "ON4KJM/P ON4LY 20200524 1312 20m CW 559 599 POTA ON-00259 ON4KJM ", + } + + + sampleFilledLogPOTA2 := []LogLine{ + {MyCall: "ON4KJM/P", Call: "S57LC", Date: "2020-05-24", MyGrid: "JO40eu", Time: "1310", Band: "20m", Frequency: "14.045", Mode: "CW", RSTsent: "599", RSTrcvd: "599", GridLoc: "JO50", MyPOTA: "ON-00259", Operator: "ON4KJM", Nickname: "ON-00259-1"}, + {MyCall: "ON4KJM/P", Call: "ON4LY", Date: "2020-05-24", MyGrid: "JO40eu", Time: "1312", Band: "20m", Mode: "CW", RSTsent: "559", RSTrcvd: "599", MyPOTA: "ON-00259", Operator: "ON4KJM", POTA: "DL-00001"}, + } + + expectedOutputPOTA2 := []string{ + "ADIF Export for Fast Log Entry by DF3CB", + "FLE", + "3.1.0", + "", + "ON4KJM/P S57LC 20200524 1310 20m CW 14.045 599 599 JO50 POTA ON-00259 ON4KJM JO40eu ON-00259-1 ", + "ON4KJM/P ON4LY 20200524 1312 20m CW 559 599 POTA ON-00259 POTA DL-00001 ON4KJM JO40eu ", + } + type args struct { fullLog []LogLine - isWWFF bool - isSOTA bool + adifParams AdifParams } tests := []struct { name string @@ -106,23 +134,47 @@ func Test_buildAdif(t *testing.T) { }{ { "Happy case-WWFF", - args{fullLog: sampleFilledLog1, isWWFF: true, isSOTA: false}, + args{ + fullLog: sampleFilledLog1, + adifParams: AdifParams{IsWWFF: true, IsSOTA: false}, + }, expectedOutput1, }, + { + "Happy case-POTA", + args{ + fullLog: sampleFilledLogPOTA, + adifParams: AdifParams{IsPOTA: true}, + }, + expectedOutputPOTA, + }, { "Happy case-Grid", - args{fullLog: sampleFilledLog2, isWWFF: true, isSOTA: false}, + args{fullLog: sampleFilledLog2, + adifParams: AdifParams{IsWWFF: true, IsSOTA: false}, + }, expectedOutput2, }, { - "Happy case-Park2Park", - args{fullLog: sampleFilledLog3, isWWFF: true, isSOTA: false}, + "Happy case-WWFF2WWFF", + args{ + fullLog: sampleFilledLog3, + adifParams: AdifParams{IsWWFF: true}, + }, expectedOutput3, }, + { + "Happy case-POTA2POTA", + args{ + fullLog: sampleFilledLogPOTA2, + adifParams: AdifParams{IsPOTA: true}, + }, + expectedOutputPOTA2, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if gotAdifList := buildAdif(tt.args.fullLog, tt.args.isWWFF, tt.args.isSOTA); !reflect.DeepEqual(gotAdifList, tt.wantAdifList) { + if gotAdifList := buildAdif(tt.args.fullLog, tt.args.adifParams); !reflect.DeepEqual(gotAdifList, tt.wantAdifList) { t.Errorf("buildAdif() = %v, want %v", gotAdifList, tt.wantAdifList) } }) diff --git a/fleprocess/csv_process.go b/fleprocess/csv_process.go index 76e8182..653006c 100644 --- a/fleprocess/csv_process.go +++ b/fleprocess/csv_process.go @@ -38,8 +38,8 @@ func ProcessCsvCommand(inputFilename, outputFilename string, isInterpolateTime, 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 CSV file") + if loadedLogFile, isLoadedOK = LoadFile(inputFilename, isInterpolateTime); !isLoadedOK { + return fmt.Errorf("there were input file parsing errors. Could not generate CSV file") } //Check if we have all the necessary data @@ -56,7 +56,7 @@ func ProcessCsvCommand(inputFilename, outputFilename string, isInterpolateTime, //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") + return fmt.Errorf("no QSO found") } isNoMySota := false @@ -66,7 +66,7 @@ func validateDataForSotaCsv(loadedLogFile []LogLine) error { isNoMySota = true } if loadedLogFile[0].MyCall == "" { - return fmt.Errorf("Missing MyCall") + return fmt.Errorf("missing MyCall") } var errorsBuffer strings.Builder @@ -83,45 +83,45 @@ func validateDataForSotaCsv(loadedLogFile []LogLine) error { if loadedLogFile[i].Date == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation)) } if loadedLogFile[i].Band == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation)) } if loadedLogFile[i].Mode == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation)) } if loadedLogFile[i].Call == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation)) } if loadedLogFile[i].Time == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing QSO time %s", errorLocation)) } //FIXME: if isNoMySota and MySota defined means that it was defined later in the log file if isNoMySota && loadedLogFile[i].MySOTA != "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("encountered an unexpexted MySota reference while processing what should be a chaser log %s", errorLocation)) } if isNoMySota && loadedLogFile[i].SOTA == "" { if errorsBuffer.String() != "" { - errorsBuffer.WriteString(fmt.Sprintf(", ")) + errorsBuffer.WriteString(", ") } errorsBuffer.WriteString(fmt.Sprintf("missing SOTA reference while attempting to process chaser log %s", errorLocation)) } diff --git a/fleprocess/csv_process_test.go b/fleprocess/csv_process_test.go index 238aa22..1579d31 100644 --- a/fleprocess/csv_process_test.go +++ b/fleprocess/csv_process_test.go @@ -48,7 +48,7 @@ func Test_validateDataForSotaCsv(t *testing.T) { {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"), + fmt.Errorf("missing MyCall"), }, { "Neither Activator nor Chaser", diff --git a/fleprocess/csv_write.go b/fleprocess/csv_write.go index 2cf69d7..67f6663 100644 --- a/fleprocess/csv_write.go +++ b/fleprocess/csv_write.go @@ -22,7 +22,7 @@ import ( "time" ) -// outputAdif generates and writes data in ADIF format +// outputCsv generates and writes data in csv format func outputCsv(outputFile string, fullLog []LogLine) { //convert the log data to an in-memory ADIF file @@ -40,7 +40,7 @@ func buildCsv(fullLog []LogLine) (csvList []string) { for _, logLine := range fullLog { var csvLine strings.Builder csvLine.WriteString("V2,") - csvLine.WriteString(fmt.Sprintf("%s", logLine.MyCall)) + csvLine.WriteString(logLine.MyCall) csvLine.WriteString(fmt.Sprintf(",%s", logLine.MySOTA)) csvLine.WriteString(fmt.Sprintf(",%s", csvDate(logLine.Date))) csvLine.WriteString(fmt.Sprintf(",%s", logLine.Time)) @@ -65,7 +65,7 @@ func buildCsv(fullLog []LogLine) (csvList []string) { return csvList } -//adifDate converts a date in YYYY-MM-DD format to YYYYMMDD +//csvDate converts a date in YYYY-MM-DD format to YYYY/MM/DD func csvDate(inputDate string) (outputDate string) { const FLEdateFormat = "2006-01-02" date, err := time.Parse(FLEdateFormat, inputDate) diff --git a/fleprocess/displayLog.go b/fleprocess/displayLog.go index ff14b56..c6886a4 100644 --- a/fleprocess/displayLog.go +++ b/fleprocess/displayLog.go @@ -29,6 +29,7 @@ func SprintLogRecord(logLine LogLine) string { output.WriteString("Operator " + logLine.Operator + "\n") output.WriteString("MyWWFF " + logLine.MyWWFF + "\n") output.WriteString("MySOTA " + logLine.MySOTA + "\n") + output.WriteString("MyPOTA " + logLine.MyPOTA + "\n") output.WriteString("MyGrid " + logLine.MyGrid + "\n") output.WriteString("QslMsg " + logLine.QslMsgFromHeader + "\n") output.WriteString("Nickname " + logLine.Nickname + "\n") @@ -70,6 +71,10 @@ func SprintHeaderValues(logLine LogLine) string { output.WriteString("MySOTA " + logLine.MySOTA + "\n") } + if logLine.MyPOTA != "" { + output.WriteString("MyPOTA " + logLine.MyPOTA + "\n") + } + if logLine.MyGrid != "" { output.WriteString("MyGrid " + logLine.MyGrid + "\n") } diff --git a/fleprocess/displayLog_test.go b/fleprocess/displayLog_test.go index e23a42b..77e2868 100644 --- a/fleprocess/displayLog_test.go +++ b/fleprocess/displayLog_test.go @@ -32,8 +32,8 @@ func TestSprintHeaderValues(t *testing.T) { }{ { "Full Option", - args{logLine: LogLine{MyCall: "on4kjm/p", Operator: "on4kjm", MyWWFF: "wwff", MySOTA: "sota"}}, - "MyCall on4kjm/p (on4kjm)\nMyWWFF wwff\nMySOTA sota\n", + args{logLine: LogLine{MyCall: "on4kjm/p", Operator: "on4kjm", MyWWFF: "wwff", MySOTA: "sota", MyPOTA: "pota"}}, + "MyCall on4kjm/p (on4kjm)\nMyWWFF wwff\nMySOTA sota\nMyPOTA pota\n", }, { "Minimal", @@ -70,6 +70,7 @@ func ExampleSprintLogRecord() { Operator: "operator", MyWWFF: "myWwff", MySOTA: "mySota", + MyPOTA: "myPota", MyGrid: "myGrid", QslMsgFromHeader: "QslMsgFromHeader", Nickname: "nickname", @@ -99,6 +100,7 @@ func ExampleSprintLogRecord() { //Operator operator //MyWWFF myWwff //MySOTA mySota + //MyPOTA myPota //MyGrid myGrid //QslMsg QslMsgFromHeader //Nickname nickname diff --git a/fleprocess/inferTime.go b/fleprocess/inferTime.go index 0dd0d0f..1b8f640 100644 --- a/fleprocess/inferTime.go +++ b/fleprocess/inferTime.go @@ -64,7 +64,7 @@ func (tb *InferTimeBlock) finalizeTimeGap() error { //Do we have a positive noTimeCount if tb.noTimeCount < 1 { - return fmt.Errorf("Invalid number of records without time (%d)", tb.noTimeCount) + return fmt.Errorf("invalid number of records without time (%d)", tb.noTimeCount) } //TODO: What should we expect as logFilePosition? @@ -76,15 +76,15 @@ func (tb *InferTimeBlock) finalizeTimeGap() error { func (tb *InferTimeBlock) validateTimeGap() error { //Check that lastRecordedTime and nextValidTime are not null if tb.lastRecordedTime.IsZero() { - return errors.New("Gap start time is empty") + return errors.New("gap start time is empty") } if tb.nextValidTime.IsZero() { - return errors.New("Gap end time is empty") + return errors.New("gap end time is empty") } //Fail if we have a negative time difference if tb.nextValidTime.Before(tb.lastRecordedTime) { - return errors.New("Gap start time is later than the Gap end time") + return errors.New("gap start time is later than the Gap end time") } return nil } @@ -101,20 +101,20 @@ func (tb *InferTimeBlock) storeTimeGap(logline LogLine, position int) (bool, err if tb.noTimeCount == 0 { //File is bad: date not found or badly formated if logline.Date == "" { - return false, errors.New("Date not defined or badly formated") + return false, errors.New("date not defined or badly formated") } if tb.lastRecordedTime, err = time.Parse(ADIFdateTimeFormat, logline.Date+" "+logline.ActualTime); err != nil { - log.Println("Fatal error during internal date conversion: ", err) + log.Println("fatal error during internal date conversion: ", err) os.Exit(1) } tb.logFilePosition = position } else { // We reached the end of the gap if tb.lastRecordedTime.IsZero() { - return false, errors.New("Gap start time is empty") + return false, errors.New("gap start time is empty") } if tb.nextValidTime, err = time.Parse(ADIFdateTimeFormat, logline.Date+" "+logline.ActualTime); err != nil { - log.Println("Fatal error during internal date conversion: ", err) + log.Println("fatal error during internal date conversion: ", err) os.Exit(1) } return true, nil @@ -122,11 +122,11 @@ func (tb *InferTimeBlock) storeTimeGap(logline LogLine, position int) (bool, err } else { //Check the data is correct. if tb.lastRecordedTime.IsZero() { - err = errors.New("Gap start time is empty") + err = errors.New("gap start time is empty") //TODO: this smells } if !tb.nextValidTime.IsZero() { - err = errors.New("Gap end time is not empty") + err = errors.New("gap end time is not empty") } tb.noTimeCount++ } diff --git a/fleprocess/inferTime_test.go b/fleprocess/inferTime_test.go index 141e5fa..785c149 100644 --- a/fleprocess/inferTime_test.go +++ b/fleprocess/inferTime_test.go @@ -123,7 +123,7 @@ func TestInferTimeBlock_computeGaps_invalidData(t *testing.T) { if err == nil { t.Error("Should have failed with an error") } - if err.Error() != "Gap start time is empty" { + if err.Error() != "gap start time is empty" { t.Error("Did not not fail with the expected error.") } } @@ -140,7 +140,7 @@ func TestInferTimeBlock_computeGaps_missingEnTime(t *testing.T) { if err == nil { t.Error("Should have failed with an error") } - if err.Error() != "Gap end time is empty" { + if err.Error() != "gap end time is empty" { t.Errorf("Did not not fail with the expected error. Failed with %s", err) } } @@ -158,7 +158,7 @@ func TestInferTimeBlock_computeGaps_negativeDifference(t *testing.T) { if err == nil { t.Error("Should have failed with an error") } - if err.Error() != "Gap start time is later than the Gap end time" { + if err.Error() != "gap start time is later than the Gap end time" { t.Errorf("Did not not fail with the expected error. Failed with %s", err) } } @@ -278,7 +278,7 @@ func TestInferTimeBlock_increment_missingLastTime(t *testing.T) { isEndGap, err := tb.storeTimeGap(logLine, recordNbr) // Then - if err.Error() != "Gap start time is empty" { + if err.Error() != "gap start time is empty" { t.Errorf("Unexpected error: %s", err) } if isEndGap == true { @@ -303,7 +303,7 @@ func TestInferTimeBlock_increment_alreadyDefinedNewTime(t *testing.T) { isEndGap, err := tb.storeTimeGap(logLine, recordNbr) // Then - if err.Error() != "Gap end time is not empty" { + if err.Error() != "gap end time is not empty" { t.Errorf("Unexpected error: %s", err) } if isEndGap == true { diff --git a/fleprocess/load_file.go b/fleprocess/load_file.go index d5e8efb..d79f7ac 100644 --- a/fleprocess/load_file.go +++ b/fleprocess/load_file.go @@ -52,23 +52,26 @@ func LoadFile(inputFilename string, isInterpolateTime bool) (filleFullLog []LogL //isInferTimeFatalError is set to true is something bad happened while storing time gaps. isInferTimeFatalError := false - regexpLineComment := regexp.MustCompile("^[[:blank:]]*#") - regexpOnlySpaces := regexp.MustCompile("^\\s+$") - regexpSingleMultiLineComment := regexp.MustCompile("^[[:blank:]]*{.+}$") - regexpStartMultiLineComment := regexp.MustCompile("^[[:blank:]]*{") - regexpEndMultiLineComment := regexp.MustCompile("}$") - regexpHeaderMyCall := regexp.MustCompile("(?i)^mycall ") - regexpHeaderOperator := regexp.MustCompile("(?i)^operator ") - regexpHeaderMyWwff := regexp.MustCompile("(?i)^mywwff ") - regexpHeaderMySota := regexp.MustCompile("(?i)^mysota ") - regexpHeaderMyGrid := regexp.MustCompile("(?i)^mygrid ") - regexpHeaderQslMsg := regexp.MustCompile("(?i)^qslmsg ") - regexpHeaderNickname := regexp.MustCompile("(?i)^nickname ") + regexpLineComment := regexp.MustCompile(`^[[:blank:]]*#`) + regexpOnlySpaces := regexp.MustCompile(`^\s+$`) + regexpSingleMultiLineComment := regexp.MustCompile(`^[[:blank:]]*{.+}$`) + regexpStartMultiLineComment := regexp.MustCompile(`^[[:blank:]]*{`) + regexpEndMultiLineComment := regexp.MustCompile(`}$`) + //FIXME: fields can delimited with space or TAB ("(?i)^mywwff\s+") + regexpHeaderMyCall := regexp.MustCompile(`(?i)^mycall\s+`) + regexpHeaderOperator := regexp.MustCompile(`(?i)^operator\s+`) + regexpHeaderMyWwff := regexp.MustCompile(`(?i)^mywwff\s+`) + regexpHeaderMySota := regexp.MustCompile(`(?i)^mysota\s+`) + regexpHeaderMyPota := regexp.MustCompile(`(?i)^mypota\s+`) + regexpHeaderMyGrid := regexp.MustCompile(`(?i)^mygrid\s+`) + regexpHeaderQslMsg := regexp.MustCompile(`(?i)^qslmsg\s+`) + regexpHeaderNickname := regexp.MustCompile(`(?i)^nickname\s+`) headerMyCall := "" headerOperator := "" headerMyWWFF := "" headerMySOTA := "" + headerMyPOTA := "" headerMyGrid := "" headerQslMsg := "" headerNickname := "" @@ -182,6 +185,26 @@ func LoadFile(inputFilename string, isInterpolateTime bool) (filleFullLog []LogL continue } + //My Pota + if regexpHeaderMyPota.MatchString(eachline) { + //Attempt to redefine value + if headerMyPOTA != "" { + errorLog = append(errorLog, fmt.Sprintf("Attempt to redefine MyPOTA at line %d", lineCount)) + continue + } + errorMsg := "" + myPotaList := regexpHeaderMyPota.Split(eachline, -1) + if len(strings.TrimSpace(myPotaList[1])) > 0 { + headerMyPOTA, errorMsg = ValidatePota(strings.TrimSpace(myPotaList[1])) + cleanedInput = append(cleanedInput, fmt.Sprintf("My Pota: %s", headerMyPOTA)) + if len(errorMsg) != 0 { + errorLog = append(errorLog, fmt.Sprintf("Invalid \"My POTA\" at line %d: %s (%s)", lineCount, myPotaList[1], errorMsg)) + } + } + //If there is no data after the marker, we just skip the data. + continue + } + //My Sota if regexpHeaderMySota.MatchString(eachline) { //Attempt to redefine value @@ -257,6 +280,7 @@ func LoadFile(inputFilename string, isInterpolateTime bool) (filleFullLog []LogL previousLogLine.MyCall = headerMyCall previousLogLine.Operator = headerOperator previousLogLine.MyWWFF = headerMyWWFF + previousLogLine.MyPOTA = headerMyPOTA previousLogLine.MySOTA = headerMySOTA previousLogLine.MyGrid = headerMyGrid previousLogLine.QSLmsg = headerQslMsg //previousLogLine.QslMsg is redundant @@ -320,7 +344,7 @@ func LoadFile(inputFilename string, isInterpolateTime bool) (filleFullLog []LogL if isInterpolateTime { //Do we have an open timeBlok that has not been closed. if (wrkTimeBlock.noTimeCount > 0) && (wrkTimeBlock.nextValidTime.IsZero()) { - errorLog = append(errorLog, fmt.Sprint("Fatal error: missing new time to infer time")) + errorLog = append(errorLog, "Fatal error: missing new time to infer time") } else { for _, timeBlock := range missingTimeBlockList { if err := timeBlock.validateTimeGap(); err != nil { diff --git a/fleprocess/load_file_test.go b/fleprocess/load_file_test.go index fb5c5c3..c3fabdb 100644 --- a/fleprocess/load_file_test.go +++ b/fleprocess/load_file_test.go @@ -37,10 +37,11 @@ func TestLoadFile_happyCase(t *testing.T) { dataArray = append(dataArray, " ") dataArray = append(dataArray, "# Header") dataArray = append(dataArray, "myCall on4kjm/p") - dataArray = append(dataArray, "operator on4kjm") + dataArray = append(dataArray, "operator\t on4kjm") dataArray = append(dataArray, "nickname Portable") - dataArray = append(dataArray, "myWwff onff-0258") + dataArray = append(dataArray, "myWwff\tonff-0258") dataArray = append(dataArray, "mySota on/on-001") + dataArray = append(dataArray, "myPota k-0802") dataArray = append(dataArray, "myGrid jo50") dataArray = append(dataArray, "QslMsg This is a QSL message") dataArray = append(dataArray, " ") diff --git a/fleprocess/output_filename.go b/fleprocess/output_filename.go index 4795f36..99c89b3 100644 --- a/fleprocess/output_filename.go +++ b/fleprocess/output_filename.go @@ -27,7 +27,7 @@ func buildOutputFilename(output string, input string, overwrite bool, newExtensi //validate that input is populated (should never happen if properly called) if input == "" { - return "", fmt.Errorf("Unexepected error: no input file provided") + return "", fmt.Errorf("unexepected error: no input file provided") } //No output was provided, let's create one from the input file @@ -46,12 +46,12 @@ func buildOutputFilename(output string, input string, overwrite bool, newExtensi } //It exisits but is a directory if info.IsDir() { - return "", fmt.Errorf("Error: specified output exists and is a directory") + return "", fmt.Errorf("error: specified output exists and is a directory") } if overwrite { //user accepted to overwrite the file return output, nil } - return "", fmt.Errorf("File already exists. Use --overwrite flag if necessary") + 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 e8ac8c7..427899c 100644 --- a/fleprocess/output_filename_test.go +++ b/fleprocess/output_filename_test.go @@ -58,7 +58,7 @@ func Test_buildOutputFilename(t *testing.T) { { "input file not provided", args{input: "", output: "xxx", overwrite: false, extension: ".adi"}, - "", errors.New("Unexepected error: no input file provided"), + "", errors.New("unexepected error: no input file provided"), }, { "Output file does not exist", @@ -68,12 +68,12 @@ func Test_buildOutputFilename(t *testing.T) { { "Output name is a directory", args{input: "a file", output: testDir, overwrite: false, extension: ".adi"}, - "", errors.New("Error: specified output exists and is a directory"), + "", errors.New("error: specified output exists and is a directory"), }, { "Output exist but no overwrite", args{input: "a file", output: testFile, overwrite: false, extension: ".adi"}, - "", errors.New("File already exists. Use --overwrite flag if necessary"), + "", errors.New("file already exists. Use --overwrite flag if necessary"), }, { "Output exist but user wants to overwrite", diff --git a/fleprocess/parse_line.go b/fleprocess/parse_line.go index 10dfc79..66dee75 100644 --- a/fleprocess/parse_line.go +++ b/fleprocess/parse_line.go @@ -30,7 +30,10 @@ type LogLine struct { MyCall string Operator string MyWWFF string + MyPOTA string MySOTA string + MyPota string + MySota string MyGrid string QslMsgFromHeader string Nickname string @@ -50,21 +53,23 @@ type LogLine struct { RSTsent string RSTrcvd string WWFF string + POTA string SOTA string } -var regexpIsFullTime = regexp.MustCompile("^[0-2]{1}[0-9]{3}$") -var regexpIsTimePart = regexp.MustCompile("^[0-5]{1}[0-9]{1}$|^[1-9]{1}$") -var regexpIsOMname = regexp.MustCompile("^@") -var regexpIsGridLoc = regexp.MustCompile("^#") -var regexpIsRst = regexp.MustCompile("^[\\d]{1,3}$") -var regexpIsFreq = regexp.MustCompile("^[\\d]+\\.[\\d]+$") -var regexpIsSotaKeyWord = regexp.MustCompile("(?i)^sota$") -var regexpIsWwffKeyWord = regexp.MustCompile("(?i)^wwff$") -var regexpDatePattern = regexp.MustCompile("^(\\d{2}|\\d{4})[-/ .]\\d{1,2}[-/ .]\\d{1,2}$") -var regexpIsDateKeyWord = regexp.MustCompile("(?i)^date$") -var regexpDayIncrementPattern = regexp.MustCompile("^\\+*$") -var regexpIsDayKeyword = regexp.MustCompile("(?i)^day$") +var regexpIsFullTime = regexp.MustCompile(`^[0-2]{1}[0-9]{3}$`) +var regexpIsTimePart = regexp.MustCompile(`^[0-5]{1}[0-9]{1}$|^[1-9]{1}$`) +var regexpIsOMname = regexp.MustCompile(`^@`) +var regexpIsGridLoc = regexp.MustCompile(`^#`) +var regexpIsRst = regexp.MustCompile(`^[\d]{1,3}$`) +var regexpIsFreq = regexp.MustCompile(`^[\d]+\.[\d]+$`) +var regexpIsSotaKeyWord = regexp.MustCompile(`(?i)^sota$`) +var regexpIsWwffKeyWord = regexp.MustCompile(`(?i)^wwff$`) +var regexpIsPotaKeyWord = regexp.MustCompile(`(?i)^pota$`) +var regexpDatePattern = regexp.MustCompile(`^(\d{2}|\d{4})[-/ .]\d{1,2}[-/ .]\d{1,2}$`) +var regexpIsDateKeyWord = regexp.MustCompile(`(?i)^date$`) +var regexpDayIncrementPattern = regexp.MustCompile(`^\+*$`) +var regexpIsDayKeyword = regexp.MustCompile(`(?i)^day$`) // ParseLine cuts a FLE line into useful bits func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg string) { @@ -82,6 +87,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg previousLine.RSTsent = "" previousLine.RSTrcvd = "" previousLine.SOTA = "" + previousLine.POTA = "" previousLine.WWFF = "" previousLine.OMname = "" previousLine.GridLoc = "" @@ -156,7 +162,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg increment := len(element) newDate, dateError := IncrementDate(logLine.Date, increment) if dateError != "" { - errorMsg = errorMsg + fmt.Sprintf(dateError) + errorMsg = errorMsg + dateError } logLine.Date = newDate continue @@ -177,6 +183,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg qrg, _ = strconv.ParseFloat(element, 32) if (logLine.BandLowerLimit != 0.0) && (logLine.BandUpperLimit != 0.0) { if (qrg >= logLine.BandLowerLimit) && (qrg <= logLine.BandUpperLimit) { + //TODO: print 3f or more is available logLine.Frequency = fmt.Sprintf("%.3f", qrg) } else { logLine.Frequency = "" @@ -202,7 +209,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg } // Is it a "full" time ? - if isRightOfCall == false { + if !isRightOfCall { if regexpIsFullTime.MatchString(element) { logLine.Time = element logLine.ActualTime = element @@ -289,6 +296,19 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg continue } + // If the "pota" keyword is used, skip it + if regexpIsPotaKeyWord.MatchString(element) { + // this keyword is not requiered anymore with FLE 3 and doesn't add any value + continue + } + + // Is it a "POTA to POTA" reference? + workRef, potaErr := ValidatePota(element) + if potaErr == "" { + logLine.POTA = workRef + continue + } + // If the "sota" keyword is used, skip it if regexpIsSotaKeyWord.MatchString(element) { // this keyword is not requiered anymore with FLE 3 and doesn't add any value diff --git a/fleprocess/parse_line_test.go b/fleprocess/parse_line_test.go index 9b0c4ac..2204975 100644 --- a/fleprocess/parse_line_test.go +++ b/fleprocess/parse_line_test.go @@ -158,6 +158,16 @@ func TestParseLine(t *testing.T) { args{inputStr: "1230 oe6cud/p onff-0258", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}}, LogLine{Call: "OE6CUD/P", Time: "1230", ActualTime: "1230", RSTsent: "59", RSTrcvd: "59", Mode: "FM", ModeType: "PHONE", WWFF: "ONFF-0258"}, "", }, + { + "POTA keywork ", + args{inputStr: "1230 oe6cud/p pota on-0258", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}}, + LogLine{Call: "OE6CUD/P", Time: "1230", ActualTime: "1230", RSTsent: "59", RSTrcvd: "59", Mode: "FM", ModeType: "PHONE", POTA: "ON-0258"}, "", + }, + { + "implied POTA keywork ", + args{inputStr: "1230 oe6cud/p on-0258", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}}, + LogLine{Call: "OE6CUD/P", Time: "1230", ActualTime: "1230", RSTsent: "59", RSTrcvd: "59", Mode: "FM", ModeType: "PHONE", POTA: "ON-0258"}, "", + }, { "date processing", args{inputStr: "20.09.7 1230 oe6cud/p onff-0258", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}}, diff --git a/fleprocess/validate.go b/fleprocess/validate.go index d3ee276..7e87ef8 100644 --- a/fleprocess/validate.go +++ b/fleprocess/validate.go @@ -51,6 +51,20 @@ func ValidateWwff(inputStr string) (ref, errorMsg string) { return wrongInputStr, errorMsg } +var validPotaRegexp = regexp.MustCompile(`^[\d]{0,1}[A-Z]{1,2}-[\d]{4}$`) + +// ValidatePota verifies whether the supplied string is a valid POTA reference. +// The syntax is: AA-CCCC: AA = national prefix, CCCC = 4-digit numeric code (e.g. ON-0001). +func ValidatePota(inputStr string) (ref, errorMsg string) { + inputStr = strings.ToUpper(strings.TrimSpace(inputStr)) + wrongInputStr := "*" + inputStr + if validPotaRegexp.MatchString(inputStr) { + return inputStr, "" + } + errorMsg = "[" + inputStr + "] is an invalid POTA reference" + return wrongInputStr, errorMsg +} + var validGridRegexp = regexp.MustCompile("(?i)^[a-z]{2}[0-9]{2}([a-z]{2})?$") // ValidateGridLocator verifies that the supplied is a valid Maidenhead locator reference diff --git a/fleprocess/validate_test.go b/fleprocess/validate_test.go index 29d0751..b0b3c7e 100644 --- a/fleprocess/validate_test.go +++ b/fleprocess/validate_test.go @@ -63,6 +63,65 @@ func TestValidateWwff(t *testing.T) { } } +func TestValidatePota(t *testing.T) { + type args struct { + inputStr string + } + tests := []struct { + name string + args args + wantRef string + wantErrorMsg string + }{ + { + "Good ref (simple)", + args{inputStr: "on-0258"}, + "ON-0258", "", + }, + { + "Good ref (single digit country)", + args{inputStr: "f-0258"}, + "F-0258", "", + }, + { + "Good ref (Numerical country)", + args{inputStr: "4x-0258"}, + "4X-0258", "", + }, + { + "Bad ref (no country prefix)", + args{inputStr: "-0258"}, + "*-0258", "[-0258] is an invalid POTA reference", + }, + { + "Bad ref (wrong separator)", + args{inputStr: "g/0258"}, + "*G/0258", "[G/0258] is an invalid POTA reference", + }, + { + "Bad ref (reference too short)", + args{inputStr: "on-258"}, + "*ON-258", "[ON-258] is an invalid POTA reference", + }, + { + "Bad ref (no country prefix)", + args{inputStr: "on-02589"}, + "*ON-02589", "[ON-02589] is an invalid POTA reference", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRef, gotErrorMsg := ValidatePota(tt.args.inputStr) + if gotRef != tt.wantRef { + t.Errorf("ValidatePota() gotRef = %v, want %v", gotRef, tt.wantRef) + } + if gotErrorMsg != tt.wantErrorMsg { + t.Errorf("ValidatePota() gotErrorMsg = %v, want %v", gotErrorMsg, tt.wantErrorMsg) + } + }) + } +} + func TestValidateSota(t *testing.T) { type args struct { inputStr string diff --git a/fleprocess/write_file_test.go b/fleprocess/write_file_test.go index f45f1de..dcdea70 100644 --- a/fleprocess/write_file_test.go +++ b/fleprocess/write_file_test.go @@ -22,7 +22,7 @@ import ( "testing" ) -const writeFileTestDir string = "test2_dir" +const WriteFileTestDir string = "test2_dir" const writeFileTestFname string = "testFile.txt" func Test_writeFile(t *testing.T) { diff --git a/go.mod b/go.mod index 44b3841..fbbf931 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module FLEcli go 1.14 require ( - github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/pelletier/go-toml v1.8.0 // indirect @@ -13,6 +13,5 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.7.1 - golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a // indirect gopkg.in/ini.v1 v1.60.2 // indirect ) diff --git a/go.sum b/go.sum index d1f853d..7a62b80 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -211,6 +213,7 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.hein.dev/go-version v0.1.0 h1:hz3epLdx+cim8EN9XRt6pqAHxwWVW0D87Xm3mUbvKvI= go.hein.dev/go-version v0.1.0/go.mod h1:WOEm7DWMroRe5GdUgHMvx+Pji5WWIpMuXmK/3foylXs= @@ -246,12 +249,15 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -269,6 +275,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -278,6 +285,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -300,6 +308,12 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zr golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -307,6 +321,9 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -332,6 +349,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200902171120-36b1a880d5d1 h1:5SfEmaQTww9A35eeANMuoDMDbba7pCPVplPWQ72i5lY= golang.org/x/tools v0.0.0-20200902171120-36b1a880d5d1/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/test/bats-scripts/test.bats b/test/bats-scripts/test.bats index 88c3c60..305a79e 100644 --- a/test/bats-scripts/test.bats +++ b/test/bats-scripts/test.bats @@ -23,6 +23,12 @@ diff test/output/temp/sota_wwff.csv test/FLE-sample/sota_wwff.csv --strip-trailing-cr } +@test "Is the generated POTA adif equivalent to the canonical one?" { + mkdir -p test/output/temp + output=$(test/docker-FLEcli.sh adif -o -i --pota test/data/sample_pota.txt test/output/temp/sample_pota.adif) + diff test/output/temp/sample_pota.adif test/output/POTA/sample_pota.adif --strip-trailing-cr +} + @test "Processing a FLE file with no QSO must fail!" { run test/docker-FLEcli.sh csv -o -i test/data/fle-4-no-qso.txt [ "$status" -eq 1 ] diff --git a/test/data/fullFeatureHeader.txt b/test/data/fullFeatureHeader.txt index 6b06405..1cda490 100644 --- a/test/data/fullFeatureHeader.txt +++ b/test/data/fullFeatureHeader.txt @@ -4,6 +4,7 @@ operator on4kjm nickname Portable myWwff onff-0258 mySota on/on-001 +myPota on-0001 QslMsg This is a QSL message date 2020-05-23 diff --git a/test/data/sample_pota.txt b/test/data/sample_pota.txt new file mode 100644 index 0000000..2c0185a --- /dev/null +++ b/test/data/sample_pota.txt @@ -0,0 +1,22 @@ +{ Sample POTA log } +# Header +mycall g3wgv +operator g3wgv +myPota g-0014 + +# Log +date 2016-04-24 +40m cw +1202 g4elz +4 g3noh +2m fm +1227 gw4gte +8 gw0tlk/m + +date 2016-06-03 +40m cw +1404 gm0aaa 3 7 +5 on4ck/p 2 +7 dl0dan/p +20m +10 yu7ls \ No newline at end of file diff --git a/test/output/POTA/sample_pota.adif b/test/output/POTA/sample_pota.adif new file mode 100644 index 0000000..6312a2b --- /dev/null +++ b/test/output/POTA/sample_pota.adif @@ -0,0 +1,12 @@ +ADIF Export for Fast Log Entry by DF3CB +FLE +3.1.0 + +G3WGV G4ELZ 20160424 1202 40m CW 599 599 POTA G-0014 G3WGV +G3WGV G3NOH 20160424 1204 40m CW 599 599 PSE QSL Direct POTA G-0014 G3WGV +G3WGV GW4GTE 20160424 1227 2m FM 59 59 Dave POTA G-0014 G3WGV +G3WGV GW0TLK/M 20160424 1228 2m FM 59 59 POTA G-0014 G3WGV +G3WGV GM0AAA 20160603 1404 40m CW 539 579 POTA G-0014 G3WGV +G3WGV ON4CK/P 20160603 1405 40m CW 529 599 POTA G-0014 G3WGV +G3WGV DL0DAN/P 20160603 1407 40m CW 599 599 POTA G-0014 G3WGV +G3WGV YU7LS 20160603 1410 20m CW 599 599 POTA G-0014 G3WGV