mirror of
https://github.com/on4kjm/FLEcli.git
synced 2025-01-18 13:01:09 +01:00
Pota support (#73)
* Add POTA processing * add end to end POTA test * allow tabs to be a valid separator in the header section
This commit is contained in:
parent
82ee44de9b
commit
61c42d1c75
31 changed files with 530 additions and 194 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -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 }}"
|
||||
|
|
2
Makefile
2
Makefile
|
@ -13,7 +13,7 @@ dep: ## Get the dependencies
|
|||
@go mod download
|
||||
|
||||
lint: ## Lint Golang files
|
||||
@go get -u golang.org/x/lint/golint
|
||||
@go install golang.org/x/lint/golint
|
||||
@golint -set_exit_status ./...
|
||||
|
||||
vet: ## Run go vet
|
||||
|
|
|
@ -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 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"}},
|
||||
"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 MY-SOTA reference"),
|
||||
fmt.Errorf("missing MyCall"),
|
||||
},
|
||||
{
|
||||
"Missing MySota",
|
||||
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-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
go.mod
3
go.mod
|
@ -3,7 +3,7 @@ module FLEcli
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||
|
@ -13,6 +13,5 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.7.1
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a // indirect
|
||||
gopkg.in/ini.v1 v1.60.2 // indirect
|
||||
)
|
||||
|
|
19
go.sum
19
go.sum
|
@ -45,6 +45,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
|||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
|
@ -211,6 +213,7 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
|||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.hein.dev/go-version v0.1.0 h1:hz3epLdx+cim8EN9XRt6pqAHxwWVW0D87Xm3mUbvKvI=
|
||||
go.hein.dev/go-version v0.1.0/go.mod h1:WOEm7DWMroRe5GdUgHMvx+Pji5WWIpMuXmK/3foylXs=
|
||||
|
@ -246,12 +249,15 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA
|
|||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -269,6 +275,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -278,6 +285,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -300,6 +308,12 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zr
|
|||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -307,6 +321,9 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -332,6 +349,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200902171120-36b1a880d5d1 h1:5SfEmaQTww9A35eeANMuoDMDbba7pCPVplPWQ72i5lY=
|
||||
golang.org/x/tools v0.0.0-20200902171120-36b1a880d5d1/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
22
test/data/sample_pota.txt
Normal file
22
test/data/sample_pota.txt
Normal file
|
@ -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
|
12
test/output/POTA/sample_pota.adif
Normal file
12
test/output/POTA/sample_pota.adif
Normal file
|
@ -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…
Reference in a new issue