Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ run-name: "CI (${{ github.event_name }}) on ${{ github.ref_name }} by @${{ githu
on:
push:
branches: ["**"]
paths-ignore: ['**/*.md']
pull_request:
branches: ["**"]
paths-ignore: ['**/*.md']

permissions:
contents: read
Expand All @@ -21,6 +23,7 @@ jobs:
codecov-name: sunspecctl
go-versions: '["1.26", "1.27"]'
test-exclude-regex: "/example(s)?($|/)"
policy-version: v1.4.1
secrets: inherit

# The per-package coverage floor (TEST-012).
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Documents against the code (DOC-008, standards/documentation.md#doc-sync).
# No path filter: this is the check that runs when ci.yml skips a
# Markdown-only change, so branch protection can require it.
name: Docs
on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
docs:
uses: otfabric/.github/.github/workflows/docs-check.yml@v3.5.0
with:
docsync-version: v0.2.0
policy-version: v1.4.1
secrets: inherit
9 changes: 9 additions & 0 deletions .otfabric/docsync.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"clis": [
{
"name": "sunspecctl",
"manifest": ".",
"exit_codes": "."
}
]
}
2 changes: 1 addition & 1 deletion .otfabric/policy.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# OT Fabric engineering policy adoption (otfabric/engineering standards/README.md#adoption).
policy: "1.2"
policy: "1.4"
profile: cli
mode: enforce
owner: bartvanbos
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Command-line tool for reading and simulating SunSpec devices over Modbus, built

## Organisation standards

This repository follows **otfabric/engineering policy 1.2**, profile **cli** (`.otfabric/policy.yaml`, mode `enforce`). Where this file and the organisation standards disagree, this file wins here — say so in your report.
This repository follows **otfabric/engineering policy 1.4**, profile **cli** (`.otfabric/policy.yaml`, mode `enforce`). Where this file and the organisation standards disagree, this file wins here — say so in your report.

## Where the truth lives

Expand Down
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help all build build-all release-all check test coverage coverage-html coverage-clean coverage-check cover fmt fmt-check vet lint lint-ci vuln install clean e2e fuzz coverage-pkg-check
.PHONY: help all build build-all release-all check test coverage coverage-html coverage-clean coverage-check cover fmt fmt-check vet lint lint-ci vuln install clean e2e fuzz coverage-pkg-check docs-check

help: ## This help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
Expand Down Expand Up @@ -31,6 +31,8 @@ TEST_PKGS := ./...
COVERAGE_MIN := 90
# Per-package floor (TEST-012): what the weakest package measures now, rounded down.
COVER_PKG_MIN := 90
# docsync release of docs-check (DOC-008).
DOCSYNC_VERSION := v0.2.0

all: check build ## Run all checks and build the CLI

Expand All @@ -39,7 +41,7 @@ coverage-pkg-check: coverage-check ## Fail when a package is below COVER_PKG_MIN
END { for (k in n) { p = k; sub(/\/[^\/]*$$/, "", p); s[p] += n[k]; if (k in h) c[p] += n[k] } \
for (p in s) { v = 100 * c[p] / s[p]; if (v < m) { printf "%s: %.1f%% < %s%%\n", p, v, m; bad = 1 } } exit bad }' coverage.out

check: fmt fmt-check vet lint lint-ci vuln test coverage-pkg-check ## Run all checks (format, vet, lint, test, coverage)
check: fmt fmt-check vet lint lint-ci vuln test coverage-pkg-check docs-check ## Run all checks (format, vet, lint, test, coverage, docs)

test: ## Run the tests with the race detector
@echo "Running tests (race detector)"
Expand Down Expand Up @@ -144,6 +146,10 @@ release-all: build-all ## Package CLI binaries into tar.gz (Unix) and zip (Windo
fi; \
done

# The documents against the code (DOC-008): docsync, as docs-check.yml runs it.
docs-check: ## Check that the documents agree with the code (docsync)
@go run github.com/otfabric/docsync@$(DOCSYNC_VERSION) check .

FUZZTIME ?= 30s

fuzz: ## Run every fuzz target for FUZZTIME each (default 30s)
Expand Down
81 changes: 81 additions & 0 deletions docsync_manifest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT

// docsync manifest v1 (template from github.com/otfabric/docsync; keep it as
// it is apart from the package clause and the line marked docsync:root).

package main

import (
"encoding/json"
"os"
"strings"
"testing"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

// TestDocsyncManifest writes the command tree to $DOCSYNC_MANIFEST for
// docsync, which checks it against the documents. Without the variable the
// test does nothing.
func TestDocsyncManifest(t *testing.T) {
out := os.Getenv("DOCSYNC_MANIFEST")
if out == "" {
t.Skip("DOCSYNC_MANIFEST is not set")
}
root := newRootCmd() // docsync:root
root.InitDefaultHelpCmd()
root.InitDefaultCompletionCmd()
type flag struct {
Name string `json:"name"`
Shorthand string `json:"shorthand,omitempty"`
Bool bool `json:"bool,omitempty"`
Required bool `json:"required,omitempty"`
Hidden bool `json:"hidden,omitempty"`
Local bool `json:"local,omitempty"`
}
type command struct {
Path string `json:"path"`
Aliases []string `json:"aliases,omitempty"`
Hidden bool `json:"hidden,omitempty"`
Runnable bool `json:"runnable"`
Subcommands []string `json:"subcommands,omitempty"`
Flags []flag `json:"flags,omitempty"`
}
var cmds []command
var walk func(c *cobra.Command)
walk = func(c *cobra.Command) {
c.InitDefaultHelpFlag()
c.InitDefaultVersionFlag()
path := strings.TrimSpace(strings.TrimPrefix(c.CommandPath(), root.Name()))
cmd := command{Path: path, Aliases: c.Aliases, Hidden: c.Hidden, Runnable: c.Runnable()}
local := map[string]bool{}
c.LocalNonPersistentFlags().VisitAll(func(f *pflag.Flag) { local[f.Name] = true })
c.PersistentFlags().VisitAll(func(f *pflag.Flag) { local[f.Name] = true })
add := func(f *pflag.Flag) {
req := false
if v, ok := f.Annotations[cobra.BashCompOneRequiredFlag]; ok && len(v) > 0 && v[0] == "true" {
req = true
}
cmd.Flags = append(cmd.Flags, flag{Name: f.Name, Shorthand: f.Shorthand, Bool: f.NoOptDefVal != "",
Required: req, Hidden: f.Hidden, Local: local[f.Name]})
}
c.LocalFlags().VisitAll(add)
c.InheritedFlags().VisitAll(add)
for _, s := range c.Commands() {
cmd.Subcommands = append(cmd.Subcommands, s.Name())
}
cmds = append(cmds, cmd)
for _, s := range c.Commands() {
walk(s)
}
}
walk(root)
data, err := json.Marshal(map[string]any{"version": 1, "name": root.Name(), "commands": cmds})
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(out, data, 0o600); err != nil {
t.Fatal(err)
}
}
Loading