From 4fa0e0310be3e967eb14712004f9cc934d55dcd8 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 30 Jan 2026 01:09:03 +0000 Subject: [PATCH] refactor(shared): add git status styles and check helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add to shared package: - Git status styles (dirty/ahead/behind/clean/conflict) - CheckMark() helper for presence indicators (✓/—) - Label() helper for key-value labels - CheckResult() helper for environment check output Update packages to use new shared utilities: - cmd/dev: use shared git styles for table cells - cmd/docs: use CheckMark() and Label() helpers - cmd/doctor: use CheckResult() and Success()/Error() helpers Co-Authored-By: Claude Opus 4.5 --- cmd/dev/dev.go | 20 ++++--------- cmd/docs/docs.go | 28 ++++++------------ cmd/docs/list.go | 22 ++++---------- cmd/doctor/doctor.go | 28 +++++++++--------- cmd/shared/styles.go | 68 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 64 deletions(-) diff --git a/cmd/dev/dev.go b/cmd/dev/dev.go index 86a6b02f..98f56630 100644 --- a/cmd/dev/dev.go +++ b/cmd/dev/dev.go @@ -45,22 +45,12 @@ var ( repoNameStyle = shared.RepoNameStyle ) -// Table styles for status display +// Table styles for status display (with padding for table cells) var ( - cellStyle = lipgloss.NewStyle(). - Padding(0, 1) - - dirtyStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#ef4444")). // red-500 - Padding(0, 1) - - aheadStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#22c55e")). // green-500 - Padding(0, 1) - - cleanStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#6b7280")). // gray-500 - Padding(0, 1) + cellStyle = lipgloss.NewStyle().Padding(0, 1) + dirtyStyle = shared.GitDirtyStyle.Padding(0, 1) + aheadStyle = shared.GitAheadStyle.Padding(0, 1) + cleanStyle = shared.GitCleanStyle.Padding(0, 1) ) // AddCommands registers the 'dev' command and all subcommands. diff --git a/cmd/docs/docs.go b/cmd/docs/docs.go index d5d8823d..9e12f577 100644 --- a/cmd/docs/docs.go +++ b/cmd/docs/docs.go @@ -2,31 +2,21 @@ package docs import ( - "github.com/charmbracelet/lipgloss" "github.com/host-uk/core/cmd/shared" "github.com/spf13/cobra" ) // Style and utility aliases from shared var ( - repoNameStyle = shared.RepoNameStyle - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle - headerStyle = shared.HeaderStyle - confirm = shared.Confirm -) - -// Package-specific styles -var ( - docsFoundStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#22c55e")) // green-500 - - docsMissingStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#6b7280")) // gray-500 - - docsFileStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#3b82f6")) // blue-500 + repoNameStyle = shared.RepoNameStyle + successStyle = shared.SuccessStyle + errorStyle = shared.ErrorStyle + dimStyle = shared.DimStyle + headerStyle = shared.HeaderStyle + confirm = shared.Confirm + docsFoundStyle = shared.SuccessStyle + docsMissingStyle = shared.DimStyle + docsFileStyle = shared.InfoStyle ) var docsCmd = &cobra.Command{ diff --git a/cmd/docs/list.go b/cmd/docs/list.go index b35fe777..c0bfd2be 100644 --- a/cmd/docs/list.go +++ b/cmd/docs/list.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/host-uk/core/cmd/shared" "github.com/spf13/cobra" ) @@ -41,22 +42,11 @@ func runDocsList(registryPath string) error { for _, repo := range reg.List() { info := scanRepoDocs(repo) - readme := docsMissingStyle.Render("—") - if info.Readme != "" { - readme = docsFoundStyle.Render("✓") - } + readme := shared.CheckMark(info.Readme != "") + claude := shared.CheckMark(info.ClaudeMd != "") + changelog := shared.CheckMark(info.Changelog != "") - claude := docsMissingStyle.Render("—") - if info.ClaudeMd != "" { - claude = docsFoundStyle.Render("✓") - } - - changelog := docsMissingStyle.Render("—") - if info.Changelog != "" { - changelog = docsFoundStyle.Render("✓") - } - - docsDir := docsMissingStyle.Render("—") + docsDir := shared.CheckMark(false) if len(info.DocsFiles) > 0 { docsDir = docsFoundStyle.Render(fmt.Sprintf("%d files", len(info.DocsFiles))) } @@ -78,7 +68,7 @@ func runDocsList(registryPath string) error { fmt.Println() fmt.Printf("%s %d with docs, %d without\n", - dimStyle.Render("Coverage:"), + shared.Label("Coverage"), withDocs, withoutDocs, ) diff --git a/cmd/doctor/doctor.go b/cmd/doctor/doctor.go index b2ce7c11..89a06ef5 100644 --- a/cmd/doctor/doctor.go +++ b/cmd/doctor/doctor.go @@ -43,14 +43,14 @@ func runDoctor(verbose bool) error { for _, c := range requiredChecks { ok, version := runCheck(c) if ok { - if verbose && version != "" { - fmt.Printf(" %s %s %s\n", successStyle.Render("✓"), c.name, dimStyle.Render(version)) + if verbose { + fmt.Println(shared.CheckResult(true, c.name, version)) } else { - fmt.Printf(" %s %s\n", successStyle.Render("✓"), c.name) + fmt.Println(shared.CheckResult(true, c.name, "")) } passed++ } else { - fmt.Printf(" %s %s - %s\n", errorStyle.Render("✗"), c.name, c.description) + fmt.Printf(" %s %s - %s\n", errorStyle.Render(shared.SymbolCross), c.name, c.description) failed++ } } @@ -60,14 +60,14 @@ func runDoctor(verbose bool) error { for _, c := range optionalChecks { ok, version := runCheck(c) if ok { - if verbose && version != "" { - fmt.Printf(" %s %s %s\n", successStyle.Render("✓"), c.name, dimStyle.Render(version)) + if verbose { + fmt.Println(shared.CheckResult(true, c.name, version)) } else { - fmt.Printf(" %s %s\n", successStyle.Render("✓"), c.name) + fmt.Println(shared.CheckResult(true, c.name, "")) } passed++ } else { - fmt.Printf(" %s %s - %s\n", dimStyle.Render("○"), c.name, dimStyle.Render(c.description)) + fmt.Printf(" %s %s - %s\n", dimStyle.Render(shared.SymbolSkip), c.name, dimStyle.Render(c.description)) optional++ } } @@ -75,16 +75,16 @@ func runDoctor(verbose bool) error { // Check GitHub access fmt.Println("\nGitHub Access:") if checkGitHubSSH() { - fmt.Printf(" %s SSH key found\n", successStyle.Render("✓")) + fmt.Println(shared.CheckResult(true, "SSH key found", "")) } else { - fmt.Printf(" %s SSH key missing - run: ssh-keygen && gh ssh-key add\n", errorStyle.Render("✗")) + fmt.Printf(" %s SSH key missing - run: ssh-keygen && gh ssh-key add\n", errorStyle.Render(shared.SymbolCross)) failed++ } if checkGitHubCLI() { - fmt.Printf(" %s CLI authenticated\n", successStyle.Render("✓")) + fmt.Println(shared.CheckResult(true, "CLI authenticated", "")) } else { - fmt.Printf(" %s CLI authentication - run: gh auth login\n", errorStyle.Render("✗")) + fmt.Printf(" %s CLI authentication - run: gh auth login\n", errorStyle.Render(shared.SymbolCross)) failed++ } @@ -95,12 +95,12 @@ func runDoctor(verbose bool) error { // Summary fmt.Println() if failed > 0 { - fmt.Printf("%s %d issues found\n", errorStyle.Render("Doctor:"), failed) + fmt.Println(shared.Error(fmt.Sprintf("Doctor: %d issues found", failed))) fmt.Println("\nInstall missing tools:") printInstallInstructions() return fmt.Errorf("%d required tools missing", failed) } - fmt.Printf("%s Environment ready\n", successStyle.Render("Doctor:")) + fmt.Println(shared.Success("Doctor: Environment ready")) return nil } diff --git a/cmd/shared/styles.go b/cmd/shared/styles.go index 9470bc07..55ae30df 100644 --- a/cmd/shared/styles.go +++ b/cmd/shared/styles.go @@ -240,6 +240,27 @@ var ( SeverityLowStyle = lipgloss.NewStyle().Foreground(ColourGray500) ) +// ───────────────────────────────────────────────────────────────────────────── +// Git Status Styles (for repo state indicators) +// ───────────────────────────────────────────────────────────────────────────── + +var ( + // GitDirtyStyle for uncommitted changes (red). + GitDirtyStyle = lipgloss.NewStyle().Foreground(ColourRed500) + + // GitAheadStyle for unpushed commits (green). + GitAheadStyle = lipgloss.NewStyle().Foreground(ColourGreen500) + + // GitBehindStyle for unpulled commits (amber). + GitBehindStyle = lipgloss.NewStyle().Foreground(ColourAmber500) + + // GitCleanStyle for clean state (gray). + GitCleanStyle = lipgloss.NewStyle().Foreground(ColourGray500) + + // GitConflictStyle for merge conflicts (red, bold). + GitConflictStyle = lipgloss.NewStyle().Bold(true).Foreground(ColourRed500) +) + // ───────────────────────────────────────────────────────────────────────────── // Box Styles (for bordered content) // ───────────────────────────────────────────────────────────────────────────── @@ -332,6 +353,53 @@ func StatusText(text string, style lipgloss.Style) string { return style.Render(text) } +// CheckMark returns a styled checkmark (✓) or dash (—) based on presence. +// Useful for showing presence/absence in tables and lists. +func CheckMark(present bool) string { + if present { + return SuccessStyle.Render(SymbolCheck) + } + return DimStyle.Render("—") +} + +// CheckMarkCustom returns a styled indicator with custom symbols and styles. +func CheckMarkCustom(present bool, presentStyle, absentStyle lipgloss.Style, presentSymbol, absentSymbol string) string { + if present { + return presentStyle.Render(presentSymbol) + } + return absentStyle.Render(absentSymbol) +} + +// Label returns a styled label for key-value display. +// Example: Label("Status") -> "Status:" in dim gray +func Label(text string) string { + return KeyStyle.Render(text + ":") +} + +// LabelValue returns a styled "label: value" pair. +// Example: LabelValue("Branch", "main") -> "Branch: main" +func LabelValue(label, value string) string { + return fmt.Sprintf("%s %s", Label(label), value) +} + +// LabelValueStyled returns a styled "label: value" pair with custom value style. +func LabelValueStyled(label, value string, valueStyle lipgloss.Style) string { + return fmt.Sprintf("%s %s", Label(label), valueStyle.Render(value)) +} + +// CheckResult formats a check result with name and optional version. +// Used for environment checks like `✓ go 1.22.0` or `✗ docker`. +func CheckResult(ok bool, name string, version string) string { + symbol := ErrorStyle.Render(SymbolCross) + if ok { + symbol = SuccessStyle.Render(SymbolCheck) + } + if version != "" { + return fmt.Sprintf(" %s %s %s", symbol, name, DimStyle.Render(version)) + } + return fmt.Sprintf(" %s %s", symbol, name) +} + // Bullet returns a bulleted item. func Bullet(text string) string { return fmt.Sprintf(" %s %s", DimStyle.Render(SymbolBullet), text)