Pota support (#73)

* Add POTA processing
* add end to end POTA test
* allow tabs to be a valid separator in the header section
pull/75/head
Jean-Marc MEESSEN 3 years ago committed by GitHub
parent 82ee44de9b
commit 61c42d1c75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -60,7 +60,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: '^1.15.2' go-version: '^1.17.2'
- run: go mod download - run: go mod download
- name: Validates GO releaser config - name: Validates GO releaser config
uses: goreleaser/goreleaser-action@master uses: goreleaser/goreleaser-action@master

@ -15,7 +15,7 @@ jobs:
run: git fetch --prune --unshallow run: git fetch --prune --unshallow
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: '^1.15.2' go-version: '^1.17.2'
- name: Release via goreleaser - name: Release via goreleaser
uses: goreleaser/goreleaser-action@master uses: goreleaser/goreleaser-action@master
with: with:

@ -1,22 +1,26 @@
# This is an example goreleaser.yaml file with some sane defaults. # This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com # Make sure to check the documentation at http://goreleaser.com
builds: builds:
- goos: - binary: FLEcli
goos:
- linux - linux
- windows - windows
- darwin - darwin
goarch: goarch:
- 386 - '386'
- amd64 - amd64
- arm - arm
- arm64 - arm64
goarm: goarm:
- 6 - '6'
ignore: ignore:
- goos: darwin - goos: darwin
goarch: 386 goarch: 386
- goos: windows - goos: windows
goarch: amd64 goarch: amd64
ldflags: ldflags:
- -s -w -X FLEcli/flecmd.version={{.Version}} -X FLEcli/flecmd.commit={{.Commit}} -X FLEcli/flecmd.date={{.Date}} -X=FLEcli/flecmd.builtBy=goReleaser - -s -w -X FLEcli/flecmd.version={{.Version}} -X FLEcli/flecmd.commit={{.Commit}} -X FLEcli/flecmd.date={{.Date}} -X=FLEcli/flecmd.builtBy=goReleaser
env: env:
@ -26,8 +30,6 @@ dockers:
- goos: linux - goos: linux
goarch: amd64 goarch: amd64
goarm: '' goarm: ''
binaries:
- FLEcli
image_templates: image_templates:
- "on4kjm/flecli:latest" - "on4kjm/flecli:latest"
- "on4kjm/flecli:{{ .Tag }}" - "on4kjm/flecli:{{ .Tag }}"

@ -13,7 +13,7 @@ dep: ## Get the dependencies
@go mod download @go mod download
lint: ## Lint Golang files lint: ## Lint Golang files
@go get -u golang.org/x/lint/golint @go install golang.org/x/lint/golint
@golint -set_exit_status ./... @golint -set_exit_status ./...
vet: ## Run go vet vet: ## Run go vet

@ -27,6 +27,7 @@ import (
var outputFilename string var outputFilename string
var isWWFFcli bool var isWWFFcli bool
var isSOTAcli bool var isSOTAcli bool
var isPOTAcli bool
var isOverwrite bool var isOverwrite bool
// adifCmd is executed when choosing the adif option (load and generate adif file) // 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: // and usage of using your command. For example:
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
//if args is empty, throw an error //if args is empty, throw an error
if len(args) == 0 { if len(args) == 0 {
//TODO: fix this ugly statement (because I am lazy) //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] inputFilename = args[0]
if len(args) == 2 { if len(args) == 2 {
@ -50,13 +52,16 @@ var adifCmd = &cobra.Command{
return fmt.Errorf("Too many arguments.%s", "") return fmt.Errorf("Too many arguments.%s", "")
} }
err := fleprocess.ProcessAdifCommand( var adifParam = new(fleprocess.AdifParams)
inputFilename, adifParam.InputFilename = inputFilename
outputFilename, adifParam.OutputFilename = outputFilename
isInterpolateTime, adifParam.IsInterpolateTime = isInterpolateTime
isWWFFcli, adifParam.IsSOTA = isSOTAcli
isSOTAcli, adifParam.IsPOTA = isPOTAcli
isOverwrite) adifParam.IsWWFF = isWWFFcli
adifParam.IsOverwrite = isOverwrite
err := fleprocess.ProcessAdifCommand(*adifParam)
if err != nil { if err != nil {
fmt.Println("\nUnable to generate ADIF file:") fmt.Println("\nUnable to generate ADIF file:")
fmt.Println(err) fmt.Println(err)
@ -73,5 +78,6 @@ func init() {
adifCmd.PersistentFlags().BoolVarP(&isInterpolateTime, "interpolate", "i", false, "Interpolates the missing time entries.") 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(&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(&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") adifCmd.PersistentFlags().BoolVarP(&isOverwrite, "overwrite", "o", false, "Overwrites the output file if it exisits")
} }

@ -21,14 +21,26 @@ import (
"strings" "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 //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 //Validate of build the output filenaem
var verifiedOutputFilename string var verifiedOutputFilename string
var err error 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 return err
} }
@ -36,17 +48,17 @@ func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime,
var loadedLogFile []LogLine var loadedLogFile []LogLine
var isLoadedOK bool var isLoadedOK bool
if loadedLogFile, isLoadedOK = LoadFile(inputFilename, isInterpolateTime); isLoadedOK == false { if loadedLogFile, isLoadedOK = LoadFile(adifParams.InputFilename, adifParams.IsInterpolateTime); !isLoadedOK {
return fmt.Errorf("There were input file parsing errors. Could not generate ADIF file") return fmt.Errorf("there were input file parsing errors. Could not generate ADIF file")
} }
//Check if we have all the necessary data //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 return err
} }
//Write the output file with the checked data //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 //If we reached this point, everything was processed OK and the file generated
return nil return nil
@ -54,31 +66,38 @@ func ProcessAdifCommand(inputFilename, outputFilename string, isInterpolateTime,
//validateDataforAdif checks whether all the required data is present //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/ //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? //do we have QSOs at all?
if len(loadedLogFile) == 0 { 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 == "" { if loadedLogFile[0].MyCall == "" {
return fmt.Errorf("Missing MyCall") return fmt.Errorf("missing MyCall")
} }
if isSOTAcli { if adifParams.IsSOTA {
if loadedLogFile[0].MySOTA == "" { 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 == "" { if loadedLogFile[0].MyWWFF == "" {
return fmt.Errorf("Missing MY-WWFF reference") return fmt.Errorf("missing MY-WWFF reference")
} }
if loadedLogFile[0].Operator == "" { 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 var errorsBuffer strings.Builder
//We accumulate the errors messages //We accumulate the errors messages
for i := 0; i < len(loadedLogFile); i++ { for i := 0; i < len(loadedLogFile); i++ {
@ -93,31 +112,31 @@ func validateDataforAdif(loadedLogFile []LogLine, isWWFFcli, isSOTAcli bool) err
if loadedLogFile[i].Date == "" { if loadedLogFile[i].Date == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation))
} }
if loadedLogFile[i].Band == "" { if loadedLogFile[i].Band == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation))
} }
if loadedLogFile[i].Mode == "" { if loadedLogFile[i].Mode == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation))
} }
if loadedLogFile[i].Call == "" { if loadedLogFile[i].Call == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation))
} }
if loadedLogFile[i].Time == "" { if loadedLogFile[i].Time == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing QSO time %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing QSO time %s", errorLocation))
} }

@ -1,31 +1,14 @@
package fleprocess package fleprocess
/*
Copyright © 2020 Jean-Marc Meessen, ON4KJM <on4kjm@gmail.com>
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 ( import (
"fmt" "fmt"
"testing" "testing"
) )
func Test_validateDataforAdif(t *testing.T) { func Test_validateDataforAdif2(t *testing.T) {
type args struct { type args struct {
loadedLogFile []LogLine loadedLogFile []LogLine
isWWFFcli bool adifParams AdifParams
isSOTAcli bool
} }
tests := []struct { tests := []struct {
name string name string
@ -33,77 +16,125 @@ func Test_validateDataforAdif(t *testing.T) {
want error want error
}{ }{
{ {
"Happy Case (no sota or wwff)", "Happy Case (no sota, pota or wwff)",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{ 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"},
{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, nil,
}, },
{ {
"No data", "No data",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{}}, args{
fmt.Errorf("No QSO found"), adifParams: AdifParams{IsWWFF: false, IsSOTA: false, IsPOTA: false},
loadedLogFile: []LogLine{},
},
fmt.Errorf("no QSO found"),
}, },
{ {
"Missing Date", "Missing Date",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{ 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: "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:02", Call: "call"},
{Date: "", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", 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)"), fmt.Errorf("missing date for log entry at 12:02 (#2), missing date for log entry at 12:03 (#3)"),
}, },
{ {
"Missing MyCall", "Missing MyCall",
args{isWWFFcli: true, isSOTAcli: true, loadedLogFile: []LogLine{ 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"),
},
{
"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: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:02", Call: "call"},
{Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", 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 MySota", "Missing MySota",
args{isWWFFcli: false, isSOTAcli: true, loadedLogFile: []LogLine{ 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"}, {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-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-POTA reference"),
}, },
{ {
"Misc. missing data (Band, Time, Mode, Call)", "Misc. missing data (Band, Time, Mode, Call)",
args{isWWFFcli: false, isSOTAcli: false, loadedLogFile: []LogLine{ 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: "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: "", Band: "band", Time: "12:02", Call: "call"},
{Date: "date", MyCall: "myCall", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", 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)"), 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", "Missing MY-WWFF",
args{isWWFFcli: true, isSOTAcli: false, loadedLogFile: []LogLine{ 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"}, {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", "Missing Operator with isWWFF",
args{isWWFFcli: true, isSOTAcli: false, loadedLogFile: []LogLine{ 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"}, {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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 //Test the error message, if any
if got != nil && tt.want != nil { if got != nil && tt.want != nil {
@ -119,14 +150,10 @@ func Test_validateDataforAdif(t *testing.T) {
} }
} }
func TestProcessAdifCommand(t *testing.T) { func TestProcessAdifCommand(t *testing.T) {
type args struct { type args struct {
inputFilename string adifParams AdifParams
outputFilename string
isInterpolateTime bool
isWWFFcli bool
isSOTAcli bool
isOverwrite bool
} }
tests := []struct { tests := []struct {
name string name string
@ -135,29 +162,57 @@ func TestProcessAdifCommand(t *testing.T) {
}{ }{
{ {
"Bad output filename (directory)", "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, true,
}, },
{ {
"input file parsing errors (missing band)", "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, true,
}, },
{ {
"input file parsing errors (wrong call)", "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, true,
}, },
{ {
"No QSO in loaded file", "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, true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { if err := ProcessAdifCommand(tt.args.adifParams); (err != nil) != tt.wantErr {
t.Errorf("ProcessCsvCommand() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ProcessAdifCommand() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })
} }

@ -23,17 +23,17 @@ import (
) )
// OutputAdif generates and writes data in ADIF format // 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 //convert the log data to an in-memory ADIF file
adifData := buildAdif(fullLog, isWWFF, isSOTA) adifData := buildAdif(fullLog, adifParams)
//write to a file //write to a file
writeFile(outputFile, adifData) writeFile(outputFile, adifData)
} }
// buildAdif creates the adif file in memory ready to be printed // 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 //Print the fixed header
adifList = append(adifList, "ADIF Export for Fast Log Entry by DF3CB") adifList = append(adifList, "ADIF Export for Fast Log Entry by DF3CB")
adifList = append(adifList, "<PROGRAMID:3>FLE") adifList = append(adifList, "<PROGRAMID:3>FLE")
@ -65,7 +65,7 @@ func buildAdif(fullLog []LogLine, isWWFF bool, isSOTA bool) (adifList []string)
if logLine.QSLmsg != "" { if logLine.QSLmsg != "" {
adifLine.WriteString(adifElement("QSLMSG", logLine.QSLmsg)) adifLine.WriteString(adifElement("QSLMSG", logLine.QSLmsg))
} }
if isWWFF { if adifParams.IsWWFF {
adifLine.WriteString(adifElement("MY_SIG", "WWFF")) adifLine.WriteString(adifElement("MY_SIG", "WWFF"))
adifLine.WriteString(adifElement("MY_SIG_INFO", logLine.MyWWFF)) adifLine.WriteString(adifElement("MY_SIG_INFO", logLine.MyWWFF))
if logLine.WWFF != "" { if logLine.WWFF != "" {
@ -73,7 +73,15 @@ func buildAdif(fullLog []LogLine, isWWFF bool, isSOTA bool) (adifList []string)
adifLine.WriteString(adifElement("SIG_INFO", logLine.WWFF)) 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)) adifLine.WriteString(adifElement("MY_SOTA_REF", logLine.MySOTA))
if logLine.SOTA != "" { if logLine.SOTA != "" {
adifLine.WriteString(adifElement("SOTA_REF", logLine.SOTA)) adifLine.WriteString(adifElement("SOTA_REF", logLine.SOTA))

@ -94,10 +94,38 @@ func Test_buildAdif(t *testing.T) {
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <SIG:4>WWFF <SIG_INFO:9>DLFF-0001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>", "<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>WWFF <MY_SIG_INFO:9>ONFF-0259 <SIG:4>WWFF <SIG_INFO:9>DLFF-0001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
} }
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",
"<PROGRAMID:3>FLE",
"<ADIF_VER:5>3.1.0",
"<EOH>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <OPERATOR:6>ON4KJM <APP_EQSL_QTH_NICKNAME:10>ON-00259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <OPERATOR:6>ON4KJM <EOR>",
}
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",
"<PROGRAMID:3>FLE",
"<ADIF_VER:5>3.1.0",
"<EOH>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>S57LC <QSO_DATE:8>20200524 <TIME_ON:4>1310 <BAND:3>20m <MODE:2>CW <FREQ:6>14.045 <RST_SENT:3>599 <RST_RCVD:3>599 <GRIDSQUARE:4>JO50 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <APP_EQSL_QTH_NICKNAME:10>ON-00259-1 <EOR>",
"<STATION_CALLSIGN:8>ON4KJM/P <CALL:5>ON4LY <QSO_DATE:8>20200524 <TIME_ON:4>1312 <BAND:3>20m <MODE:2>CW <RST_SENT:3>559 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:8>ON-00259 <SIG:4>POTA <SIG_INFO:8>DL-00001 <OPERATOR:6>ON4KJM <MY_GRIDSQUARE:6>JO40eu <EOR>",
}
type args struct { type args struct {
fullLog []LogLine fullLog []LogLine
isWWFF bool adifParams AdifParams
isSOTA bool
} }
tests := []struct { tests := []struct {
name string name string
@ -106,23 +134,47 @@ func Test_buildAdif(t *testing.T) {
}{ }{
{ {
"Happy case-WWFF", "Happy case-WWFF",
args{fullLog: sampleFilledLog1, isWWFF: true, isSOTA: false}, args{
fullLog: sampleFilledLog1,
adifParams: AdifParams{IsWWFF: true, IsSOTA: false},
},
expectedOutput1, expectedOutput1,
}, },
{
"Happy case-POTA",
args{
fullLog: sampleFilledLogPOTA,
adifParams: AdifParams{IsPOTA: true},
},
expectedOutputPOTA,
},
{ {
"Happy case-Grid", "Happy case-Grid",
args{fullLog: sampleFilledLog2, isWWFF: true, isSOTA: false}, args{fullLog: sampleFilledLog2,
adifParams: AdifParams{IsWWFF: true, IsSOTA: false},
},
expectedOutput2, expectedOutput2,
}, },
{ {
"Happy case-Park2Park", "Happy case-WWFF2WWFF",
args{fullLog: sampleFilledLog3, isWWFF: true, isSOTA: false}, args{
fullLog: sampleFilledLog3,
adifParams: AdifParams{IsWWFF: true},
},
expectedOutput3, expectedOutput3,
}, },
{
"Happy case-POTA2POTA",
args{
fullLog: sampleFilledLogPOTA2,
adifParams: AdifParams{IsPOTA: true},
},
expectedOutputPOTA2,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) t.Errorf("buildAdif() = %v, want %v", gotAdifList, tt.wantAdifList)
} }
}) })

@ -38,8 +38,8 @@ func ProcessCsvCommand(inputFilename, outputFilename string, isInterpolateTime,
var loadedLogFile []LogLine var loadedLogFile []LogLine
var isLoadedOK bool var isLoadedOK bool
if loadedLogFile, isLoadedOK = LoadFile(inputFilename, isInterpolateTime); isLoadedOK == false { if loadedLogFile, isLoadedOK = LoadFile(inputFilename, isInterpolateTime); !isLoadedOK {
return fmt.Errorf("There were input file parsing errors. Could not generate CSV file") return fmt.Errorf("there were input file parsing errors. Could not generate CSV file")
} }
//Check if we have all the necessary data //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 //validateDataForSotaCsv checks whether all the requiered data is present in the supplied data
func validateDataForSotaCsv(loadedLogFile []LogLine) error { func validateDataForSotaCsv(loadedLogFile []LogLine) error {
if len(loadedLogFile) == 0 { if len(loadedLogFile) == 0 {
return fmt.Errorf("No QSO found") return fmt.Errorf("no QSO found")
} }
isNoMySota := false isNoMySota := false
@ -66,7 +66,7 @@ func validateDataForSotaCsv(loadedLogFile []LogLine) error {
isNoMySota = true isNoMySota = true
} }
if loadedLogFile[0].MyCall == "" { if loadedLogFile[0].MyCall == "" {
return fmt.Errorf("Missing MyCall") return fmt.Errorf("missing MyCall")
} }
var errorsBuffer strings.Builder var errorsBuffer strings.Builder
@ -83,45 +83,45 @@ func validateDataForSotaCsv(loadedLogFile []LogLine) error {
if loadedLogFile[i].Date == "" { if loadedLogFile[i].Date == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing date %s", errorLocation))
} }
if loadedLogFile[i].Band == "" { if loadedLogFile[i].Band == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing band %s", errorLocation))
} }
if loadedLogFile[i].Mode == "" { if loadedLogFile[i].Mode == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing mode %s", errorLocation))
} }
if loadedLogFile[i].Call == "" { if loadedLogFile[i].Call == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing call %s", errorLocation))
} }
if loadedLogFile[i].Time == "" { if loadedLogFile[i].Time == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing QSO time %s", errorLocation)) 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 //FIXME: if isNoMySota and MySota defined means that it was defined later in the log file
if isNoMySota && loadedLogFile[i].MySOTA != "" { if isNoMySota && loadedLogFile[i].MySOTA != "" {
if errorsBuffer.String() != "" { 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)) 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 isNoMySota && loadedLogFile[i].SOTA == "" {
if errorsBuffer.String() != "" { if errorsBuffer.String() != "" {
errorsBuffer.WriteString(fmt.Sprintf(", ")) errorsBuffer.WriteString(", ")
} }
errorsBuffer.WriteString(fmt.Sprintf("missing SOTA reference while attempting to process chaser log %s", errorLocation)) errorsBuffer.WriteString(fmt.Sprintf("missing SOTA reference while attempting to process chaser log %s", errorLocation))
} }

@ -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:02", Call: "call"},
{Date: "date", MyCall: "", MySOTA: "mySota", Mode: "mode", Band: "band", Time: "12:03", 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", "Neither Activator nor Chaser",

@ -22,7 +22,7 @@ import (
"time" "time"
) )
// outputAdif generates and writes data in ADIF format // outputCsv generates and writes data in csv format
func outputCsv(outputFile string, fullLog []LogLine) { func outputCsv(outputFile string, fullLog []LogLine) {
//convert the log data to an in-memory ADIF file //convert the log data to an in-memory ADIF file
@ -40,7 +40,7 @@ func buildCsv(fullLog []LogLine) (csvList []string) {
for _, logLine := range fullLog { for _, logLine := range fullLog {
var csvLine strings.Builder var csvLine strings.Builder
csvLine.WriteString("V2,") 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", logLine.MySOTA))
csvLine.WriteString(fmt.Sprintf(",%s", csvDate(logLine.Date))) csvLine.WriteString(fmt.Sprintf(",%s", csvDate(logLine.Date)))
csvLine.WriteString(fmt.Sprintf(",%s", logLine.Time)) csvLine.WriteString(fmt.Sprintf(",%s", logLine.Time))
@ -65,7 +65,7 @@ func buildCsv(fullLog []LogLine) (csvList []string) {
return csvList 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) { func csvDate(inputDate string) (outputDate string) {
const FLEdateFormat = "2006-01-02" const FLEdateFormat = "2006-01-02"
date, err := time.Parse(FLEdateFormat, inputDate) date, err := time.Parse(FLEdateFormat, inputDate)

@ -29,6 +29,7 @@ func SprintLogRecord(logLine LogLine) string {
output.WriteString("Operator " + logLine.Operator + "\n") output.WriteString("Operator " + logLine.Operator + "\n")
output.WriteString("MyWWFF " + logLine.MyWWFF + "\n") output.WriteString("MyWWFF " + logLine.MyWWFF + "\n")
output.WriteString("MySOTA " + logLine.MySOTA + "\n") output.WriteString("MySOTA " + logLine.MySOTA + "\n")
output.WriteString("MyPOTA " + logLine.MyPOTA + "\n")
output.WriteString("MyGrid " + logLine.MyGrid + "\n") output.WriteString("MyGrid " + logLine.MyGrid + "\n")
output.WriteString("QslMsg " + logLine.QslMsgFromHeader + "\n") output.WriteString("QslMsg " + logLine.QslMsgFromHeader + "\n")
output.WriteString("Nickname " + logLine.Nickname + "\n") output.WriteString("Nickname " + logLine.Nickname + "\n")
@ -70,6 +71,10 @@ func SprintHeaderValues(logLine LogLine) string {
output.WriteString("MySOTA " + logLine.MySOTA + "\n") output.WriteString("MySOTA " + logLine.MySOTA + "\n")
} }
if logLine.MyPOTA != "" {
output.WriteString("MyPOTA " + logLine.MyPOTA + "\n")
}
if logLine.MyGrid != "" { if logLine.MyGrid != "" {
output.WriteString("MyGrid " + logLine.MyGrid + "\n") output.WriteString("MyGrid " + logLine.MyGrid + "\n")
} }

@ -32,8 +32,8 @@ func TestSprintHeaderValues(t *testing.T) {
}{ }{
{ {
"Full Option", "Full Option",
args{logLine: LogLine{MyCall: "on4kjm/p", Operator: "on4kjm", MyWWFF: "wwff", MySOTA: "sota"}}, args{logLine: LogLine{MyCall: "on4kjm/p", Operator: "on4kjm", MyWWFF: "wwff", MySOTA: "sota", MyPOTA: "pota"}},
"MyCall on4kjm/p (on4kjm)\nMyWWFF wwff\nMySOTA sota\n", "MyCall on4kjm/p (on4kjm)\nMyWWFF wwff\nMySOTA sota\nMyPOTA pota\n",
}, },
{ {
"Minimal", "Minimal",
@ -70,6 +70,7 @@ func ExampleSprintLogRecord() {
Operator: "operator", Operator: "operator",
MyWWFF: "myWwff", MyWWFF: "myWwff",
MySOTA: "mySota", MySOTA: "mySota",
MyPOTA: "myPota",
MyGrid: "myGrid", MyGrid: "myGrid",
QslMsgFromHeader: "QslMsgFromHeader", QslMsgFromHeader: "QslMsgFromHeader",
Nickname: "nickname", Nickname: "nickname",
@ -99,6 +100,7 @@ func ExampleSprintLogRecord() {
//Operator operator //Operator operator
//MyWWFF myWwff //MyWWFF myWwff
//MySOTA mySota //MySOTA mySota
//MyPOTA myPota
//MyGrid myGrid //MyGrid myGrid
//QslMsg QslMsgFromHeader //QslMsg QslMsgFromHeader
//Nickname nickname //Nickname nickname

@ -64,7 +64,7 @@ func (tb *InferTimeBlock) finalizeTimeGap() error {
//Do we have a positive noTimeCount //Do we have a positive noTimeCount
if tb.noTimeCount < 1 { 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? //TODO: What should we expect as logFilePosition?
@ -76,15 +76,15 @@ func (tb *InferTimeBlock) finalizeTimeGap() error {
func (tb *InferTimeBlock) validateTimeGap() error { func (tb *InferTimeBlock) validateTimeGap() error {
//Check that lastRecordedTime and nextValidTime are not null //Check that lastRecordedTime and nextValidTime are not null
if tb.lastRecordedTime.IsZero() { if tb.lastRecordedTime.IsZero() {
return errors.New("Gap start time is empty") return errors.New("gap start time is empty")
} }
if tb.nextValidTime.IsZero() { 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 //Fail if we have a negative time difference
if tb.nextValidTime.Before(tb.lastRecordedTime) { 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 return nil
} }
@ -101,20 +101,20 @@ func (tb *InferTimeBlock) storeTimeGap(logline LogLine, position int) (bool, err
if tb.noTimeCount == 0 { if tb.noTimeCount == 0 {
//File is bad: date not found or badly formated //File is bad: date not found or badly formated
if logline.Date == "" { 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 { 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) os.Exit(1)
} }
tb.logFilePosition = position tb.logFilePosition = position
} else { } else {
// We reached the end of the gap // We reached the end of the gap
if tb.lastRecordedTime.IsZero() { 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 { 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) os.Exit(1)
} }
return true, nil return true, nil
@ -122,11 +122,11 @@ func (tb *InferTimeBlock) storeTimeGap(logline LogLine, position int) (bool, err
} else { } else {
//Check the data is correct. //Check the data is correct.
if tb.lastRecordedTime.IsZero() { if tb.lastRecordedTime.IsZero() {
err = errors.New("Gap start time is empty") err = errors.New("gap start time is empty")
//TODO: this smells //TODO: this smells
} }
if !tb.nextValidTime.IsZero() { if !tb.nextValidTime.IsZero() {
err = errors.New("Gap end time is not empty") err = errors.New("gap end time is not empty")
} }
tb.noTimeCount++ tb.noTimeCount++
} }

@ -123,7 +123,7 @@ func TestInferTimeBlock_computeGaps_invalidData(t *testing.T) {
if err == nil { if err == nil {
t.Error("Should have failed with an error") 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.") t.Error("Did not not fail with the expected error.")
} }
} }
@ -140,7 +140,7 @@ func TestInferTimeBlock_computeGaps_missingEnTime(t *testing.T) {
if err == nil { if err == nil {
t.Error("Should have failed with an error") 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) 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 { if err == nil {
t.Error("Should have failed with an error") 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) 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) isEndGap, err := tb.storeTimeGap(logLine, recordNbr)
// Then // Then
if err.Error() != "Gap start time is empty" { if err.Error() != "gap start time is empty" {
t.Errorf("Unexpected error: %s", err) t.Errorf("Unexpected error: %s", err)
} }
if isEndGap == true { if isEndGap == true {
@ -303,7 +303,7 @@ func TestInferTimeBlock_increment_alreadyDefinedNewTime(t *testing.T) {
isEndGap, err := tb.storeTimeGap(logLine, recordNbr) isEndGap, err := tb.storeTimeGap(logLine, recordNbr)
// Then // 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) t.Errorf("Unexpected error: %s", err)
} }
if isEndGap == true { if isEndGap == true {

@ -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 is set to true is something bad happened while storing time gaps.
isInferTimeFatalError := false isInferTimeFatalError := false
regexpLineComment := regexp.MustCompile("^[[:blank:]]*#") regexpLineComment := regexp.MustCompile(`^[[:blank:]]*#`)
regexpOnlySpaces := regexp.MustCompile("^\\s+$") regexpOnlySpaces := regexp.MustCompile(`^\s+$`)
regexpSingleMultiLineComment := regexp.MustCompile("^[[:blank:]]*{.+}$") regexpSingleMultiLineComment := regexp.MustCompile(`^[[:blank:]]*{.+}$`)
regexpStartMultiLineComment := regexp.MustCompile("^[[:blank:]]*{") regexpStartMultiLineComment := regexp.MustCompile(`^[[:blank:]]*{`)
regexpEndMultiLineComment := regexp.MustCompile("}$") regexpEndMultiLineComment := regexp.MustCompile(`}$`)
regexpHeaderMyCall := regexp.MustCompile("(?i)^mycall ") //FIXME: fields can delimited with space or TAB ("(?i)^mywwff\s+")
regexpHeaderOperator := regexp.MustCompile("(?i)^operator ") regexpHeaderMyCall := regexp.MustCompile(`(?i)^mycall\s+`)
regexpHeaderMyWwff := regexp.MustCompile("(?i)^mywwff ") regexpHeaderOperator := regexp.MustCompile(`(?i)^operator\s+`)
regexpHeaderMySota := regexp.MustCompile("(?i)^mysota ") regexpHeaderMyWwff := regexp.MustCompile(`(?i)^mywwff\s+`)
regexpHeaderMyGrid := regexp.MustCompile("(?i)^mygrid ") regexpHeaderMySota := regexp.MustCompile(`(?i)^mysota\s+`)
regexpHeaderQslMsg := regexp.MustCompile("(?i)^qslmsg ") regexpHeaderMyPota := regexp.MustCompile(`(?i)^mypota\s+`)
regexpHeaderNickname := regexp.MustCompile("(?i)^nickname ") regexpHeaderMyGrid := regexp.MustCompile(`(?i)^mygrid\s+`)
regexpHeaderQslMsg := regexp.MustCompile(`(?i)^qslmsg\s+`)
regexpHeaderNickname := regexp.MustCompile(`(?i)^nickname\s+`)
headerMyCall := "" headerMyCall := ""
headerOperator := "" headerOperator := ""
headerMyWWFF := "" headerMyWWFF := ""
headerMySOTA := "" headerMySOTA := ""
headerMyPOTA := ""
headerMyGrid := "" headerMyGrid := ""
headerQslMsg := "" headerQslMsg := ""
headerNickname := "" headerNickname := ""
@ -182,6 +185,26 @@ func LoadFile(inputFilename string, isInterpolateTime bool) (filleFullLog []LogL
continue 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 //My Sota
if regexpHeaderMySota.MatchString(eachline) { if regexpHeaderMySota.MatchString(eachline) {
//Attempt to redefine value //Attempt to redefine value
@ -257,6 +280,7 @@ func LoadFile(inputFilename string, isInterpolateTime bool) (filleFullLog []LogL
previousLogLine.MyCall = headerMyCall previousLogLine.MyCall = headerMyCall
previousLogLine.Operator = headerOperator previousLogLine.Operator = headerOperator
previousLogLine.MyWWFF = headerMyWWFF previousLogLine.MyWWFF = headerMyWWFF
previousLogLine.MyPOTA = headerMyPOTA
previousLogLine.MySOTA = headerMySOTA previousLogLine.MySOTA = headerMySOTA
previousLogLine.MyGrid = headerMyGrid previousLogLine.MyGrid = headerMyGrid
previousLogLine.QSLmsg = headerQslMsg //previousLogLine.QslMsg is redundant previousLogLine.QSLmsg = headerQslMsg //previousLogLine.QslMsg is redundant
@ -320,7 +344,7 @@ func LoadFile(inputFilename string, isInterpolateTime bool) (filleFullLog []LogL
if isInterpolateTime { if isInterpolateTime {
//Do we have an open timeBlok that has not been closed. //Do we have an open timeBlok that has not been closed.
if (wrkTimeBlock.noTimeCount > 0) && (wrkTimeBlock.nextValidTime.IsZero()) { 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 { } else {
for _, timeBlock := range missingTimeBlockList { for _, timeBlock := range missingTimeBlockList {
if err := timeBlock.validateTimeGap(); err != nil { if err := timeBlock.validateTimeGap(); err != nil {

@ -37,10 +37,11 @@ func TestLoadFile_happyCase(t *testing.T) {
dataArray = append(dataArray, " ") dataArray = append(dataArray, " ")
dataArray = append(dataArray, "# Header") dataArray = append(dataArray, "# Header")
dataArray = append(dataArray, "myCall on4kjm/p") 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, "nickname Portable")
dataArray = append(dataArray, "myWwff onff-0258") dataArray = append(dataArray, "myWwff\tonff-0258")
dataArray = append(dataArray, "mySota on/on-001") dataArray = append(dataArray, "mySota on/on-001")
dataArray = append(dataArray, "myPota k-0802")
dataArray = append(dataArray, "myGrid jo50") dataArray = append(dataArray, "myGrid jo50")
dataArray = append(dataArray, "QslMsg This is a QSL message") dataArray = append(dataArray, "QslMsg This is a QSL message")
dataArray = append(dataArray, " ") dataArray = append(dataArray, " ")

@ -27,7 +27,7 @@ func buildOutputFilename(output string, input string, overwrite bool, newExtensi
//validate that input is populated (should never happen if properly called) //validate that input is populated (should never happen if properly called)
if input == "" { 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 //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 //It exisits but is a directory
if info.IsDir() { 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 { if overwrite {
//user accepted to overwrite the file //user accepted to overwrite the file
return output, nil 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")
} }

@ -58,7 +58,7 @@ func Test_buildOutputFilename(t *testing.T) {
{ {
"input file not provided", "input file not provided",
args{input: "", output: "xxx", overwrite: false, extension: ".adi"}, 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", "Output file does not exist",
@ -68,12 +68,12 @@ func Test_buildOutputFilename(t *testing.T) {
{ {
"Output name is a directory", "Output name is a directory",
args{input: "a file", output: testDir, overwrite: false, extension: ".adi"}, 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", "Output exist but no overwrite",
args{input: "a file", output: testFile, overwrite: false, extension: ".adi"}, 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", "Output exist but user wants to overwrite",

@ -30,7 +30,10 @@ type LogLine struct {
MyCall string MyCall string
Operator string Operator string
MyWWFF string MyWWFF string
MyPOTA string
MySOTA string MySOTA string
MyPota string
MySota string
MyGrid string MyGrid string
QslMsgFromHeader string QslMsgFromHeader string
Nickname string Nickname string
@ -50,21 +53,23 @@ type LogLine struct {
RSTsent string RSTsent string
RSTrcvd string RSTrcvd string
WWFF string WWFF string
POTA string
SOTA string SOTA string
} }
var regexpIsFullTime = regexp.MustCompile("^[0-2]{1}[0-9]{3}$") var regexpIsFullTime = regexp.MustCompile(`^[0-2]{1}[0-9]{3}$`)
var regexpIsTimePart = regexp.MustCompile("^[0-5]{1}[0-9]{1}$|^[1-9]{1}$") var regexpIsTimePart = regexp.MustCompile(`^[0-5]{1}[0-9]{1}$|^[1-9]{1}$`)
var regexpIsOMname = regexp.MustCompile("^@") var regexpIsOMname = regexp.MustCompile(`^@`)
var regexpIsGridLoc = regexp.MustCompile("^#") var regexpIsGridLoc = regexp.MustCompile(`^#`)
var regexpIsRst = regexp.MustCompile("^[\\d]{1,3}$") var regexpIsRst = regexp.MustCompile(`^[\d]{1,3}$`)
var regexpIsFreq = regexp.MustCompile("^[\\d]+\\.[\\d]+$") var regexpIsFreq = regexp.MustCompile(`^[\d]+\.[\d]+$`)
var regexpIsSotaKeyWord = regexp.MustCompile("(?i)^sota$") var regexpIsSotaKeyWord = regexp.MustCompile(`(?i)^sota$`)
var regexpIsWwffKeyWord = regexp.MustCompile("(?i)^wwff$") var regexpIsWwffKeyWord = regexp.MustCompile(`(?i)^wwff$`)
var regexpDatePattern = regexp.MustCompile("^(\\d{2}|\\d{4})[-/ .]\\d{1,2}[-/ .]\\d{1,2}$") var regexpIsPotaKeyWord = regexp.MustCompile(`(?i)^pota$`)
var regexpIsDateKeyWord = regexp.MustCompile("(?i)^date$") var regexpDatePattern = regexp.MustCompile(`^(\d{2}|\d{4})[-/ .]\d{1,2}[-/ .]\d{1,2}$`)
var regexpDayIncrementPattern = regexp.MustCompile("^\\+*$") var regexpIsDateKeyWord = regexp.MustCompile(`(?i)^date$`)
var regexpIsDayKeyword = regexp.MustCompile("(?i)^day$") var regexpDayIncrementPattern = regexp.MustCompile(`^\+*$`)
var regexpIsDayKeyword = regexp.MustCompile(`(?i)^day$`)
// ParseLine cuts a FLE line into useful bits // ParseLine cuts a FLE line into useful bits
func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg string) { 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.RSTsent = ""
previousLine.RSTrcvd = "" previousLine.RSTrcvd = ""
previousLine.SOTA = "" previousLine.SOTA = ""
previousLine.POTA = ""
previousLine.WWFF = "" previousLine.WWFF = ""
previousLine.OMname = "" previousLine.OMname = ""
previousLine.GridLoc = "" previousLine.GridLoc = ""
@ -156,7 +162,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg
increment := len(element) increment := len(element)
newDate, dateError := IncrementDate(logLine.Date, increment) newDate, dateError := IncrementDate(logLine.Date, increment)
if dateError != "" { if dateError != "" {
errorMsg = errorMsg + fmt.Sprintf(dateError) errorMsg = errorMsg + dateError
} }
logLine.Date = newDate logLine.Date = newDate
continue continue
@ -177,6 +183,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg
qrg, _ = strconv.ParseFloat(element, 32) qrg, _ = strconv.ParseFloat(element, 32)
if (logLine.BandLowerLimit != 0.0) && (logLine.BandUpperLimit != 0.0) { if (logLine.BandLowerLimit != 0.0) && (logLine.BandUpperLimit != 0.0) {
if (qrg >= logLine.BandLowerLimit) && (qrg <= logLine.BandUpperLimit) { if (qrg >= logLine.BandLowerLimit) && (qrg <= logLine.BandUpperLimit) {
//TODO: print 3f or more is available
logLine.Frequency = fmt.Sprintf("%.3f", qrg) logLine.Frequency = fmt.Sprintf("%.3f", qrg)
} else { } else {
logLine.Frequency = "" logLine.Frequency = ""
@ -202,7 +209,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg
} }
// Is it a "full" time ? // Is it a "full" time ?
if isRightOfCall == false { if !isRightOfCall {
if regexpIsFullTime.MatchString(element) { if regexpIsFullTime.MatchString(element) {
logLine.Time = element logLine.Time = element
logLine.ActualTime = element logLine.ActualTime = element
@ -289,6 +296,19 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg
continue 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 the "sota" keyword is used, skip it
if regexpIsSotaKeyWord.MatchString(element) { if regexpIsSotaKeyWord.MatchString(element) {
// this keyword is not requiered anymore with FLE 3 and doesn't add any value // this keyword is not requiered anymore with FLE 3 and doesn't add any value

@ -158,6 +158,16 @@ func TestParseLine(t *testing.T) {
args{inputStr: "1230 oe6cud/p onff-0258", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}}, 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"}, "", 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", "date processing",
args{inputStr: "20.09.7 1230 oe6cud/p onff-0258", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}}, args{inputStr: "20.09.7 1230 oe6cud/p onff-0258", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}},

@ -51,6 +51,20 @@ func ValidateWwff(inputStr string) (ref, errorMsg string) {
return wrongInputStr, errorMsg 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})?$") 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 // ValidateGridLocator verifies that the supplied is a valid Maidenhead locator reference

@ -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) { func TestValidateSota(t *testing.T) {
type args struct { type args struct {
inputStr string inputStr string

@ -22,7 +22,7 @@ import (
"testing" "testing"
) )
const writeFileTestDir string = "test2_dir" const WriteFileTestDir string = "test2_dir"
const writeFileTestFname string = "testFile.txt" const writeFileTestFname string = "testFile.txt"
func Test_writeFile(t *testing.T) { func Test_writeFile(t *testing.T) {

@ -3,7 +3,7 @@ module FLEcli
go 1.14 go 1.14
require ( 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/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/pelletier/go-toml v1.8.0 // 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/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 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 gopkg.in/ini.v1 v1.60.2 // indirect
) )

@ -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.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 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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/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-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= 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/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/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.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.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 h1:hz3epLdx+cim8EN9XRt6pqAHxwWVW0D87Xm3mUbvKvI=
go.hein.dev/go-version v0.1.0/go.mod h1:WOEm7DWMroRe5GdUgHMvx+Pji5WWIpMuXmK/3foylXs= 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-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 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-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-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/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.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.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.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.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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-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-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-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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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-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-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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-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 h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= 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.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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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-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/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= 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-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 h1:5SfEmaQTww9A35eeANMuoDMDbba7pCPVplPWQ72i5lY=
golang.org/x/tools v0.0.0-20200902171120-36b1a880d5d1/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -23,6 +23,12 @@
diff test/output/temp/sota_wwff.csv test/FLE-sample/sota_wwff.csv --strip-trailing-cr 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!" { @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 run test/docker-FLEcli.sh csv -o -i test/data/fle-4-no-qso.txt
[ "$status" -eq 1 ] [ "$status" -eq 1 ]

@ -4,6 +4,7 @@ operator on4kjm
nickname Portable nickname Portable
myWwff onff-0258 myWwff onff-0258
mySota on/on-001 mySota on/on-001
myPota on-0001
QslMsg This is a QSL message QslMsg This is a QSL message
date 2020-05-23 date 2020-05-23

@ -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 <PSE QSL Direct>
2m fm
1227 gw4gte <Dave>
8 gw0tlk/m
date 2016-06-03
40m cw
1404 gm0aaa 3 7
5 on4ck/p 2
7 dl0dan/p
20m
10 yu7ls

@ -0,0 +1,12 @@
ADIF Export for Fast Log Entry by DF3CB
<PROGRAMID:3>FLE
<ADIF_VER:5>3.1.0
<EOH>
<STATION_CALLSIGN:5>G3WGV <CALL:5>G4ELZ <QSO_DATE:8>20160424 <TIME_ON:4>1202 <BAND:3>40m <MODE:2>CW <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
<STATION_CALLSIGN:5>G3WGV <CALL:5>G3NOH <QSO_DATE:8>20160424 <TIME_ON:4>1204 <BAND:3>40m <MODE:2>CW <RST_SENT:3>599 <RST_RCVD:3>599 <COMMENT:14>PSE QSL Direct <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
<STATION_CALLSIGN:5>G3WGV <CALL:6>GW4GTE <QSO_DATE:8>20160424 <TIME_ON:4>1227 <BAND:2>2m <MODE:2>FM <RST_SENT:2>59 <RST_RCVD:2>59 <COMMENT:4>Dave <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
<STATION_CALLSIGN:5>G3WGV <CALL:8>GW0TLK/M <QSO_DATE:8>20160424 <TIME_ON:4>1228 <BAND:2>2m <MODE:2>FM <RST_SENT:2>59 <RST_RCVD:2>59 <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
<STATION_CALLSIGN:5>G3WGV <CALL:6>GM0AAA <QSO_DATE:8>20160603 <TIME_ON:4>1404 <BAND:3>40m <MODE:2>CW <RST_SENT:3>539 <RST_RCVD:3>579 <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
<STATION_CALLSIGN:5>G3WGV <CALL:7>ON4CK/P <QSO_DATE:8>20160603 <TIME_ON:4>1405 <BAND:3>40m <MODE:2>CW <RST_SENT:3>529 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
<STATION_CALLSIGN:5>G3WGV <CALL:8>DL0DAN/P <QSO_DATE:8>20160603 <TIME_ON:4>1407 <BAND:3>40m <MODE:2>CW <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
<STATION_CALLSIGN:5>G3WGV <CALL:5>YU7LS <QSO_DATE:8>20160603 <TIME_ON:4>1410 <BAND:3>20m <MODE:2>CW <RST_SENT:3>599 <RST_RCVD:3>599 <MY_SIG:4>POTA <MY_SIG_INFO:6>G-0014 <OPERATOR:5>G3WGV <EOR>
Loading…
Cancel
Save