summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-14 10:39:16 +0300
committerPaul Buetow <paul@buetow.org>2026-04-14 10:39:16 +0300
commitff749457e392288e29dbf553bf8e0e64cc8b6401 (patch)
tree4d43b1de40007ebcc880bd596114e097a30cc590 /internal
parentb0ac28f90ddf85e85b0308229569292fd6f7f9a2 (diff)
refactor: extract record line parser to internal/recordline (m3)
Deduplicate parseRecordLine from goprecords and storage; shared recordline.Parse with Fields type. Tests live in recordline package; DB import path still covered by goprecords TestImportFromDir. Made-with: Cursor
Diffstat (limited to 'internal')
-rw-r--r--internal/goprecords/aggregate.go6
-rw-r--r--internal/goprecords/parse_test.go78
-rw-r--r--internal/goprecords/types.go44
-rw-r--r--internal/recordline/recordline.go49
-rw-r--r--internal/recordline/recordline_test.go83
-rw-r--r--internal/storage/db.go47
6 files changed, 138 insertions, 169 deletions
diff --git a/internal/goprecords/aggregate.go b/internal/goprecords/aggregate.go
index c143426..cefb545 100644
--- a/internal/goprecords/aggregate.go
+++ b/internal/goprecords/aggregate.go
@@ -7,6 +7,8 @@ import (
"os"
"path/filepath"
"strings"
+
+ "codeberg.org/snonux/goprecords/internal/recordline"
)
// Aggregates holds all category maps. Host uses HostAggregate; others use Aggregate.
@@ -81,7 +83,7 @@ func processRecordsFile(ctx context.Context, path, host string, out *Aggregates)
return ctx.Err()
default:
}
- rec, ok := parseRecordLine(sc.Text())
+ rec, ok := recordline.Parse(sc.Text())
if !ok {
continue
}
@@ -111,7 +113,7 @@ func lastKernelFromFile(ctx context.Context, path string) (string, error) {
return "", ctx.Err()
default:
}
- rec, ok := parseRecordLine(sc.Text())
+ rec, ok := recordline.Parse(sc.Text())
if !ok {
continue
}
diff --git a/internal/goprecords/parse_test.go b/internal/goprecords/parse_test.go
index ce63e7a..a38012e 100644
--- a/internal/goprecords/parse_test.go
+++ b/internal/goprecords/parse_test.go
@@ -51,84 +51,6 @@ func TestParseMetric(t *testing.T) {
}
}
-func TestParseRecordLine(t *testing.T) {
- tests := []struct {
- in string
- want recordLine
- ok bool
- }{
- {
- "12345:1700000000:Linux 6.5.0-generic",
- recordLine{
- Uptime: 12345,
- BootTime: 1700000000,
- OS: "Linux 6.5.0-generic",
- KernelName: "Linux",
- KernelMajor: "Linux 6...",
- },
- true,
- },
- {
- " 99:100:FreeBSD 14.0-RELEASE ",
- recordLine{
- Uptime: 99,
- BootTime: 100,
- OS: "FreeBSD 14.0-RELEASE",
- KernelName: "FreeBSD",
- KernelMajor: "FreeBSD 14...",
- },
- true,
- },
- {
- "500:200:SingleToken",
- recordLine{
- Uptime: 500,
- BootTime: 200,
- OS: "SingleToken",
- KernelName: "SingleToken",
- KernelMajor: "SingleToken SingleToken...",
- },
- true,
- },
- {
- "100:200:Linux 6.5.0:extra",
- recordLine{
- Uptime: 100,
- BootTime: 200,
- OS: "Linux 6.5.0:extra",
- KernelName: "Linux",
- KernelMajor: "Linux 6...",
- },
- true,
- },
- {
- "abc:def:Linux 6.5.0",
- recordLine{
- Uptime: 0,
- BootTime: 0,
- OS: "Linux 6.5.0",
- KernelName: "Linux",
- KernelMajor: "Linux 6...",
- },
- true,
- },
- {"", recordLine{}, false},
- {" ", recordLine{}, false},
- {"only:two", recordLine{}, false},
- {"no-colons-at-all", recordLine{}, false},
- }
- for _, tt := range tests {
- got, ok := parseRecordLine(tt.in)
- if ok != tt.ok {
- t.Errorf("parseRecordLine(%q) ok=%v, want %v", tt.in, ok, tt.ok)
- continue
- }
- if tt.ok && got != tt.want {
- t.Errorf("parseRecordLine(%q) = %+v, want %+v", tt.in, got, tt.want)
- }
- }
-}
-
func TestParseOutputFormat(t *testing.T) {
tests := []struct {
in string
diff --git a/internal/goprecords/types.go b/internal/goprecords/types.go
index 84e7f4f..6e5bf10 100644
--- a/internal/goprecords/types.go
+++ b/internal/goprecords/types.go
@@ -76,15 +76,6 @@ func NewHostAggregate(name, lastKernel string) *HostAggregate {
}
}
-// recordLine holds the parsed fields from a single uptimed record line.
-type recordLine struct {
- Uptime uint64
- BootTime uint64
- OS string
- KernelName string
- KernelMajor string
-}
-
// tableRow is one row in the report table.
type tableRow struct {
Pos string
@@ -264,41 +255,6 @@ func ParseOutputFormat(s string) (OutputFormat, error) {
}
}
-func parseRecordLine(line string) (recordLine, bool) {
- line = strings.TrimSpace(line)
- if line == "" {
- return recordLine{}, false
- }
- parts := strings.SplitN(line, ":", 3)
- if len(parts) != 3 {
- return recordLine{}, false
- }
- uptime, _ := strconv.ParseUint(parts[0], 10, 64)
- bootTime, _ := strconv.ParseUint(parts[1], 10, 64)
- osStr := parts[2]
- kernelName := osStr
- if i := strings.Index(osStr, " "); i > 0 {
- kernelName = osStr[:i]
- }
- kernelMajor := kernelName + " "
- rest := osStr
- if i := strings.Index(osStr, " "); i >= 0 {
- rest = osStr[i+1:]
- }
- if j := strings.Index(rest, "."); j >= 0 {
- kernelMajor += rest[:j] + "..."
- } else {
- kernelMajor += rest + "..."
- }
- return recordLine{
- Uptime: uptime,
- BootTime: bootTime,
- OS: osStr,
- KernelName: kernelName,
- KernelMajor: kernelMajor,
- }, true
-}
-
func wordWrap(s string, lineLimit int) string {
if lineLimit <= 0 || len(s) <= lineLimit {
return s
diff --git a/internal/recordline/recordline.go b/internal/recordline/recordline.go
new file mode 100644
index 0000000..940f235
--- /dev/null
+++ b/internal/recordline/recordline.go
@@ -0,0 +1,49 @@
+package recordline
+
+import (
+ "strconv"
+ "strings"
+)
+
+type Fields struct {
+ Uptime uint64
+ BootTime uint64
+ OS string
+ KernelName string
+ KernelMajor string
+}
+
+func Parse(line string) (Fields, bool) {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ return Fields{}, false
+ }
+ parts := strings.SplitN(line, ":", 3)
+ if len(parts) != 3 {
+ return Fields{}, false
+ }
+ uptime, _ := strconv.ParseUint(parts[0], 10, 64)
+ bootTime, _ := strconv.ParseUint(parts[1], 10, 64)
+ osStr := parts[2]
+ kernelName := osStr
+ if i := strings.Index(osStr, " "); i > 0 {
+ kernelName = osStr[:i]
+ }
+ kernelMajor := kernelName + " "
+ rest := osStr
+ if i := strings.Index(osStr, " "); i >= 0 {
+ rest = osStr[i+1:]
+ }
+ if j := strings.Index(rest, "."); j >= 0 {
+ kernelMajor += rest[:j] + "..."
+ } else {
+ kernelMajor += rest + "..."
+ }
+ return Fields{
+ Uptime: uptime,
+ BootTime: bootTime,
+ OS: osStr,
+ KernelName: kernelName,
+ KernelMajor: kernelMajor,
+ }, true
+}
diff --git a/internal/recordline/recordline_test.go b/internal/recordline/recordline_test.go
new file mode 100644
index 0000000..4fcc152
--- /dev/null
+++ b/internal/recordline/recordline_test.go
@@ -0,0 +1,83 @@
+package recordline
+
+import (
+ "testing"
+)
+
+func TestParse(t *testing.T) {
+ tests := []struct {
+ in string
+ want Fields
+ ok bool
+ }{
+ {
+ "12345:1700000000:Linux 6.5.0-generic",
+ Fields{
+ Uptime: 12345,
+ BootTime: 1700000000,
+ OS: "Linux 6.5.0-generic",
+ KernelName: "Linux",
+ KernelMajor: "Linux 6...",
+ },
+ true,
+ },
+ {
+ " 99:100:FreeBSD 14.0-RELEASE ",
+ Fields{
+ Uptime: 99,
+ BootTime: 100,
+ OS: "FreeBSD 14.0-RELEASE",
+ KernelName: "FreeBSD",
+ KernelMajor: "FreeBSD 14...",
+ },
+ true,
+ },
+ {
+ "500:200:SingleToken",
+ Fields{
+ Uptime: 500,
+ BootTime: 200,
+ OS: "SingleToken",
+ KernelName: "SingleToken",
+ KernelMajor: "SingleToken SingleToken...",
+ },
+ true,
+ },
+ {
+ "100:200:Linux 6.5.0:extra",
+ Fields{
+ Uptime: 100,
+ BootTime: 200,
+ OS: "Linux 6.5.0:extra",
+ KernelName: "Linux",
+ KernelMajor: "Linux 6...",
+ },
+ true,
+ },
+ {
+ "abc:def:Linux 6.5.0",
+ Fields{
+ Uptime: 0,
+ BootTime: 0,
+ OS: "Linux 6.5.0",
+ KernelName: "Linux",
+ KernelMajor: "Linux 6...",
+ },
+ true,
+ },
+ {"", Fields{}, false},
+ {" ", Fields{}, false},
+ {"only:two", Fields{}, false},
+ {"no-colons-at-all", Fields{}, false},
+ }
+ for _, tt := range tests {
+ got, ok := Parse(tt.in)
+ if ok != tt.ok {
+ t.Errorf("Parse(%q) ok=%v, want %v", tt.in, ok, tt.ok)
+ continue
+ }
+ if tt.ok && got != tt.want {
+ t.Errorf("Parse(%q) = %+v, want %+v", tt.in, got, tt.want)
+ }
+ }
+}
diff --git a/internal/storage/db.go b/internal/storage/db.go
index ea9d764..14d7050 100644
--- a/internal/storage/db.go
+++ b/internal/storage/db.go
@@ -7,9 +7,9 @@ import (
"fmt"
"os"
"path/filepath"
- "strconv"
"strings"
+ "codeberg.org/snonux/goprecords/internal/recordline"
_ "modernc.org/sqlite"
)
@@ -128,14 +128,6 @@ func LoadRecords(ctx context.Context, db *sql.DB) ([]Record, error) {
return out, nil
}
-type recordLine struct {
- Uptime uint64
- BootTime uint64
- OS string
- KernelName string
- KernelMajor string
-}
-
func importFile(ctx context.Context, insert *sql.Stmt, path, host string) error {
f, err := os.Open(path)
if err != nil {
@@ -149,7 +141,7 @@ func importFile(ctx context.Context, insert *sql.Stmt, path, host string) error
return ctx.Err()
default:
}
- rec, ok := parseRecordLine(sc.Text())
+ rec, ok := recordline.Parse(sc.Text())
if !ok {
continue
}
@@ -162,38 +154,3 @@ func importFile(ctx context.Context, insert *sql.Stmt, path, host string) error
}
return nil
}
-
-func parseRecordLine(line string) (recordLine, bool) {
- line = strings.TrimSpace(line)
- if line == "" {
- return recordLine{}, false
- }
- parts := strings.SplitN(line, ":", 3)
- if len(parts) != 3 {
- return recordLine{}, false
- }
- uptime, _ := strconv.ParseUint(parts[0], 10, 64)
- bootTime, _ := strconv.ParseUint(parts[1], 10, 64)
- osStr := parts[2]
- kernelName := osStr
- if i := strings.Index(osStr, " "); i > 0 {
- kernelName = osStr[:i]
- }
- kernelMajor := kernelName + " "
- rest := osStr
- if i := strings.Index(osStr, " "); i >= 0 {
- rest = osStr[i+1:]
- }
- if j := strings.Index(rest, "."); j >= 0 {
- kernelMajor += rest[:j] + "..."
- } else {
- kernelMajor += rest + "..."
- }
- return recordLine{
- Uptime: uptime,
- BootTime: bootTime,
- OS: osStr,
- KernelName: kernelName,
- KernelMajor: kernelMajor,
- }, true
-}