refactor: modernise to Go 1.26 — iterators, slices, maps, strings
- Add ParsePlaybookIter, ParseTasksIter, GetHostsIter, AllHostsIter (ansible) - Add ListTemplatesIter (container), TargetsIter (build), LanguagesIter (sdk) - Replace sort.Slice with slices.SortFunc across cmd/dev, cmd/qa, cmd/monitor, cmd/setup - Replace manual map-key-sort with slices.Sorted(maps.Keys(...)) - Replace strings.Split with strings.SplitSeq where result is iterated (devkit) - Replace range-over-int in complexity_test, ansible/modules, devops - Remove redundant manual min() in favour of built-in - 22 files, all tests pass Co-Authored-By: Gemini <noreply@google.com> Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
171becfdd6
commit
f9eb54b856
23 changed files with 231 additions and 110 deletions
|
|
@ -590,7 +590,7 @@ func (e *Executor) gatherFacts(ctx context.Context, host string, play *Play) err
|
|||
|
||||
// OS info
|
||||
stdout, _, _, _ = client.Run(ctx, "cat /etc/os-release 2>/dev/null | grep -E '^(ID|VERSION_ID)=' | head -2")
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
for line := range strings.SplitSeq(stdout, "\n") {
|
||||
if strings.HasPrefix(line, "ID=") {
|
||||
facts.Distribution = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -970,7 +970,7 @@ func ctxSleep(ctx context.Context, seconds int) <-chan struct{} {
|
|||
func sleepChan(seconds int) <-chan struct{} {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
for i := 0; i < seconds; i++ {
|
||||
for range seconds {
|
||||
select {
|
||||
case <-ch:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ package ansible
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/log"
|
||||
|
|
@ -46,6 +49,21 @@ func (p *Parser) ParsePlaybook(path string) ([]Play, error) {
|
|||
return plays, nil
|
||||
}
|
||||
|
||||
// ParsePlaybookIter returns an iterator for plays in an Ansible playbook file.
|
||||
func (p *Parser) ParsePlaybookIter(path string) (iter.Seq[Play], error) {
|
||||
plays, err := p.ParsePlaybook(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(yield func(Play) bool) {
|
||||
for _, play := range plays {
|
||||
if !yield(play) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseInventory parses an Ansible inventory file.
|
||||
func (p *Parser) ParseInventory(path string) (*Inventory, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
|
|
@ -82,6 +100,21 @@ func (p *Parser) ParseTasks(path string) ([]Task, error) {
|
|||
return tasks, nil
|
||||
}
|
||||
|
||||
// ParseTasksIter returns an iterator for tasks in a tasks file.
|
||||
func (p *Parser) ParseTasksIter(path string) (iter.Seq[Task], error) {
|
||||
tasks, err := p.ParseTasks(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(yield func(Task) bool) {
|
||||
for _, task := range tasks {
|
||||
if !yield(task) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseRole parses a role and returns its tasks.
|
||||
func (p *Parser) ParseRole(name string, tasksFrom string) ([]Task, error) {
|
||||
if tasksFrom == "" {
|
||||
|
|
@ -319,6 +352,18 @@ func GetHosts(inv *Inventory, pattern string) []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetHostsIter returns an iterator for hosts matching a pattern from inventory.
|
||||
func GetHostsIter(inv *Inventory, pattern string) iter.Seq[string] {
|
||||
hosts := GetHosts(inv, pattern)
|
||||
return func(yield func(string) bool) {
|
||||
for _, host := range hosts {
|
||||
if !yield(host) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAllHosts(group *InventoryGroup) []string {
|
||||
if group == nil {
|
||||
return nil
|
||||
|
|
@ -334,6 +379,33 @@ func getAllHosts(group *InventoryGroup) []string {
|
|||
return hosts
|
||||
}
|
||||
|
||||
// AllHostsIter returns an iterator for all hosts in an inventory group.
|
||||
func AllHostsIter(group *InventoryGroup) iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
// Sort keys for deterministic iteration
|
||||
keys := slices.Sorted(maps.Keys(group.Hosts))
|
||||
for _, name := range keys {
|
||||
if !yield(name) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Sort children keys for deterministic iteration
|
||||
childKeys := slices.Sorted(maps.Keys(group.Children))
|
||||
for _, name := range childKeys {
|
||||
child := group.Children[name]
|
||||
for host := range AllHostsIter(child) {
|
||||
if !yield(host) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getGroupHosts(group *InventoryGroup, name string) []string {
|
||||
if group == nil {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package build
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -159,11 +160,22 @@ func ConfigExists(fs io.Medium, dir string) bool {
|
|||
return fileExists(fs, ConfigPath(dir))
|
||||
}
|
||||
|
||||
// TargetsIter returns an iterator for the build targets.
|
||||
func (cfg *BuildConfig) TargetsIter() iter.Seq[TargetConfig] {
|
||||
return func(yield func(TargetConfig) bool) {
|
||||
for _, t := range cfg.Targets {
|
||||
if !yield(t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToTargets converts TargetConfig slice to Target slice for use with builders.
|
||||
func (cfg *BuildConfig) ToTargets() []Target {
|
||||
targets := make([]Target, len(cfg.Targets))
|
||||
for i, t := range cfg.Targets {
|
||||
targets[i] = Target(t)
|
||||
targets[i] = Target{OS: t.OS, Arch: t.Arch}
|
||||
}
|
||||
return targets
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
|
|
@ -66,8 +67,8 @@ func runHealth(registryPath string, verbose bool) error {
|
|||
})
|
||||
|
||||
// Sort for consistent verbose output
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].Name < statuses[j].Name
|
||||
slices.SortFunc(statuses, func(a, b git.RepoStatus) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
// Aggregate stats
|
||||
|
|
@ -162,14 +163,7 @@ func formatRepoList(reposList []string) string {
|
|||
}
|
||||
|
||||
func joinRepos(reposList []string) string {
|
||||
result := ""
|
||||
for i, r := range reposList {
|
||||
if i > 0 {
|
||||
result += ", "
|
||||
}
|
||||
result += r
|
||||
}
|
||||
return result
|
||||
return strings.Join(reposList, ", ")
|
||||
}
|
||||
|
||||
func statusPart(count int, label string, style *cli.AnsiStyle) string {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -107,8 +107,8 @@ func runIssues(registryPath string, limit int, assignee string) error {
|
|||
cli.Print("\033[2K\r") // Clear progress line
|
||||
|
||||
// Sort by created date (newest first)
|
||||
sort.Slice(allIssues, func(i, j int) bool {
|
||||
return allIssues[i].CreatedAt.After(allIssues[j].CreatedAt)
|
||||
slices.SortFunc(allIssues, func(a, b GitHubIssue) int {
|
||||
return b.CreatedAt.Compare(a.CreatedAt)
|
||||
})
|
||||
|
||||
// Print issues
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -111,14 +111,17 @@ func runReviews(registryPath string, author string, showAll bool) error {
|
|||
cli.Print("\033[2K\r") // Clear progress line
|
||||
|
||||
// Sort: pending review first, then by date
|
||||
sort.Slice(allPRs, func(i, j int) bool {
|
||||
slices.SortFunc(allPRs, func(a, b GitHubPR) int {
|
||||
// Pending reviews come first
|
||||
iPending := allPRs[i].ReviewDecision == "" || allPRs[i].ReviewDecision == "REVIEW_REQUIRED"
|
||||
jPending := allPRs[j].ReviewDecision == "" || allPRs[j].ReviewDecision == "REVIEW_REQUIRED"
|
||||
if iPending != jPending {
|
||||
return iPending
|
||||
aPending := a.ReviewDecision == "" || a.ReviewDecision == "REVIEW_REQUIRED"
|
||||
bPending := b.ReviewDecision == "" || b.ReviewDecision == "REVIEW_REQUIRED"
|
||||
if aPending != bPending {
|
||||
if aPending {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return allPRs[i].CreatedAt.After(allPRs[j].CreatedAt)
|
||||
return b.CreatedAt.Compare(a.CreatedAt)
|
||||
})
|
||||
|
||||
// Print PRs
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/go-agentic"
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"forge.lthn.ai/core/go-agentic"
|
||||
"forge.lthn.ai/core/go-scm/git"
|
||||
"forge.lthn.ai/core/go/pkg/i18n"
|
||||
)
|
||||
|
|
@ -94,8 +95,8 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
|||
statuses := result.([]git.RepoStatus)
|
||||
|
||||
// Sort by repo name for consistent output
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].Name < statuses[j].Name
|
||||
slices.SortFunc(statuses, func(a, b git.RepoStatus) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
// Display status table
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"forge.lthn.ai/core/go/pkg/i18n"
|
||||
"forge.lthn.ai/core/go/pkg/io"
|
||||
"forge.lthn.ai/core/go/pkg/repos"
|
||||
)
|
||||
|
||||
// Workflow command flags
|
||||
|
|
@ -79,8 +82,8 @@ func runWorkflowList(registryPath string) error {
|
|||
}
|
||||
|
||||
// Sort repos by name for consistent output
|
||||
sort.Slice(repoList, func(i, j int) bool {
|
||||
return repoList[i].Name < repoList[j].Name
|
||||
slices.SortFunc(repoList, func(a, b *repos.Repo) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
// Collect all unique workflow files across all repos
|
||||
|
|
@ -97,11 +100,7 @@ func runWorkflowList(registryPath string) error {
|
|||
}
|
||||
|
||||
// Sort workflow names
|
||||
var workflowNames []string
|
||||
for wf := range workflowSet {
|
||||
workflowNames = append(workflowNames, wf)
|
||||
}
|
||||
sort.Strings(workflowNames)
|
||||
workflowNames := slices.Sorted(maps.Keys(workflowSet))
|
||||
|
||||
if len(workflowNames) == 0 {
|
||||
cli.Text(i18n.T("cmd.dev.workflow.no_workflows"))
|
||||
|
|
@ -168,8 +167,8 @@ func runWorkflowSync(registryPath string, workflowFile string, dryRun bool) erro
|
|||
}
|
||||
|
||||
// Sort repos by name for consistent output
|
||||
sort.Slice(repoList, func(i, j int) bool {
|
||||
return repoList[i].Name < repoList[j].Name
|
||||
slices.SortFunc(repoList, func(a, b *repos.Repo) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
if dryRun {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/go-agentic"
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"forge.lthn.ai/core/go/pkg/framework"
|
||||
"forge.lthn.ai/core/go-agentic"
|
||||
"forge.lthn.ai/core/go-scm/git"
|
||||
"forge.lthn.ai/core/go/pkg/framework"
|
||||
)
|
||||
|
||||
// Tasks for dev service
|
||||
|
|
@ -90,8 +91,8 @@ func (s *Service) runWork(task TaskWork) error {
|
|||
statuses := result.([]git.RepoStatus)
|
||||
|
||||
// Sort by name
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].Name < statuses[j].Name
|
||||
slices.SortFunc(statuses, func(a, b git.RepoStatus) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
// Display status table
|
||||
|
|
@ -234,8 +235,8 @@ func (s *Service) runStatus(task TaskStatus) error {
|
|||
}
|
||||
|
||||
statuses := result.([]git.RepoStatus)
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].Name < statuses[j].Name
|
||||
slices.SortFunc(statuses, func(a, b git.RepoStatus) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
s.printStatusTable(statuses)
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@
|
|||
package monitor
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
|
|
@ -434,13 +436,11 @@ func sortBySeverity(findings []Finding) {
|
|||
"low": 3,
|
||||
}
|
||||
|
||||
sort.Slice(findings, func(i, j int) bool {
|
||||
oi := severityOrder[findings[i].Severity]
|
||||
oj := severityOrder[findings[j].Severity]
|
||||
if oi != oj {
|
||||
return oi < oj
|
||||
}
|
||||
return findings[i].RepoName < findings[j].RepoName
|
||||
slices.SortFunc(findings, func(a, b Finding) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(severityOrder[a.Severity], severityOrder[b.Severity]),
|
||||
cmp.Compare(a.RepoName, b.RepoName),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -491,11 +491,7 @@ func outputTable(findings []Finding) error {
|
|||
}
|
||||
|
||||
// Sort repos for consistent output
|
||||
repoNames := make([]string, 0, len(byRepo))
|
||||
for repo := range byRepo {
|
||||
repoNames = append(repoNames, repo)
|
||||
}
|
||||
sort.Strings(repoNames)
|
||||
repoNames := slices.Sorted(maps.Keys(byRepo))
|
||||
|
||||
// Print by repo
|
||||
for _, repo := range repoNames {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package qa
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
|
@ -15,7 +16,7 @@ import (
|
|||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
|
|
@ -92,11 +93,11 @@ func RunDocblockCheck(paths []string, threshold float64, verbose, jsonOutput boo
|
|||
}
|
||||
|
||||
// Sort missing by file then line
|
||||
sort.Slice(result.Missing, func(i, j int) bool {
|
||||
if result.Missing[i].File != result.Missing[j].File {
|
||||
return result.Missing[i].File < result.Missing[j].File
|
||||
}
|
||||
return result.Missing[i].Line < result.Missing[j].Line
|
||||
slices.SortFunc(result.Missing, func(a, b MissingDocblock) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.File, b.File),
|
||||
cmp.Compare(a.Line, b.Line),
|
||||
)
|
||||
})
|
||||
|
||||
// Print result
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@
|
|||
package qa
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
|
|
@ -99,8 +100,8 @@ func runHealth() error {
|
|||
cli.Print("\033[2K\r") // Clear progress
|
||||
|
||||
// Sort: problems first, then passing
|
||||
sort.Slice(healthResults, func(i, j int) bool {
|
||||
return healthPriority(healthResults[i].Status) < healthPriority(healthResults[j].Status)
|
||||
slices.SortFunc(healthResults, func(a, b RepoHealth) int {
|
||||
return cmp.Compare(healthPriority(a.Status), healthPriority(b.Status))
|
||||
})
|
||||
|
||||
// Filter if --problems flag
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@
|
|||
package qa
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -203,8 +204,8 @@ func categoriseIssues(issues []Issue) map[string][]Issue {
|
|||
|
||||
// Sort each category by priority
|
||||
for cat := range result {
|
||||
sort.Slice(result[cat], func(i, j int) bool {
|
||||
return result[cat][i].Priority < result[cat][j].Priority
|
||||
slices.SortFunc(result[cat], func(a, b Issue) int {
|
||||
return cmp.Compare(a.Priority, b.Priority)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -392,10 +393,3 @@ func printTriagedIssue(issue Issue) {
|
|||
cli.Print(" %s %s\n", dimStyle.Render("->"), issue.ActionHint)
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"slices"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"forge.lthn.ai/core/go/pkg/i18n"
|
||||
|
|
@ -43,8 +44,8 @@ func runPackageWizard(reg *repos.Registry, preselectedTypes []string) ([]string,
|
|||
var options []string
|
||||
|
||||
// Sort by name
|
||||
sort.Slice(allRepos, func(i, j int) bool {
|
||||
return allRepos[i].Name < allRepos[j].Name
|
||||
slices.SortFunc(allRepos, func(a, b *repos.Repo) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
for _, repo := range allRepos {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ package container
|
|||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/io"
|
||||
|
|
@ -42,17 +45,29 @@ var builtinTemplates = []Template{
|
|||
// It combines embedded templates with any templates found in the user's
|
||||
// .core/linuxkit directory.
|
||||
func ListTemplates() []Template {
|
||||
templates := make([]Template, len(builtinTemplates))
|
||||
copy(templates, builtinTemplates)
|
||||
return slices.Collect(ListTemplatesIter())
|
||||
}
|
||||
|
||||
// Check for user templates in .core/linuxkit/
|
||||
userTemplatesDir := getUserTemplatesDir()
|
||||
if userTemplatesDir != "" {
|
||||
userTemplates := scanUserTemplates(userTemplatesDir)
|
||||
templates = append(templates, userTemplates...)
|
||||
// ListTemplatesIter returns an iterator for all available LinuxKit templates.
|
||||
func ListTemplatesIter() iter.Seq[Template] {
|
||||
return func(yield func(Template) bool) {
|
||||
// Yield builtin templates
|
||||
for _, t := range builtinTemplates {
|
||||
if !yield(t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check for user templates in .core/linuxkit/
|
||||
userTemplatesDir := getUserTemplatesDir()
|
||||
if userTemplatesDir != "" {
|
||||
for _, t := range scanUserTemplates(userTemplatesDir) {
|
||||
if !yield(t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return templates
|
||||
}
|
||||
|
||||
// GetTemplate returns the content of a template by name.
|
||||
|
|
@ -182,9 +197,7 @@ func ExtractVariables(content string) (required []string, optional map[string]st
|
|||
}
|
||||
|
||||
// Convert set to slice
|
||||
for v := range requiredSet {
|
||||
required = append(required, v)
|
||||
}
|
||||
required = slices.Sorted(maps.Keys(requiredSet))
|
||||
|
||||
return required, optional
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func loopy(items []int) int {
|
|||
for _, v := range items {
|
||||
total += v
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
total += i
|
||||
}
|
||||
return total
|
||||
|
|
@ -146,8 +146,8 @@ func monster(x, y, z int) int {
|
|||
} else if x < -10 {
|
||||
result = 4
|
||||
}
|
||||
for i := 0; i < x; i++ {
|
||||
for j := 0; j < y; j++ {
|
||||
for i := range x {
|
||||
for j := range y {
|
||||
if i > j && j > 0 {
|
||||
result += i
|
||||
} else if i == j || i < 0 {
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ func (t *Toolkit) FindTODOs(dir string) ([]TODO, error) {
|
|||
var todos []TODO
|
||||
re := regexp.MustCompile(pattern)
|
||||
|
||||
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(stdout), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@ func (t *Toolkit) UncommittedFiles() ([]string, error) {
|
|||
return nil, fmt.Errorf("git status failed: %s\n%s", err, stderr)
|
||||
}
|
||||
var files []string
|
||||
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(stdout), "\n") {
|
||||
if len(line) > 3 {
|
||||
files = append(files, strings.TrimSpace(line[3:]))
|
||||
}
|
||||
|
|
@ -295,7 +295,7 @@ func (t *Toolkit) Lint(pkg string) ([]Finding, error) {
|
|||
}
|
||||
|
||||
var findings []Finding
|
||||
for _, line := range strings.Split(strings.TrimSpace(stderr), "\n") {
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(stderr), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
|
@ -325,7 +325,7 @@ func (t *Toolkit) ScanSecrets(dir string) ([]SecretLeak, error) {
|
|||
}
|
||||
|
||||
var leaks []SecretLeak
|
||||
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(stdout), "\n") {
|
||||
if line == "" || strings.HasPrefix(line, "RuleID") {
|
||||
continue
|
||||
}
|
||||
|
|
@ -374,7 +374,7 @@ func (t *Toolkit) TestCount(pkg string) (int, error) {
|
|||
return 0, fmt.Errorf("go test -list failed: %s\n%s", err, stderr)
|
||||
}
|
||||
count := 0
|
||||
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
|
||||
for line := range strings.SplitSeq(strings.TrimSpace(stdout), "\n") {
|
||||
if strings.HasPrefix(line, "Test") || strings.HasPrefix(line, "Benchmark") {
|
||||
count++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ type govulncheckOSV struct {
|
|||
}
|
||||
|
||||
type govulncheckAffect struct {
|
||||
Package *govulncheckPkg `json:"package,omitempty"`
|
||||
Ranges []govulncheckRange `json:"ranges,omitempty"`
|
||||
Package *govulncheckPkg `json:"package,omitempty"`
|
||||
Ranges []govulncheckRange `json:"ranges,omitempty"`
|
||||
Severity []govulncheckSeverity `json:"database_specific,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -72,8 +72,8 @@ type govulncheckSeverity struct {
|
|||
}
|
||||
|
||||
type govulncheckFind struct {
|
||||
OSV string `json:"osv"`
|
||||
Trace []govulncheckTrace `json:"trace"`
|
||||
OSV string `json:"osv"`
|
||||
Trace []govulncheckTrace `json:"trace"`
|
||||
}
|
||||
|
||||
type govulncheckTrace struct {
|
||||
|
|
@ -108,7 +108,7 @@ func ParseVulnCheckJSON(stdout, stderr string) (*VulnResult, error) {
|
|||
|
||||
// Parse line-by-line to gracefully skip malformed entries.
|
||||
// json.Decoder.More() hangs on non-JSON input, so we split first.
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
for line := range strings.SplitSeq(stdout, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ func (d *DevOps) Boot(ctx context.Context, opts BootOptions) error {
|
|||
// Wait for SSH to be ready and scan host key
|
||||
// We try for up to 60 seconds as the VM takes a moment to boot
|
||||
var lastErr error
|
||||
for i := 0; i < 30; i++ {
|
||||
for range 30 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -1,14 +1,21 @@
|
|||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
forge.lthn.ai/core/cli v0.0.1 h1:nqpc4Tv8a4H/ERei+/71DVQxkCFU8HPFJP4120qPXgk=
|
||||
forge.lthn.ai/core/cli v0.0.1/go.mod h1:xa3Nqw3sUtYYJ1k+1jYul18tgs6sBevCUsGsHJI1hHA=
|
||||
forge.lthn.ai/core/go v0.0.1 h1:ubk4nmkA3treOUNgPS28wKd1jB6cUlEQUV7jDdGa3zM=
|
||||
forge.lthn.ai/core/go v0.0.1/go.mod h1:59YsnuMaAGQUxIhX68oK2/HnhQJEPWL1iEZhDTrNCbY=
|
||||
forge.lthn.ai/core/go-agentic v0.0.1 h1:GSFIyLaP1nSmagUYtqh8Y0ETwoFRlH9VXZB8gKlYpcY=
|
||||
forge.lthn.ai/core/go-agentic v0.0.1/go.mod h1:b14WpcpYfg5DQCoqRHcMskQ/2HaOKfCU49EOB++OZOk=
|
||||
forge.lthn.ai/core/go-crypt v0.0.1 h1:fmFc2SJ/VOXDRjkcYoLWfL7lI4HfPJeVS/Na6zHHcvw=
|
||||
forge.lthn.ai/core/go-crypt v0.0.1/go.mod h1:/j/rUN2ZMV7x1B5BYxH3QdwkgZg0HNBw5XuyFZeyxBY=
|
||||
forge.lthn.ai/core/go-scm v0.0.1 h1:boiH2zK+28ChgM+KTjKFWEwyOt4sbdnkpnmLpo0aUgY=
|
||||
forge.lthn.ai/core/go-scm v0.0.1/go.mod h1:71zxrM+2nXlTzgnMctnpRmT/ZAFwHVxDj0bPK0pGPnY=
|
||||
forge.lthn.ai/core/go-store v0.1.0 h1:ONO4NfnFVey2QOE5JAZp5dQPI2pxRCHWAtQ+oYFJgGE=
|
||||
forge.lthn.ai/core/go-store v0.1.0/go.mod h1:FpUlLEX/ebyoxpk96F7ktr0vYvmFtC5Rpi9fi88UVqw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ=
|
||||
github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY=
|
||||
github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc=
|
||||
github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
|
@ -83,6 +90,7 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
|||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1 h1:x1cSEj4Ug5mpuZgUHLvUmlc5r//KHFn6iYiRSrRcVy4=
|
||||
github.com/kluctl/go-embed-python v0.0.0-3.13.1-20241219-1/go.mod h1:3ebNU9QBrNpUO+Hj6bHaGpkh5pymDHQ+wwVPHTE4mCE=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
|
@ -174,6 +182,7 @@ github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso
|
|||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ=
|
||||
|
|
@ -185,6 +194,7 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
|
|||
github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
|
||||
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package release
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -166,6 +167,17 @@ type ChangelogConfig struct {
|
|||
Exclude []string `yaml:"exclude"`
|
||||
}
|
||||
|
||||
// PublishersIter returns an iterator for the publishers.
|
||||
func (c *Config) PublishersIter() iter.Seq[PublisherConfig] {
|
||||
return func(yield func(PublisherConfig) bool) {
|
||||
for _, p := range c.Publishers {
|
||||
if !yield(p) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig loads release configuration from the .core/release.yaml file in the given directory.
|
||||
// If the config file does not exist, it returns DefaultConfig().
|
||||
// Returns an error if the file exists but cannot be parsed.
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ package generators
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Options holds common generation options.
|
||||
|
|
@ -62,11 +65,19 @@ func (r *Registry) Register(g Generator) {
|
|||
|
||||
// Languages returns all registered language identifiers.
|
||||
func (r *Registry) Languages() []string {
|
||||
langs := make([]string, 0, len(r.generators))
|
||||
for lang := range r.generators {
|
||||
langs = append(langs, lang)
|
||||
return slices.Collect(r.LanguagesIter())
|
||||
}
|
||||
|
||||
// LanguagesIter returns an iterator for all registered language identifiers.
|
||||
func (r *Registry) LanguagesIter() iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
// Sort keys for deterministic iteration
|
||||
for _, lang := range slices.Sorted(maps.Keys(r.generators)) {
|
||||
if !yield(lang) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return langs
|
||||
}
|
||||
|
||||
// dockerUserArgs returns Docker --user args for the current user on Unix systems.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue