mirror of
https://github.com/on4kjm/FLEcli.git
synced 2025-01-18 13:01:09 +01:00
Add the time interpolating feature
This commit is contained in:
parent
1077b7254f
commit
6172f13193
13 changed files with 638 additions and 135 deletions
|
@ -45,8 +45,8 @@ func buildAdif(fullLog []LogLine) (adifList []string) {
|
|||
for _, logLine := range fullLog {
|
||||
adifLine := ""
|
||||
adifLine = adifLine + adifElement("STATION_CALLSIGN", logLine.MyCall)
|
||||
adifLine = adifLine + adifElement("CALL", logLine.Call)
|
||||
adifLine = adifLine + adifElement("QSO_DATE", adifDate(logLine.Date))
|
||||
adifLine = adifLine + adifElement("CALL", logLine.Call)
|
||||
adifLine = adifLine + adifElement("QSO_DATE", adifDate(logLine.Date))
|
||||
adifLine = adifLine + adifElement("TIME_ON", logLine.Time)
|
||||
adifLine = adifLine + adifElement("BAND", logLine.Band)
|
||||
adifLine = adifLine + adifElement("MODE", logLine.Mode)
|
||||
|
|
111
cmd/display.go
111
cmd/display.go
|
@ -1,111 +0,0 @@
|
|||
package cmd
|
||||
|
||||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// SprintLogRecord outputs the content of a logline
|
||||
func SprintLogRecord(logLine LogLine) (output string) {
|
||||
output = ""
|
||||
output = output + "Date " + logLine.Date + "\n"
|
||||
output = output + "MyCall " + logLine.MyCall + "\n"
|
||||
output = output + "Operator " + logLine.Operator + "\n"
|
||||
output = output + "MyWWFF " + logLine.MyWWFF + "\n"
|
||||
output = output + "MySOTA " + logLine.MySOTA + "\n"
|
||||
output = output + "QslMsg " + logLine.QslMsg + "\n"
|
||||
output = output + "Nickname " + logLine.Nickname + "\n"
|
||||
output = output + "Mode " + logLine.Mode + "\n"
|
||||
output = output + "ModeType " + logLine.ModeType + "\n"
|
||||
output = output + "Band " + logLine.Band + "\n"
|
||||
output = output + " Lower " + fmt.Sprintf("%f", logLine.BandLowerLimit) + "\n"
|
||||
output = output + " Upper " + fmt.Sprintf("%f", logLine.BandUpperLimit) + "\n"
|
||||
output = output + "Frequency " + logLine.Frequency + "\n"
|
||||
output = output + "Time " + logLine.Time + "\n"
|
||||
output = output + "Call " + logLine.Call + "\n"
|
||||
output = output + "Comment " + logLine.Comment + "\n"
|
||||
output = output + "QSLmsg " + logLine.QSLmsg + "\n"
|
||||
output = output + "OMname " + logLine.OMname + "\n"
|
||||
output = output + "GridLoc " + logLine.GridLoc + "\n"
|
||||
output = output + "RSTsent " + logLine.RSTsent + "\n"
|
||||
output = output + "RSTrcvd " + logLine.RSTrcvd + "\n"
|
||||
output = output + "SOTA " + logLine.SOTA + "\n"
|
||||
output = output + "WWFF " + logLine.WWFF + "\n"
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// SprintHeaderValues displays the header values
|
||||
func SprintHeaderValues(logLine LogLine) (output string) {
|
||||
output = ""
|
||||
|
||||
output = output + "MyCall " + logLine.MyCall
|
||||
if logLine.Operator != "" {
|
||||
output = output + " (" + logLine.Operator + ")"
|
||||
}
|
||||
output = output + "\n"
|
||||
|
||||
if logLine.MyWWFF != "" {
|
||||
output = output + "MyWWFF " + logLine.MyWWFF + "\n"
|
||||
}
|
||||
|
||||
if logLine.MySOTA != "" {
|
||||
output = output + "MySOTA " + logLine.MySOTA + "\n"
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Date, Time, band, mode, call, report sent, report rcvd, Notes
|
||||
var logLineFormat = "%-10s %-4s %-4s %-4s %-10s %-4s %-4s %s \n"
|
||||
|
||||
// SprintColumnTitles displays the column titles for a log line
|
||||
func SprintColumnTitles(logLine LogLine) (output string) {
|
||||
output = fmt.Sprintf(logLineFormat, "Date", "Time", "Band", "Mode", "Call", "Sent", "Rcvd", "Notes")
|
||||
output = output + fmt.Sprintf(logLineFormat, "----", "----", "----", "----", "----", "----", "----", "----")
|
||||
return output
|
||||
}
|
||||
|
||||
// SprintLogInColumn displays the logLine in column mode
|
||||
func SprintLogInColumn(logLine LogLine) (output string) {
|
||||
notes := ""
|
||||
if logLine.Frequency != "" {
|
||||
notes = notes + "QRG: " + logLine.Frequency + " "
|
||||
}
|
||||
if logLine.Comment != "" {
|
||||
notes = notes + "[" + logLine.Comment + "] "
|
||||
}
|
||||
if logLine.QSLmsg != "" {
|
||||
notes = notes + "[" + logLine.QSLmsg + "] "
|
||||
}
|
||||
if logLine.OMname != "" {
|
||||
notes = notes + logLine.OMname + " "
|
||||
}
|
||||
if logLine.GridLoc != "" {
|
||||
notes = notes + logLine.GridLoc + " "
|
||||
}
|
||||
if logLine.WWFF != "" {
|
||||
notes = notes + logLine.WWFF + " "
|
||||
}
|
||||
if logLine.SOTA != "" {
|
||||
notes = notes + logLine.SOTA + " "
|
||||
}
|
||||
|
||||
output = fmt.Sprintf(logLineFormat, logLine.Date, logLine.Time, logLine.Band, logLine.Mode, logLine.Call, logLine.RSTsent, logLine.RSTrcvd, notes)
|
||||
|
||||
return output
|
||||
}
|
114
cmd/displayLog.go
Normal file
114
cmd/displayLog.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package cmd
|
||||
|
||||
/*
|
||||
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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SprintLogRecord outputs the content of a logline
|
||||
func SprintLogRecord(logLine LogLine) string {
|
||||
var output strings.Builder
|
||||
|
||||
output.WriteString("Date " + logLine.Date + "\n")
|
||||
output.WriteString("MyCall " + logLine.MyCall + "\n")
|
||||
output.WriteString("Operator " + logLine.Operator + "\n")
|
||||
output.WriteString("MyWWFF " + logLine.MyWWFF + "\n")
|
||||
output.WriteString("MySOTA " + logLine.MySOTA + "\n")
|
||||
output.WriteString("QslMsg " + logLine.QslMsg + "\n")
|
||||
output.WriteString("Nickname " + logLine.Nickname + "\n")
|
||||
output.WriteString("Mode " + logLine.Mode + "\n")
|
||||
output.WriteString("ModeType " + logLine.ModeType + "\n")
|
||||
output.WriteString("Band " + logLine.Band + "\n")
|
||||
output.WriteString(" Lower " + fmt.Sprintf("%f", logLine.BandLowerLimit) + "\n")
|
||||
output.WriteString(" Upper " + fmt.Sprintf("%f", logLine.BandUpperLimit) + "\n")
|
||||
output.WriteString("Frequency " + logLine.Frequency + "\n")
|
||||
output.WriteString("Time " + logLine.Time + "\n")
|
||||
output.WriteString("Call " + logLine.Call + "\n")
|
||||
output.WriteString("Comment " + logLine.Comment + "\n")
|
||||
output.WriteString("QSLmsg " + logLine.QSLmsg + "\n")
|
||||
output.WriteString("OMname " + logLine.OMname + "\n")
|
||||
output.WriteString("GridLoc " + logLine.GridLoc + "\n")
|
||||
output.WriteString("RSTsent " + logLine.RSTsent + "\n")
|
||||
output.WriteString("RSTrcvd " + logLine.RSTrcvd + "\n")
|
||||
output.WriteString("SOTA " + logLine.SOTA + "\n")
|
||||
output.WriteString("WWFF " + logLine.WWFF + "\n")
|
||||
|
||||
return output.String()
|
||||
}
|
||||
|
||||
// SprintHeaderValues displays the header values
|
||||
func SprintHeaderValues(logLine LogLine) string {
|
||||
var output strings.Builder
|
||||
|
||||
output.WriteString("MyCall " + logLine.MyCall)
|
||||
if logLine.Operator != "" {
|
||||
output.WriteString(" (" + logLine.Operator + ")")
|
||||
}
|
||||
output.WriteString("\n")
|
||||
|
||||
if logLine.MyWWFF != "" {
|
||||
output.WriteString("MyWWFF " + logLine.MyWWFF + "\n")
|
||||
}
|
||||
|
||||
if logLine.MySOTA != "" {
|
||||
output.WriteString("MySOTA " + logLine.MySOTA + "\n")
|
||||
}
|
||||
|
||||
return output.String()
|
||||
}
|
||||
|
||||
// Date, Time, band, mode, call, report sent, report rcvd, Notes
|
||||
var logLineFormat = "%-10s %-4s %-4s %-4s %-10s %-4s %-4s %s \n"
|
||||
|
||||
// SprintColumnTitles displays the column titles for a log line
|
||||
func SprintColumnTitles(logLine LogLine) string {
|
||||
var output strings.Builder
|
||||
output.WriteString(fmt.Sprintf(logLineFormat, "Date", "Time", "Band", "Mode", "Call", "Sent", "Rcvd", "Notes"))
|
||||
output.WriteString(fmt.Sprintf(logLineFormat, "----", "----", "----", "----", "----", "----", "----", "----"))
|
||||
return output.String()
|
||||
}
|
||||
|
||||
// SprintLogInColumn displays the logLine in column mode
|
||||
func SprintLogInColumn(logLine LogLine) (output string) {
|
||||
var notes strings.Builder
|
||||
if logLine.Frequency != "" {
|
||||
notes.WriteString("QRG: " + logLine.Frequency + " ")
|
||||
}
|
||||
if logLine.Comment != "" {
|
||||
notes.WriteString("[" + logLine.Comment + "] ")
|
||||
}
|
||||
if logLine.QSLmsg != "" {
|
||||
notes.WriteString("[" + logLine.QSLmsg + "] ")
|
||||
}
|
||||
if logLine.OMname != "" {
|
||||
notes.WriteString(logLine.OMname + " ")
|
||||
}
|
||||
if logLine.GridLoc != "" {
|
||||
notes.WriteString(logLine.GridLoc + " ")
|
||||
}
|
||||
if logLine.WWFF != "" {
|
||||
notes.WriteString(logLine.WWFF + " ")
|
||||
}
|
||||
if logLine.SOTA != "" {
|
||||
notes.WriteString(logLine.SOTA + " ")
|
||||
}
|
||||
|
||||
output = fmt.Sprintf(logLineFormat, logLine.Date, logLine.Time, logLine.Band, logLine.Mode, logLine.Call, logLine.RSTsent, logLine.RSTrcvd, notes.String())
|
||||
|
||||
return output
|
||||
}
|
127
cmd/inferTime.go
Normal file
127
cmd/inferTime.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
//InferTimeBlock contains the information describing a time gap
|
||||
type InferTimeBlock struct {
|
||||
lastRecordedTime time.Time
|
||||
nextValidTime time.Time
|
||||
//Number of records without actual time
|
||||
noTimeCount int
|
||||
//Position in file of the first log entry with missing date
|
||||
logFilePosition int
|
||||
//Computed time interval
|
||||
deltatime time.Duration
|
||||
}
|
||||
|
||||
//ADIFdateTimeFormat describes the ADIF date & time parsing and displaying format pattern
|
||||
const ADIFdateTimeFormat = "2006-01-02 1504"
|
||||
|
||||
//displayTimeGapInfo will print the details stored in an InferTimeBlock
|
||||
func (tb *InferTimeBlock) String() string {
|
||||
var buffer strings.Builder
|
||||
buffer.WriteString(fmt.Sprintf("Last Recorded Time: %s\n", tb.lastRecordedTime.Format(ADIFdateTimeFormat)))
|
||||
buffer.WriteString(fmt.Sprintf("next Recorded Time: %s\n", tb.nextValidTime.Format(ADIFdateTimeFormat)))
|
||||
buffer.WriteString(fmt.Sprintf("Log position of last recorded time: %d\n", tb.logFilePosition))
|
||||
buffer.WriteString(fmt.Sprintf("Nbr of entries without time: %d\n", tb.noTimeCount))
|
||||
buffer.WriteString(fmt.Sprintf("Computed interval: %ds\n", int(tb.deltatime.Seconds())))
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
//finalizeTimeGap makes the necessary checks and computation
|
||||
func (tb *InferTimeBlock) finalizeTimeGap() error {
|
||||
|
||||
//Check that lastRecordedTime and nextValidTime are not null
|
||||
if tb.lastRecordedTime.IsZero() {
|
||||
return errors.New("Gap start time is empty")
|
||||
}
|
||||
if tb.nextValidTime.IsZero() {
|
||||
return errors.New("Gap end time is empty")
|
||||
}
|
||||
|
||||
//Are the two times equal?
|
||||
if tb.nextValidTime == tb.lastRecordedTime {
|
||||
return errors.New("The start and end gap times are equal")
|
||||
}
|
||||
|
||||
//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")
|
||||
}
|
||||
|
||||
//Compute the gap
|
||||
diff := tb.nextValidTime.Sub(tb.lastRecordedTime)
|
||||
tb.deltatime = time.Duration(diff / time.Duration(tb.noTimeCount+1))
|
||||
|
||||
//Do we have a positive noTimeCount
|
||||
if tb.noTimeCount < 1 {
|
||||
return fmt.Errorf("Invalid number of records without time (%d)", tb.noTimeCount)
|
||||
}
|
||||
|
||||
//TODO: What should we expect as logFilePosition?
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//storeTimeGap updates an InferTimeBLock (last valid time, nbr of records without time). It returns true if we reached the end of the time gap.
|
||||
func (tb *InferTimeBlock) storeTimeGap(logline LogLine, position int) (bool, error) {
|
||||
var err error
|
||||
|
||||
//TODO: try to return fast and/or simpllify
|
||||
|
||||
//ActualTime is filled if a time could be found in the FLE input
|
||||
if logline.ActualTime != "" {
|
||||
//Are we starting a new block
|
||||
if tb.noTimeCount == 0 {
|
||||
if tb.lastRecordedTime, err = time.Parse(ADIFdateTimeFormat, logline.Date+" "+logline.ActualTime); err != nil {
|
||||
log.Println("Fatal error during internal date concersion: ", 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")
|
||||
}
|
||||
if tb.nextValidTime, err = time.Parse(ADIFdateTimeFormat, logline.Date+" "+logline.ActualTime); err != nil {
|
||||
log.Println("Fatal error during internal date concersion: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
} else {
|
||||
//Check the data is correct.
|
||||
if tb.lastRecordedTime.IsZero() {
|
||||
err = errors.New("Gap start time is empty")
|
||||
//TODO: this smells
|
||||
}
|
||||
if !tb.nextValidTime.IsZero() {
|
||||
err = errors.New("Gap end time is not empty")
|
||||
}
|
||||
tb.noTimeCount++
|
||||
}
|
||||
return false, err
|
||||
}
|
298
cmd/inferTime_test.go
Normal file
298
cmd/inferTime_test.go
Normal file
|
@ -0,0 +1,298 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInferTimeBlock_full_happyCase(t *testing.T) {
|
||||
//Given
|
||||
recordNumber := 4
|
||||
|
||||
logLine1 := LogLine{}
|
||||
logLine1.Date = "2020-05-24"
|
||||
logLine1.Time = "1401"
|
||||
logLine1.ActualTime = "1401"
|
||||
|
||||
logLine2 := LogLine{}
|
||||
logLine2.Date = "2020-05-24"
|
||||
logLine2.Time = "1401"
|
||||
|
||||
logLine3 := LogLine{}
|
||||
logLine3.Date = "2020-05-24"
|
||||
logLine3.Time = "1410"
|
||||
logLine3.ActualTime = "1410"
|
||||
|
||||
//When
|
||||
tb := InferTimeBlock{}
|
||||
isEndGap, err := tb.storeTimeGap(logLine1, recordNumber)
|
||||
if isEndGap == true || err != nil {
|
||||
t.Error("Unexpected results processing logline 1")
|
||||
}
|
||||
|
||||
isEndGap, err = tb.storeTimeGap(logLine2, recordNumber+1)
|
||||
if isEndGap == true || err != nil {
|
||||
t.Error("Unexpected results processing logline 2")
|
||||
}
|
||||
|
||||
isEndGap, err = tb.storeTimeGap(logLine3, recordNumber+2)
|
||||
if isEndGap == false || err != nil {
|
||||
t.Error("Unexpected results processing logline 3")
|
||||
}
|
||||
|
||||
err = tb.finalizeTimeGap()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error finalizing the timeGap")
|
||||
}
|
||||
|
||||
fmt.Println(tb.String())
|
||||
|
||||
//Then
|
||||
expectedCount := 1
|
||||
if tb.noTimeCount != expectedCount {
|
||||
t.Errorf("Unexpected number of missing records: %d, expected %d", tb.noTimeCount, expectedCount)
|
||||
}
|
||||
|
||||
expectedInterval := time.Duration(time.Second * 270)
|
||||
if tb.deltatime != expectedInterval {
|
||||
t.Errorf("Unexpected interval: %d, expected %d", tb.deltatime, expectedInterval)
|
||||
}
|
||||
|
||||
expectedLastRecordedTime := time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
if tb.lastRecordedTime != expectedLastRecordedTime {
|
||||
t.Errorf("Unexpected last recorded time: %s, expected %s", tb.lastRecordedTime, expectedLastRecordedTime)
|
||||
}
|
||||
|
||||
expectedNextValidTime := time.Date(2020, time.May, 24, 14, 10, 0, 0, time.UTC)
|
||||
if tb.nextValidTime != expectedNextValidTime {
|
||||
t.Errorf("Unexpected last recorded time: %s, expected %s", tb.nextValidTime, expectedNextValidTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_display_happyCase(t *testing.T) {
|
||||
//Given
|
||||
tb := InferTimeBlock{}
|
||||
tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
tb.nextValidTime = time.Date(2020, time.May, 24, 14, 10, 10, 0, time.UTC)
|
||||
tb.noTimeCount = 1
|
||||
|
||||
//When
|
||||
buffer1 := tb.String()
|
||||
|
||||
tb.finalizeTimeGap()
|
||||
|
||||
buffer2 := tb.String()
|
||||
|
||||
//Then
|
||||
expectedBuffer1 := "Last Recorded Time: 2020-05-24 1401\nnext Recorded Time: 2020-05-24 1410\nLog position of last recorded time: 0\nNbr of entries without time: 1\nComputed interval: 0s\n"
|
||||
expectedBuffer2 := "Last Recorded Time: 2020-05-24 1401\nnext Recorded Time: 2020-05-24 1410\nLog position of last recorded time: 0\nNbr of entries without time: 1\nComputed interval: 275s\n"
|
||||
|
||||
if buffer1 != expectedBuffer1 {
|
||||
t.Errorf("Not the expected display: got: \n%s\n while expecting: \n%s\n", buffer1, expectedBuffer1)
|
||||
}
|
||||
if buffer2 != expectedBuffer2 {
|
||||
t.Errorf("Not the expected finalized display: got: \n%s\n while expecting: \n%s\n", buffer2, expectedBuffer2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_computeGaps_invalidData(t *testing.T) {
|
||||
//Given
|
||||
tb := InferTimeBlock{}
|
||||
|
||||
//When
|
||||
err := tb.finalizeTimeGap()
|
||||
|
||||
//Then
|
||||
if err == nil {
|
||||
t.Error("Should have failed with an error")
|
||||
}
|
||||
if err.Error() != "Gap start time is empty" {
|
||||
t.Error("Did not not fail with the expected error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_computeGaps_missingEnTime(t *testing.T) {
|
||||
//Given
|
||||
tb := InferTimeBlock{}
|
||||
tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
|
||||
//When
|
||||
err := tb.finalizeTimeGap()
|
||||
|
||||
//Then
|
||||
if err == nil {
|
||||
t.Error("Should have failed with an error")
|
||||
}
|
||||
if err.Error() != "Gap end time is empty" {
|
||||
t.Errorf("Did not not fail with the expected error. Failed with %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_computeGaps_negativeDifference(t *testing.T) {
|
||||
//Given
|
||||
tb := InferTimeBlock{}
|
||||
tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 10, 0, 0, time.UTC)
|
||||
tb.nextValidTime = time.Date(2020, time.May, 24, 14, 01, 10, 0, time.UTC)
|
||||
|
||||
//When
|
||||
err := tb.finalizeTimeGap()
|
||||
|
||||
//Then
|
||||
if err == nil {
|
||||
t.Error("Should have failed with an error")
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_computeGaps_noDifference(t *testing.T) {
|
||||
//Given
|
||||
tb := InferTimeBlock{}
|
||||
tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 00, 0, 0, time.UTC)
|
||||
tb.nextValidTime = time.Date(2020, time.May, 24, 14, 00, 00, 0, time.UTC)
|
||||
|
||||
//When
|
||||
err := tb.finalizeTimeGap()
|
||||
|
||||
//Then
|
||||
if err == nil {
|
||||
t.Error("Should have failed with an error")
|
||||
}
|
||||
if err.Error() != "The start and end gap times are equal" {
|
||||
t.Errorf("Did not not fail with the expected error. Failed with %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_computeGaps_happyCase(t *testing.T) {
|
||||
//Given
|
||||
tb := InferTimeBlock{}
|
||||
tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
tb.nextValidTime = time.Date(2020, time.May, 24, 14, 10, 10, 0, time.UTC)
|
||||
tb.noTimeCount = 1
|
||||
|
||||
//When
|
||||
err := tb.finalizeTimeGap()
|
||||
|
||||
//Then
|
||||
if err != nil {
|
||||
t.Error("Should not have failed")
|
||||
}
|
||||
|
||||
//TODO: add some other validation
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_startsNewBlock(t *testing.T) {
|
||||
// Given
|
||||
logLine := LogLine{}
|
||||
logLine.Date = "2020-05-24"
|
||||
logLine.Time = "1401"
|
||||
logLine.ActualTime = "1401"
|
||||
|
||||
recordNbr := 4
|
||||
|
||||
tb := InferTimeBlock{}
|
||||
|
||||
// When
|
||||
isEndGap, err := tb.storeTimeGap(logLine, recordNbr)
|
||||
|
||||
// Then
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
if isEndGap == true {
|
||||
t.Errorf("Result is true while expectig false")
|
||||
}
|
||||
if tb.lastRecordedTime != time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC) {
|
||||
t.Errorf("Not the expected lastRecordedTime")
|
||||
}
|
||||
if tb.noTimeCount != 0 {
|
||||
t.Errorf("nTimeCount should be 0, but is %d", tb.noTimeCount)
|
||||
}
|
||||
if tb.logFilePosition != recordNbr {
|
||||
t.Errorf("logFilePosition not set correctly: is %d while expecting %d", tb.logFilePosition, recordNbr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_incrementCounter(t *testing.T) {
|
||||
// Given
|
||||
logLine := LogLine{}
|
||||
logLine.Date = "2020-05-24"
|
||||
logLine.Time = "1401"
|
||||
|
||||
recordNbr := 4
|
||||
|
||||
tb := InferTimeBlock{}
|
||||
tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
tb.logFilePosition = recordNbr
|
||||
|
||||
// When
|
||||
isEndGap, err := tb.storeTimeGap(logLine, recordNbr)
|
||||
|
||||
// Then
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
if isEndGap == true {
|
||||
t.Errorf("Result is true while expectig false")
|
||||
}
|
||||
if tb.lastRecordedTime != time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC) {
|
||||
t.Errorf("Not the expected lastRecordedTime")
|
||||
}
|
||||
if tb.noTimeCount != 1 {
|
||||
t.Errorf("nTimeCount should be 1, but is %d", tb.noTimeCount)
|
||||
}
|
||||
if tb.logFilePosition != recordNbr {
|
||||
t.Errorf("logFilePosition not set correctly: is %d while expecting %d", tb.logFilePosition, recordNbr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_increment_missingLastTime(t *testing.T) {
|
||||
// Given
|
||||
logLine := LogLine{}
|
||||
logLine.Date = "2020-05-24"
|
||||
logLine.Time = "1401"
|
||||
|
||||
recordNbr := 4
|
||||
|
||||
tb := InferTimeBlock{}
|
||||
//tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
tb.logFilePosition = recordNbr
|
||||
|
||||
// When
|
||||
isEndGap, err := tb.storeTimeGap(logLine, recordNbr)
|
||||
|
||||
// Then
|
||||
if err.Error() != "Gap start time is empty" {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
if isEndGap == true {
|
||||
t.Errorf("Result is true while expectig false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTimeBlock_increment_alreadyDefinedNewTime(t *testing.T) {
|
||||
// Given
|
||||
logLine := LogLine{}
|
||||
logLine.Date = "2020-05-24"
|
||||
logLine.Time = "1401"
|
||||
|
||||
recordNbr := 4
|
||||
|
||||
tb := InferTimeBlock{}
|
||||
tb.lastRecordedTime = time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
tb.nextValidTime = time.Date(2020, time.May, 24, 14, 01, 0, 0, time.UTC)
|
||||
tb.logFilePosition = recordNbr
|
||||
|
||||
// When
|
||||
isEndGap, err := tb.storeTimeGap(logLine, recordNbr)
|
||||
|
||||
// Then
|
||||
if err.Error() != "Gap end time is not empty" {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
if isEndGap == true {
|
||||
t.Errorf("Result is true while expectig false")
|
||||
}
|
||||
}
|
67
cmd/load.go
67
cmd/load.go
|
@ -19,10 +19,14 @@ limitations under the License.
|
|||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
//"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
//"strings"
|
||||
)
|
||||
|
||||
|
@ -101,6 +105,9 @@ func loadFile() (filleFullLog []LogLine, isProcessedOK bool) {
|
|||
headerDate := ""
|
||||
lineCount := 0
|
||||
|
||||
wrkTimeBlock := InferTimeBlock{}
|
||||
missingTimeBlockList := []InferTimeBlock{}
|
||||
|
||||
var isInMultiLine = false
|
||||
var cleanedInput []string
|
||||
var errorLog []string
|
||||
|
@ -254,16 +261,70 @@ func loadFile() (filleFullLog []LogLine, isProcessedOK bool) {
|
|||
previousLogLine.Nickname = headerNickname
|
||||
previousLogLine.Date = headerDate
|
||||
|
||||
//
|
||||
//parse a line
|
||||
logline, errorLine := ParseLine(eachline, previousLogLine)
|
||||
|
||||
//we have a valid line (contains a call)
|
||||
if logline.Call != "" {
|
||||
fullLog = append(fullLog, logline)
|
||||
|
||||
//store time inference data
|
||||
if isInterpolateTime {
|
||||
var isEndOfGap bool
|
||||
if isEndOfGap, err = wrkTimeBlock.storeTimeGap(logline, len(fullLog)); err != nil {
|
||||
log.Println("Fatal error: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
//If we reached the end of the time gap, we make the necessary checks and make our gap calculation
|
||||
if isEndOfGap {
|
||||
if err := wrkTimeBlock.finalizeTimeGap(); err != nil {
|
||||
//If an error occured it is a fatal error
|
||||
log.Println("Fatal error: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//add it to the gap collection
|
||||
missingTimeBlockList = append(missingTimeBlockList, wrkTimeBlock)
|
||||
|
||||
//create a new block
|
||||
wrkTimeBlock = InferTimeBlock{}
|
||||
|
||||
//Store this record in the new block as a new gap might be following
|
||||
//no error or endOfGap processing as it has already been succesfully processed
|
||||
wrkTimeBlock.storeTimeGap(logline, len(fullLog))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Store append the accumulated soft parsing errors into the global parsing error log file
|
||||
if errorLine != "" {
|
||||
errorLog = append(errorLog, fmt.Sprintf("Parsing error at line %d: %s ", lineCount, errorLine))
|
||||
}
|
||||
|
||||
//store the current logline so that it can be used as a model when parsing the next line
|
||||
previousLogLine = logline
|
||||
//Go back to the top (Continue not necessary)
|
||||
|
||||
//We go back to the top to process the next loaded log line (Continue not necessary here)
|
||||
}
|
||||
|
||||
//***
|
||||
//*** We have done processing the log file, so let's post process it
|
||||
//***
|
||||
|
||||
//if asked to infer the date, lets update the loaded logfile accordingly
|
||||
if isInterpolateTime {
|
||||
for _, timeBlock := range missingTimeBlockList {
|
||||
for i := 0; i < timeBlock.noTimeCount; i++ {
|
||||
position := timeBlock.logFilePosition + i
|
||||
pLogLine := &fullLog[position]
|
||||
|
||||
// durationOffset := time.Second * time.Duration(timeBlock.deltatime*(i+1))
|
||||
durationOffset := timeBlock.deltatime * time.Duration(i+1)
|
||||
newTime := timeBlock.lastRecordedTime.Add(durationOffset)
|
||||
updatedTimeString := newTime.Format("1504")
|
||||
pLogLine.Time = updatedTimeString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayLogSimple(fullLog)
|
||||
|
|
|
@ -42,6 +42,7 @@ type LogLine struct {
|
|||
BandUpperLimit float64
|
||||
Frequency string
|
||||
Time string
|
||||
ActualTime string //time actually recorded in FLE
|
||||
Call string
|
||||
Comment string
|
||||
QSLmsg string
|
||||
|
@ -80,6 +81,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg
|
|||
previousLine.OMname = ""
|
||||
previousLine.GridLoc = ""
|
||||
previousLine.Comment = ""
|
||||
previousLine.ActualTime = ""
|
||||
logLine = previousLine
|
||||
|
||||
//TODO: what happens when we have <> or when there are multiple comments
|
||||
|
@ -155,6 +157,7 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg
|
|||
if isRightOfCall == false {
|
||||
if regexpIsFullTime.MatchString(element) {
|
||||
logLine.Time = element
|
||||
logLine.ActualTime = element
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -162,9 +165,11 @@ func ParseLine(inputStr string, previousLine LogLine) (logLine LogLine, errorMsg
|
|||
if regexpIsTimePart.MatchString(element) {
|
||||
if logLine.Time == "" {
|
||||
logLine.Time = element
|
||||
logLine.ActualTime = element
|
||||
} else {
|
||||
goodPart := logLine.Time[:len(logLine.Time)-len(element)]
|
||||
logLine.Time = goodPart + element
|
||||
logLine.ActualTime = goodPart + element
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -24,27 +24,27 @@ func TestParseLine(t *testing.T) {
|
|||
{
|
||||
"Parse for time",
|
||||
args{inputStr: "1314 g3noh", previousLine: LogLine{Mode: "SSB"}},
|
||||
LogLine{Time: "1314", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
LogLine{Time: "1314", ActualTime: "1314", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
},
|
||||
{
|
||||
"Parse partial time - 1",
|
||||
args{inputStr: "4 g3noh", previousLine: LogLine{Time: "", Mode: "SSB"}},
|
||||
LogLine{Time: "4", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "", //TODO: should fail
|
||||
LogLine{Time: "4", ActualTime: "4", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "", //TODO: should fail
|
||||
},
|
||||
{
|
||||
"Parse partial time - 2",
|
||||
args{inputStr: "15 g3noh", previousLine: LogLine{Time: "1200", Mode: "SSB"}},
|
||||
LogLine{Time: "1215", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
LogLine{Time: "1215", ActualTime: "1215", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
},
|
||||
{
|
||||
"Parse partial time - 3",
|
||||
args{inputStr: "4 g3noh", previousLine: LogLine{Time: "1200", Mode: "SSB"}},
|
||||
LogLine{Time: "1204", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
LogLine{Time: "1204", ActualTime: "1204", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
},
|
||||
{
|
||||
"Parse for comment",
|
||||
args{inputStr: "4 g3noh <PSE QSL Direct>", previousLine: LogLine{Mode: "SSB"}},
|
||||
LogLine{Time: "4", Comment: "PSE QSL Direct", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
LogLine{Time: "4", ActualTime: "4", Comment: "PSE QSL Direct", Call: "G3NOH", Mode: "SSB", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
},
|
||||
{
|
||||
"Parse for QSL",
|
||||
|
@ -79,32 +79,32 @@ func TestParseLine(t *testing.T) {
|
|||
{
|
||||
"parse partial RST (sent) - CW",
|
||||
args{inputStr: "1230 on4kjm 5", previousLine: LogLine{Mode: "CW", ModeType: "CW"}},
|
||||
LogLine{Call: "ON4KJM", Time: "1230", RSTsent: "559", RSTrcvd: "599", Mode: "CW", ModeType: "CW"}, "",
|
||||
LogLine{Call: "ON4KJM", Time: "1230", ActualTime: "1230", RSTsent: "559", RSTrcvd: "599", Mode: "CW", ModeType: "CW"}, "",
|
||||
},
|
||||
{
|
||||
"parse partial RST (received) - CW",
|
||||
args{inputStr: "1230 on4kjm 5 44", previousLine: LogLine{Mode: "CW", ModeType: "CW"}},
|
||||
LogLine{Call: "ON4KJM", Time: "1230", RSTsent: "559", RSTrcvd: "449", Mode: "CW", ModeType: "CW"}, "",
|
||||
LogLine{Call: "ON4KJM", Time: "1230", ActualTime: "1230", RSTsent: "559", RSTrcvd: "449", Mode: "CW", ModeType: "CW"}, "",
|
||||
},
|
||||
{
|
||||
"parse full RST (received) - CW",
|
||||
args{inputStr: "1230 on4kjm 5 448", previousLine: LogLine{Mode: "CW", ModeType: "CW"}},
|
||||
LogLine{Call: "ON4KJM", Time: "1230", RSTsent: "559", RSTrcvd: "448", Mode: "CW", ModeType: "CW"}, "",
|
||||
LogLine{Call: "ON4KJM", Time: "1230", ActualTime: "1230", RSTsent: "559", RSTrcvd: "448", Mode: "CW", ModeType: "CW"}, "",
|
||||
},
|
||||
{
|
||||
"parse partial report (sent) - FM",
|
||||
args{inputStr: "1230 on4kjm 5", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}},
|
||||
LogLine{Call: "ON4KJM", Time: "1230", RSTsent: "55", RSTrcvd: "59", Mode: "FM", ModeType: "PHONE"}, "",
|
||||
LogLine{Call: "ON4KJM", Time: "1230", ActualTime: "1230", RSTsent: "55", RSTrcvd: "59", Mode: "FM", ModeType: "PHONE"}, "",
|
||||
},
|
||||
{
|
||||
"parse partial report (received) - FM",
|
||||
args{inputStr: "1230 on4kjm 5 44", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}},
|
||||
LogLine{Call: "ON4KJM", Time: "1230", RSTsent: "55", RSTrcvd: "44", Mode: "FM", ModeType: "PHONE"}, "",
|
||||
LogLine{Call: "ON4KJM", Time: "1230", ActualTime: "1230", RSTsent: "55", RSTrcvd: "44", Mode: "FM", ModeType: "PHONE"}, "",
|
||||
},
|
||||
{
|
||||
"Incompatible report",
|
||||
args{inputStr: "1230 on4kjm 5 599", previousLine: LogLine{Mode: "FM", ModeType: "PHONE"}},
|
||||
LogLine{Call: "ON4KJM", Time: "1230", RSTsent: "55", RSTrcvd: "*599", Mode: "FM", ModeType: "PHONE"}, "Invalid report (599) for PHONE mode ",
|
||||
LogLine{Call: "ON4KJM", Time: "1230", ActualTime: "1230", RSTsent: "55", RSTrcvd: "*599", Mode: "FM", ModeType: "PHONE"}, "Invalid report (599) for PHONE mode ",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -135,31 +135,31 @@ func TestHappyParseLine(t *testing.T) {
|
|||
"test1",
|
||||
args{inputStr: "1202 g4elz",
|
||||
previousLine: LogLine{Mode: "CW", ModeType: "CW", Band: "40m", BandLowerLimit: 7, BandUpperLimit: 7.3}},
|
||||
LogLine{Time: "1202", Call: "G4ELZ", Band: "40m", BandLowerLimit: 7, BandUpperLimit: 7.3, Mode: "CW", ModeType: "CW", RSTsent: "599", RSTrcvd: "599"}, "",
|
||||
LogLine{Time: "1202", ActualTime: "1202", Call: "G4ELZ", Band: "40m", BandLowerLimit: 7, BandUpperLimit: 7.3, Mode: "CW", ModeType: "CW", RSTsent: "599", RSTrcvd: "599"}, "",
|
||||
},
|
||||
{
|
||||
"test2",
|
||||
args{inputStr: "4 g3noh <PSE QSL Direct>",
|
||||
previousLine: LogLine{Time: "1202", Mode: "CW", ModeType: "CW", Band: "40m", BandLowerLimit: 7, BandUpperLimit: 7.3}},
|
||||
LogLine{Time: "1204", Call: "G3NOH", Band: "40m", BandLowerLimit: 7, BandUpperLimit: 7.3, Mode: "CW", ModeType: "CW", Comment: "PSE QSL Direct", RSTsent: "599", RSTrcvd: "599"}, "",
|
||||
LogLine{Time: "1204", ActualTime: "1204", Call: "G3NOH", Band: "40m", BandLowerLimit: 7, BandUpperLimit: 7.3, Mode: "CW", ModeType: "CW", Comment: "PSE QSL Direct", RSTsent: "599", RSTrcvd: "599"}, "",
|
||||
},
|
||||
{
|
||||
"test3",
|
||||
args{inputStr: "1227 gw4gte <Dave>",
|
||||
previousLine: LogLine{Time: "1202", Mode: "FM", ModeType: "PHONE", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148}},
|
||||
LogLine{Time: "1227", Call: "GW4GTE", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148, Mode: "FM", ModeType: "PHONE", Comment: "Dave", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
LogLine{Time: "1227", ActualTime: "1227", Call: "GW4GTE", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148, Mode: "FM", ModeType: "PHONE", Comment: "Dave", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
},
|
||||
{
|
||||
"test4",
|
||||
args{inputStr: "8 gw0tlk/m gwff-0021",
|
||||
previousLine: LogLine{Time: "1227", Mode: "FM", ModeType: "PHONE", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148}},
|
||||
LogLine{Time: "1228", Call: "GW0TLK/M", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148, Mode: "FM", ModeType: "PHONE", WWFF: "GWFF-0021", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
LogLine{Time: "1228", ActualTime: "1228", Call: "GW0TLK/M", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148, Mode: "FM", ModeType: "PHONE", WWFF: "GWFF-0021", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
},
|
||||
{
|
||||
"test5",
|
||||
args{inputStr: "7 dl0dan/p dlff-0002 dl/al-044",
|
||||
previousLine: LogLine{Time: "1220", Mode: "FM", ModeType: "PHONE", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148}},
|
||||
LogLine{Time: "1227", Call: "DL0DAN/P", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148, Mode: "FM", ModeType: "PHONE", WWFF: "DLFF-0002", SOTA: "DL/AL-044", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
LogLine{Time: "1227", ActualTime: "1227", Call: "DL0DAN/P", Band: "2m", BandLowerLimit: 144, BandUpperLimit: 148, Mode: "FM", ModeType: "PHONE", WWFF: "DLFF-0002", SOTA: "DL/AL-044", RSTsent: "59", RSTrcvd: "59"}, "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
|
||||
var cfgFile string
|
||||
var inputFilename string
|
||||
var isInterpolateTime bool
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
|
@ -64,6 +65,7 @@ func init() {
|
|||
cobra.OnInitialize(initConfig)
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.FLEcli.yaml)")
|
||||
rootCmd.PersistentFlags().BoolVarP(&isInterpolateTime, "interpolate", "", false, "Interpolates the missing time entries.")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&inputFilename, "input", "i", "", "FLE formatted input file (mandatory)")
|
||||
rootCmd.MarkPersistentFlagRequired("input")
|
||||
|
|
3
go.mod
3
go.mod
|
@ -4,6 +4,7 @@ go 1.14
|
|||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.3.1 // indirect
|
||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
|
@ -13,6 +14,6 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.7.0
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc
|
||||
gopkg.in/ini.v1 v1.57.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
)
|
||||
|
|
1
go.sum
1
go.sum
|
@ -287,6 +287,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
|
|
|
@ -3,3 +3,6 @@
|
|||
* `./FLEcli -i test/data/fle-1.txt load`
|
||||
* `./FLEcli -i test/data/sample_contest_ru.txt load`
|
||||
* `go test ./...` runs the unit tests
|
||||
|
||||
* `./FLEcli -i test/data/ON4KJM@ONFF-025920200524.txt --interpolate adif --wwff --overwrite`
|
||||
* `./FLEcli adif -i=test/data/ON4KJM@ONFF-025920200524.txt --interpolate --wwff --overwrite`
|
||||
|
|
6
todo.md
6
todo.md
|
@ -15,12 +15,14 @@
|
|||
* [x] decode and check frequency
|
||||
* [ ] New MYGRID keyword
|
||||
* [ ] Support different date delimiter
|
||||
* [ ] Support extrapolated date
|
||||
* [x] Support extrapolated date
|
||||
* [ ] Support date not prefixed by "date" (non header mode) DATE keyword is now optional
|
||||
* [ ] Support date increment
|
||||
* [ ] Support WWFF keyword
|
||||
* [ ] Validate that we have the necessary dat for the output
|
||||
|
||||
## Output processing
|
||||
* [ ] WWFF ADIF output
|
||||
* [x] WWFF ADIF output
|
||||
* [ ] Standard ADIF output
|
||||
* [ ] SOTA ADIF
|
||||
* [ ] SOTA CSV
|
||||
|
|
Loading…
Reference in a new issue