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/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

@ -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:

@ -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 }}"

@ -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

@ -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")
}

@ -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))
}

@ -1,31 +1,14 @@
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 (
"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)
}
})
}

@ -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, "<PROGRAMID:3>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))

@ -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>",
}
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 {
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)
}
})

@ -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))
}

@ -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",

@ -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)

@ -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")
}

@ -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

@ -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++
}

@ -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 {

@ -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 {

@ -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, " ")

@ -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")
}

@ -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",

@ -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

@ -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"}},

@ -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

@ -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

@ -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) {

@ -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
)

@ -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=

@ -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 ]

@ -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

@ -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