diff --git a/.gitignore b/.gitignore
index 3ef7b65..005c643 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.claude
.serena
+.mcp.json
commands.nu
/gh-oss-stats
scripts/
diff --git a/README.md b/README.md
index 14866c6..6f9a784 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Track and display your merged PRs, commits, and contributions to external reposi
- π **External Contribution Tracking** - Discovers all your merged PRs to repos you don't own
- π **Comprehensive Stats** - Total PRs, commits, lines of code, and repository stars
- β **Smart Filtering** - Filter by minimum stars, exclude organizations
-- π 18 **Color Themes** - Match your profile's aesthetic
+- π **18 Color Themes** - Match your profile's aesthetic
- π¦ **Developer-Friendly** - Use as a Go library or standalone CLI, outputs JSON
@@ -72,9 +72,9 @@ jobs:
### Run it manually
If you don't want to wait for the end of the week, you can run this action manually
-1. G to `your github repo`
+1. Go to your github repo
2. Click `actions` tab
-3. On the left side, you shouls see your action named `Generate OSS Badge`, click on it
+3. On the left side, you should see your action named `Generate OSS Badge`, click on it
4. Click on `run workflow` drop down, then click `run workflow`
That is it. It will run and generate the badge for you.
diff --git a/cmd/gh-oss-stats/badgeConfig.go b/cmd/gh-oss-stats/badgeConfig.go
index a5bf0b9..d6fda04 100644
--- a/cmd/gh-oss-stats/badgeConfig.go
+++ b/cmd/gh-oss-stats/badgeConfig.go
@@ -13,6 +13,16 @@ type BadgeConfig struct {
output string
sort string
limit int
+ // Custom color overrides (empty = use theme default)
+ colorBackground string
+ colorBackgroundAlt string
+ colorText string
+ colorTextSecondary string
+ colorBorder string
+ colorAccent string
+ colorPositive string
+ colorNegative string
+ colorStar string
}
// newBadgeConfig creates a new BadgeConfig with default values
@@ -34,4 +44,14 @@ func (bf *BadgeConfig) registerBadgeFlags(fs *flag.FlagSet) {
fs.StringVar(&bf.output, "badge-output", "", "Badge output file (default: badge.svg)")
fs.StringVar(&bf.sort, "badge-sort", string(badge.DefaultSortBy), "Sort contributions by: prs, stars, commits")
fs.IntVar(&bf.limit, "badge-limit", badge.DefaultPRsLimit, "Number of contributions to show")
+
+ fs.StringVar(&bf.colorBackground, "badge-color-background", "", "Custom background color (hex, e.g. #1a1b26)")
+ fs.StringVar(&bf.colorBackgroundAlt, "badge-color-background-alt", "", "Custom alt background color (hex)")
+ fs.StringVar(&bf.colorText, "badge-color-text", "", "Custom primary text color (hex)")
+ fs.StringVar(&bf.colorTextSecondary, "badge-color-text-secondary", "", "Custom secondary text color (hex)")
+ fs.StringVar(&bf.colorBorder, "badge-color-border", "", "Custom border color (hex)")
+ fs.StringVar(&bf.colorAccent, "badge-color-accent", "", "Custom accent/brand color (hex)")
+ fs.StringVar(&bf.colorPositive, "badge-color-positive", "", "Custom positive/additions color (hex)")
+ fs.StringVar(&bf.colorNegative, "badge-color-negative", "", "Custom negative/deletions color (hex)")
+ fs.StringVar(&bf.colorStar, "badge-color-star", "", "Custom star count color (hex)")
}
diff --git a/cmd/gh-oss-stats/badgeConfig_test.go b/cmd/gh-oss-stats/badgeConfig_test.go
index 887cb2c..ebe077e 100644
--- a/cmd/gh-oss-stats/badgeConfig_test.go
+++ b/cmd/gh-oss-stats/badgeConfig_test.go
@@ -188,8 +188,115 @@ func TestBadgeConfigPointerReturn(t *testing.T) {
// Modifying bc1 should not affect bc2
bc1.style = "modified"
-
+
if bc2.style == "modified" {
t.Error("Modifying bc1 affected bc2; they should be independent instances")
}
}
+
+func TestBadgeColorFlagsRegistered(t *testing.T) {
+ bc := newBadgeConfig()
+ fs := flag.NewFlagSet("test", flag.ContinueOnError)
+ bc.registerBadgeFlags(fs)
+
+ colorFlags := []string{
+ "badge-color-background",
+ "badge-color-background-alt",
+ "badge-color-text",
+ "badge-color-text-secondary",
+ "badge-color-border",
+ "badge-color-accent",
+ "badge-color-positive",
+ "badge-color-negative",
+ "badge-color-star",
+ }
+
+ for _, name := range colorFlags {
+ t.Run(name, func(t *testing.T) {
+ f := fs.Lookup(name)
+ if f == nil {
+ t.Errorf("flag --%s not registered", name)
+ return
+ }
+ if f.DefValue != "" {
+ t.Errorf("flag --%s default = %q, want %q", name, f.DefValue, "")
+ }
+ })
+ }
+}
+
+func TestBadgeColorFlagDefaults(t *testing.T) {
+ bc := newBadgeConfig()
+ fs := flag.NewFlagSet("test", flag.ContinueOnError)
+ bc.registerBadgeFlags(fs)
+
+ // Parse with no color flags β all color fields should remain empty
+ if err := fs.Parse([]string{}); err != nil {
+ t.Fatalf("Parse failed: %v", err)
+ }
+
+ fields := []struct {
+ name string
+ got string
+ }{
+ {"colorBackground", bc.colorBackground},
+ {"colorBackgroundAlt", bc.colorBackgroundAlt},
+ {"colorText", bc.colorText},
+ {"colorTextSecondary", bc.colorTextSecondary},
+ {"colorBorder", bc.colorBorder},
+ {"colorAccent", bc.colorAccent},
+ {"colorPositive", bc.colorPositive},
+ {"colorNegative", bc.colorNegative},
+ {"colorStar", bc.colorStar},
+ }
+
+ for _, f := range fields {
+ if f.got != "" {
+ t.Errorf("%s default = %q, want empty string", f.name, f.got)
+ }
+ }
+}
+
+func TestBadgeColorFlagsParsed(t *testing.T) {
+ bc := newBadgeConfig()
+ fs := flag.NewFlagSet("test", flag.ContinueOnError)
+ bc.registerBadgeFlags(fs)
+
+ args := []string{
+ "--badge-color-background", "#0d1117",
+ "--badge-color-background-alt", "#161b22",
+ "--badge-color-text", "#c9d1d9",
+ "--badge-color-text-secondary", "#8b949e",
+ "--badge-color-border", "#30363d",
+ "--badge-color-accent", "#58a6ff",
+ "--badge-color-positive", "#3fb950",
+ "--badge-color-negative", "#f85149",
+ "--badge-color-star", "#e3b341",
+ }
+
+ if err := fs.Parse(args); err != nil {
+ t.Fatalf("Parse failed: %v", err)
+ }
+
+ tests := []struct {
+ name string
+ got string
+ want string
+ }{
+ {"colorBackground", bc.colorBackground, "#0d1117"},
+ {"colorBackgroundAlt", bc.colorBackgroundAlt, "#161b22"},
+ {"colorText", bc.colorText, "#c9d1d9"},
+ {"colorTextSecondary", bc.colorTextSecondary, "#8b949e"},
+ {"colorBorder", bc.colorBorder, "#30363d"},
+ {"colorAccent", bc.colorAccent, "#58a6ff"},
+ {"colorPositive", bc.colorPositive, "#3fb950"},
+ {"colorNegative", bc.colorNegative, "#f85149"},
+ {"colorStar", bc.colorStar, "#e3b341"},
+ }
+
+ for _, tt := range tests {
+ if tt.got != tt.want {
+ t.Errorf("%s = %q, want %q", tt.name, tt.got, tt.want)
+ }
+ }
+}
diff --git a/cmd/gh-oss-stats/badgeGeneration.go b/cmd/gh-oss-stats/badgeGeneration.go
index aebee13..1e953a7 100644
--- a/cmd/gh-oss-stats/badgeGeneration.go
+++ b/cmd/gh-oss-stats/badgeGeneration.go
@@ -3,11 +3,21 @@ package main
import (
"fmt"
"os"
+ "regexp"
"github.com/mabd-dev/gh-oss-stats/pkg/ossstats"
"github.com/mabd-dev/gh-oss-stats/pkg/ossstats/badge"
)
+var hexColorRE = regexp.MustCompile(`^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$`)
+
+func validateHexColor(flag, value string) error {
+ if !hexColorRE.MatchString(value) {
+ return fmt.Errorf("invalid color for --%s: %q (expected hex format, e.g. #rgb, #rrggbb, #rrggbbaa)", flag, value)
+ }
+ return nil
+}
+
func createBadgeOptions(conf BadgeConfig) (badge.BadgeOptions, error) {
badgeStyle, err := badge.BadgeStyleFromName(conf.style)
if err != nil {
@@ -33,12 +43,53 @@ func createBadgeOptions(conf BadgeConfig) (badge.BadgeOptions, error) {
os.Exit(1)
}
+ colorFlags := []struct {
+ flag string
+ value string
+ }{
+ {"badge-color-background", conf.colorBackground},
+ {"badge-color-background-alt", conf.colorBackgroundAlt},
+ {"badge-color-text", conf.colorText},
+ {"badge-color-text-secondary", conf.colorTextSecondary},
+ {"badge-color-border", conf.colorBorder},
+ {"badge-color-accent", conf.colorAccent},
+ {"badge-color-positive", conf.colorPositive},
+ {"badge-color-negative", conf.colorNegative},
+ {"badge-color-star", conf.colorStar},
+ }
+ for _, cf := range colorFlags {
+ if cf.value != "" {
+ if err := validateHexColor(cf.flag, cf.value); err != nil {
+ return badge.BadgeOptions{}, err
+ }
+ }
+ }
+
+ var customColors *badge.ThemeColors
+ if conf.colorBackground != "" || conf.colorBackgroundAlt != "" ||
+ conf.colorText != "" || conf.colorTextSecondary != "" ||
+ conf.colorBorder != "" || conf.colorAccent != "" ||
+ conf.colorPositive != "" || conf.colorNegative != "" || conf.colorStar != "" {
+ customColors = &badge.ThemeColors{
+ Background: conf.colorBackground,
+ BackgroundAlt: conf.colorBackgroundAlt,
+ Text: conf.colorText,
+ TextSecondary: conf.colorTextSecondary,
+ Border: conf.colorBorder,
+ Accent: conf.colorAccent,
+ Positive: conf.colorPositive,
+ Negative: conf.colorNegative,
+ Star: conf.colorStar,
+ }
+ }
+
return badge.BadgeOptions{
- Style: badgeStyle,
- Variant: badgeVariant,
- Theme: badgeTheme,
- SortBy: badgeSortBy,
- Limit: conf.limit,
+ Style: badgeStyle,
+ Variant: badgeVariant,
+ Theme: badgeTheme,
+ SortBy: badgeSortBy,
+ Limit: conf.limit,
+ CustomColors: customColors,
}, nil
}
diff --git a/cmd/gh-oss-stats/badgeGeneration_test.go b/cmd/gh-oss-stats/badgeGeneration_test.go
new file mode 100644
index 0000000..454997d
--- /dev/null
+++ b/cmd/gh-oss-stats/badgeGeneration_test.go
@@ -0,0 +1,334 @@
+package main
+
+import (
+ "testing"
+
+ "github.com/mabd-dev/gh-oss-stats/pkg/ossstats/badge"
+)
+
+func TestCreateBadgeOptionsNoCustomColors(t *testing.T) {
+ conf := BadgeConfig{
+ style: string(badge.DefaultBadgeStyle),
+ variant: string(badge.DefaultBadgeVariant),
+ theme: string(badge.DefaultBadgeTheme),
+ sort: string(badge.DefaultSortBy),
+ limit: badge.DefaultPRsLimit,
+ // all color fields left as zero value ("")
+ }
+
+ opts, err := createBadgeOptions(conf)
+ if err != nil {
+ t.Fatalf("createBadgeOptions failed: %v", err)
+ }
+
+ if opts.CustomColors != nil {
+ t.Errorf("CustomColors = %+v, want nil when no color flags are set", opts.CustomColors)
+ }
+}
+
+func TestCreateBadgeOptionsAllCustomColors(t *testing.T) {
+ conf := BadgeConfig{
+ style: string(badge.DefaultBadgeStyle),
+ variant: string(badge.DefaultBadgeVariant),
+ theme: string(badge.DefaultBadgeTheme),
+ sort: string(badge.DefaultSortBy),
+ limit: badge.DefaultPRsLimit,
+ colorBackground: "#0d1117",
+ colorBackgroundAlt: "#161b22",
+ colorText: "#c9d1d9",
+ colorTextSecondary: "#8b949e",
+ colorBorder: "#30363d",
+ colorAccent: "#58a6ff",
+ colorPositive: "#3fb950",
+ colorNegative: "#f85149",
+ colorStar: "#e3b341",
+ }
+
+ opts, err := createBadgeOptions(conf)
+ if err != nil {
+ t.Fatalf("createBadgeOptions failed: %v", err)
+ }
+
+ if opts.CustomColors == nil {
+ t.Fatal("CustomColors is nil, want non-nil when color flags are set")
+ }
+
+ c := opts.CustomColors
+ tests := []struct {
+ name string
+ got string
+ want string
+ }{
+ {"Background", c.Background, "#0d1117"},
+ {"BackgroundAlt", c.BackgroundAlt, "#161b22"},
+ {"Text", c.Text, "#c9d1d9"},
+ {"TextSecondary", c.TextSecondary, "#8b949e"},
+ {"Border", c.Border, "#30363d"},
+ {"Accent", c.Accent, "#58a6ff"},
+ {"Positive", c.Positive, "#3fb950"},
+ {"Negative", c.Negative, "#f85149"},
+ {"Star", c.Star, "#e3b341"},
+ }
+
+ for _, tt := range tests {
+ if tt.got != tt.want {
+ t.Errorf("CustomColors.%s = %q, want %q", tt.name, tt.got, tt.want)
+ }
+ }
+}
+
+func TestCreateBadgeOptionsPartialCustomColors(t *testing.T) {
+ // Only accent and star are set; all others should be empty strings in CustomColors.
+ conf := BadgeConfig{
+ style: string(badge.DefaultBadgeStyle),
+ variant: string(badge.DefaultBadgeVariant),
+ theme: string(badge.DefaultBadgeTheme),
+ sort: string(badge.DefaultSortBy),
+ limit: badge.DefaultPRsLimit,
+ colorAccent: "#ff6b6b",
+ colorStar: "#ffd700",
+ }
+
+ opts, err := createBadgeOptions(conf)
+ if err != nil {
+ t.Fatalf("createBadgeOptions failed: %v", err)
+ }
+
+ if opts.CustomColors == nil {
+ t.Fatal("CustomColors is nil, want non-nil when at least one color flag is set")
+ }
+
+ if opts.CustomColors.Accent != "#ff6b6b" {
+ t.Errorf("Accent = %q, want %q", opts.CustomColors.Accent, "#ff6b6b")
+ }
+ if opts.CustomColors.Star != "#ffd700" {
+ t.Errorf("Star = %q, want %q", opts.CustomColors.Star, "#ffd700")
+ }
+ // Unset fields should be empty strings (theme override logic in RenderSVG will skip them).
+ if opts.CustomColors.Background != "" {
+ t.Errorf("Background = %q, want empty string for unset flag", opts.CustomColors.Background)
+ }
+}
+
+func TestCreateBadgeOptionsSingleColorFlag(t *testing.T) {
+ colorFields := []struct {
+ name string
+ makeConf func() BadgeConfig
+ check func(*badge.ThemeColors) (string, string) // returns got, want
+ }{
+ {
+ "background",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorBackground = "#aabbcc"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.Background, "#aabbcc" },
+ },
+ {
+ "background-alt",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorBackgroundAlt = "#112233"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.BackgroundAlt, "#112233" },
+ },
+ {
+ "text",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorText = "#ffffff"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.Text, "#ffffff" },
+ },
+ {
+ "text-secondary",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorTextSecondary = "#aaaaaa"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.TextSecondary, "#aaaaaa" },
+ },
+ {
+ "border",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorBorder = "#333333"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.Border, "#333333" },
+ },
+ {
+ "accent",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorAccent = "#ff0000"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.Accent, "#ff0000" },
+ },
+ {
+ "positive",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorPositive = "#00ff00"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.Positive, "#00ff00" },
+ },
+ {
+ "negative",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorNegative = "#ff00ff"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.Negative, "#ff00ff" },
+ },
+ {
+ "star",
+ func() BadgeConfig {
+ c := baseConf()
+ c.colorStar = "#ffff00"
+ return c
+ },
+ func(c *badge.ThemeColors) (string, string) { return c.Star, "#ffff00" },
+ },
+ }
+
+ for _, tt := range colorFields {
+ t.Run(tt.name, func(t *testing.T) {
+ opts, err := createBadgeOptions(tt.makeConf())
+ if err != nil {
+ t.Fatalf("createBadgeOptions failed: %v", err)
+ }
+ if opts.CustomColors == nil {
+ t.Fatal("CustomColors is nil, want non-nil")
+ }
+ got, want := tt.check(opts.CustomColors)
+ if got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ })
+ }
+}
+
+// baseConf returns a minimal valid BadgeConfig with no color overrides.
+func baseConf() BadgeConfig {
+ return BadgeConfig{
+ style: string(badge.DefaultBadgeStyle),
+ variant: string(badge.DefaultBadgeVariant),
+ theme: string(badge.DefaultBadgeTheme),
+ sort: string(badge.DefaultSortBy),
+ limit: badge.DefaultPRsLimit,
+ }
+}
+
+func TestValidateHexColor(t *testing.T) {
+ tests := []struct {
+ value string
+ wantErr bool
+ }{
+ // Valid
+ {"#abc", false},
+ {"#ABC", false},
+ {"#1a2b3c", false},
+ {"#1A2B3C", false},
+ {"#00000000", false}, // 8-digit with alpha
+ // Invalid
+ {"", true}, // empty (callers skip empty, but function itself rejects)
+ {"abc", true}, // missing #
+ {"#gg0000", true}, // invalid hex digits
+ {"#12345", true}, // 5 digits
+ {"#1234567", true}, // 7 digits
+ {"#123456789", true}, // 9 digits
+ {"red", true}, // named color
+ {"rgb(0,0,0)", true}, // CSS function
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.value, func(t *testing.T) {
+ err := validateHexColor("badge-color-accent", tt.value)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("validateHexColor(%q) error = %v, wantErr %v", tt.value, err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestValidateHexColorErrorMessage(t *testing.T) {
+ err := validateHexColor("badge-color-accent", "notacolor")
+ if err == nil {
+ t.Fatal("expected error, got nil")
+ }
+ msg := err.Error()
+ if !contains(msg, "--badge-color-accent") {
+ t.Errorf("error message should mention the flag name, got: %q", msg)
+ }
+ if !contains(msg, "notacolor") {
+ t.Errorf("error message should mention the invalid value, got: %q", msg)
+ }
+}
+
+func TestCreateBadgeOptionsInvalidColor(t *testing.T) {
+ colorFlagFields := []struct {
+ flagName string
+ setConf func(*BadgeConfig)
+ }{
+ {"badge-color-background", func(c *BadgeConfig) { c.colorBackground = "red" }},
+ {"badge-color-background-alt", func(c *BadgeConfig) { c.colorBackgroundAlt = "blue" }},
+ {"badge-color-text", func(c *BadgeConfig) { c.colorText = "white" }},
+ {"badge-color-text-secondary", func(c *BadgeConfig) { c.colorTextSecondary = "gray" }},
+ {"badge-color-border", func(c *BadgeConfig) { c.colorBorder = "black" }},
+ {"badge-color-accent", func(c *BadgeConfig) { c.colorAccent = "invalid" }},
+ {"badge-color-positive", func(c *BadgeConfig) { c.colorPositive = "green" }},
+ {"badge-color-negative", func(c *BadgeConfig) { c.colorNegative = "rgb(255,0,0)" }},
+ {"badge-color-star", func(c *BadgeConfig) { c.colorStar = "gold" }},
+ }
+
+ for _, tt := range colorFlagFields {
+ t.Run(tt.flagName, func(t *testing.T) {
+ conf := baseConf()
+ tt.setConf(&conf)
+
+ _, err := createBadgeOptions(conf)
+ if err == nil {
+ t.Fatalf("expected error for invalid color on --%s, got nil", tt.flagName)
+ }
+ if !contains(err.Error(), "--"+tt.flagName) {
+ t.Errorf("error should mention the flag name --%s, got: %q", tt.flagName, err.Error())
+ }
+ })
+ }
+}
+
+func TestCreateBadgeOptionsValidColors(t *testing.T) {
+ validColors := []string{"#fff", "#FFF", "#1a2b3c", "#1A2B3C", "#00000000"}
+
+ for _, color := range validColors {
+ t.Run(color, func(t *testing.T) {
+ conf := baseConf()
+ conf.colorAccent = color
+
+ _, err := createBadgeOptions(conf)
+ if err != nil {
+ t.Errorf("unexpected error for valid color %q: %v", color, err)
+ }
+ })
+ }
+}
+
+func contains(s, sub string) bool {
+ return len(s) >= len(sub) && (s == sub || len(sub) == 0 ||
+ func() bool {
+ for i := 0; i <= len(s)-len(sub); i++ {
+ if s[i:i+len(sub)] == sub {
+ return true
+ }
+ }
+ return false
+ }())
+}
diff --git a/docs/TECHNICAL.md b/docs/TECHNICAL.md
index 035397f..6cf9446 100644
--- a/docs/TECHNICAL.md
+++ b/docs/TECHNICAL.md
@@ -183,14 +183,25 @@ These flags control badge generation and styling. Used by the main command (with
| Flag | Type | Default | Description |
|-------|-----------|-------------|-------------|
| --badge | boolean | false | Generate SVG Badge (main command only) |
+| --from-file | string | "" | Path to stats JSON file to generate badge from |
+| --data | string | "" | Stats as JSON string |
| --badge-style | string | summary | Badge style: `summary`, `compact`, `detailed` |
| --badge-variant | string | default | Badge variant: `default`, `text-based` |
| --badge-theme | string | dark | Color theme: `dark`, `light`, `nord`, `dracula`, `gruvbox-light`, `gruvbox-dark`, etc... |
| --badge-output | string | ./badge.svg | Output file path for generated badge |
| --badge-sort | string | prs | Sort contributions by: `prs`, `stars`, `commits` |
| --badge-limit | int | 5 | Number of contributions to display in detailed badge |
-
-
+| --badge-color-background | string | "" | Custom main background color (hex, e.g. `#1a1b26`) |
+| --badge-color-background-alt | string | "" | Custom alt background color (hex) |
+| --badge-color-text | string | "" | Custom primary text color (hex) |
+| --badge-color-text-secondary | string | "" | Custom secondary/label text color (hex) |
+| --badge-color-border | string | "" | Custom border/divider color (hex) |
+| --badge-color-accent | string | "" | Custom accent/brand color (hex) |
+| --badge-color-positive | string | "" | Custom positive/additions color (hex) |
+| --badge-color-negative | string | "" | Custom negative/deletions color (hex) |
+| --badge-color-star | string | "" | Custom star count color (hex) |
+
+> Custom color flags override individual colors from the selected theme. Unset flags retain the theme's color.
### Badge Generation
diff --git a/docs/badges/default-detailed-catppuccin-frappe.svg b/docs/badges/default-detailed-catppuccin-frappe.svg
index ab0cc0c..253a26a 100644
--- a/docs/badges/default-detailed-catppuccin-frappe.svg
+++ b/docs/badges/default-detailed-catppuccin-frappe.svg
@@ -155,8 +155,8 @@
font-family="Inter, system-ui, -apple-system, sans-serif"
font-size="16"
font-weight="bold"
- letter-spacing="0em">android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
android-public
class="repo-name"
+ letter-spacing="0em">android-public
Tomato
class="repo-name"
+ letter-spacing="0em">Tomato
JetpackComposeTracker
class="repo-name"
+ letter-spacing="0em">JetpackComposeTracker
nav3-recipes
class="repo-name"
+ letter-spacing="0em">nav3-recipes
cahier
class="repo-name"
+ letter-spacing="0em">cahier
Compose-Rolling-Number
class="repo-name"
+ letter-spacing="0em">Compose-Rolling-Number
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-catppuccin-latte.svg b/docs/badges/text-based-detailed-catppuccin-latte.svg
index 5d3d2f5..4ca43af 100644
--- a/docs/badges/text-based-detailed-catppuccin-latte.svg
+++ b/docs/badges/text-based-detailed-catppuccin-latte.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-catppuccin-macchiato.svg b/docs/badges/text-based-detailed-catppuccin-macchiato.svg
index 9cce519..5ec5ca2 100644
--- a/docs/badges/text-based-detailed-catppuccin-macchiato.svg
+++ b/docs/badges/text-based-detailed-catppuccin-macchiato.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-catppuccin-mocha.svg b/docs/badges/text-based-detailed-catppuccin-mocha.svg
index 2e7a202..f3df399 100644
--- a/docs/badges/text-based-detailed-catppuccin-mocha.svg
+++ b/docs/badges/text-based-detailed-catppuccin-mocha.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-dark.svg b/docs/badges/text-based-detailed-dark.svg
index 557221f..e91f205 100644
--- a/docs/badges/text-based-detailed-dark.svg
+++ b/docs/badges/text-based-detailed-dark.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-dracula.svg b/docs/badges/text-based-detailed-dracula.svg
index 2b5a6d6..170a446 100644
--- a/docs/badges/text-based-detailed-dracula.svg
+++ b/docs/badges/text-based-detailed-dracula.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-gruvbox-dark.svg b/docs/badges/text-based-detailed-gruvbox-dark.svg
index 7c46032..a37db02 100644
--- a/docs/badges/text-based-detailed-gruvbox-dark.svg
+++ b/docs/badges/text-based-detailed-gruvbox-dark.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-gruvbox-light.svg b/docs/badges/text-based-detailed-gruvbox-light.svg
index 83967fa..9b2a6b1 100644
--- a/docs/badges/text-based-detailed-gruvbox-light.svg
+++ b/docs/badges/text-based-detailed-gruvbox-light.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-light.svg b/docs/badges/text-based-detailed-light.svg
index 2afbc48..60834df 100644
--- a/docs/badges/text-based-detailed-light.svg
+++ b/docs/badges/text-based-detailed-light.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-monokai.svg b/docs/badges/text-based-detailed-monokai.svg
index 7a92663..4238294 100644
--- a/docs/badges/text-based-detailed-monokai.svg
+++ b/docs/badges/text-based-detailed-monokai.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-nord.svg b/docs/badges/text-based-detailed-nord.svg
index ba6c515..8d719ad 100644
--- a/docs/badges/text-based-detailed-nord.svg
+++ b/docs/badges/text-based-detailed-nord.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-one-dark-vivid.svg b/docs/badges/text-based-detailed-one-dark-vivid.svg
index 16b53b1..fbb7137 100644
--- a/docs/badges/text-based-detailed-one-dark-vivid.svg
+++ b/docs/badges/text-based-detailed-one-dark-vivid.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-one-dark.svg b/docs/badges/text-based-detailed-one-dark.svg
index 5d21dbf..0222874 100644
--- a/docs/badges/text-based-detailed-one-dark.svg
+++ b/docs/badges/text-based-detailed-one-dark.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-solarized-dark.svg b/docs/badges/text-based-detailed-solarized-dark.svg
index f580c66..fa08a59 100644
--- a/docs/badges/text-based-detailed-solarized-dark.svg
+++ b/docs/badges/text-based-detailed-solarized-dark.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-solarized-light.svg b/docs/badges/text-based-detailed-solarized-light.svg
index ba51075..901240d 100644
--- a/docs/badges/text-based-detailed-solarized-light.svg
+++ b/docs/badges/text-based-detailed-solarized-light.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-tokyo-night-light.svg b/docs/badges/text-based-detailed-tokyo-night-light.svg
index c05fecd..48c3a51 100644
--- a/docs/badges/text-based-detailed-tokyo-night-light.svg
+++ b/docs/badges/text-based-detailed-tokyo-night-light.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-tokyo-night-storm.svg b/docs/badges/text-based-detailed-tokyo-night-storm.svg
index 48a924a..9460796 100644
--- a/docs/badges/text-based-detailed-tokyo-night-storm.svg
+++ b/docs/badges/text-based-detailed-tokyo-night-storm.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/badges/text-based-detailed-tokyo-night.svg b/docs/badges/text-based-detailed-tokyo-night.svg
index 3e4cac9..39a1935 100644
--- a/docs/badges/text-based-detailed-tokyo-night.svg
+++ b/docs/badges/text-based-detailed-tokyo-night.svg
@@ -123,7 +123,7 @@
- android-public
+ @ibad-al-rahman/android-public
β
15
@@ -134,7 +134,7 @@
- Tomato
+ @nsh07/Tomato
β
15
@@ -145,7 +145,7 @@
- JetpackComposeTracker
+ @qamarelsafadi/JetpackComposeTracker
β
15
@@ -156,7 +156,7 @@
- nav3-recipes
+ @android/nav3-recipes
β
15
@@ -167,7 +167,7 @@
- cahier
+ @android/cahier
β
15
@@ -178,7 +178,7 @@
- Compose-Rolling-Number
+ @esatgozcu/Compose-Rolling-Number
β
15
diff --git a/docs/release-notes/v0.3.3.md b/docs/release-notes/v0.3.3.md
index 76c3e85..dfb8f52 100644
--- a/docs/release-notes/v0.3.3.md
+++ b/docs/release-notes/v0.3.3.md
@@ -1,6 +1,6 @@
# Release Notes: v0.3.3
-Release Date: Feb 13, 2025
+Release Date: Feb 13, 2026
Codename: Update template design + support 5 new color schemes
## β¨ Features
diff --git a/docs/release-notes/v0.3.4.md b/docs/release-notes/v0.3.4.md
new file mode 100644
index 0000000..cc2a510
--- /dev/null
+++ b/docs/release-notes/v0.3.4.md
@@ -0,0 +1,20 @@
+# Release Notes: v0.3.4
+
+Release Date: Feb 13, 2026
+Codename: Fix text overflow
+
+
+## β¨ Features
+
+- NEW: Added custom badge theme colors cli-flags
+
+
+## β»οΈ Improvements
+
+- Add **ownerName** to text-based variant, detailed style badge
+
+
+## π Bug Fixes
+
+- **Detailed Badge**: Prevent text-overflow by truncating repoName + ownerName
+
diff --git a/pkg/ossstats/badge/badge.go b/pkg/ossstats/badge/badge.go
index fd2128e..bdc2985 100644
--- a/pkg/ossstats/badge/badge.go
+++ b/pkg/ossstats/badge/badge.go
@@ -49,6 +49,36 @@ func RenderSVG(stats *ossstats.Stats, opts BadgeOptions) (string, error) {
// Get theme colors
colors := GetThemeColors(opts.Theme)
+ if opts.CustomColors != nil {
+ c := opts.CustomColors
+ if c.Background != "" {
+ colors.Background = c.Background
+ }
+ if c.BackgroundAlt != "" {
+ colors.BackgroundAlt = c.BackgroundAlt
+ }
+ if c.Text != "" {
+ colors.Text = c.Text
+ }
+ if c.TextSecondary != "" {
+ colors.TextSecondary = c.TextSecondary
+ }
+ if c.Border != "" {
+ colors.Border = c.Border
+ }
+ if c.Accent != "" {
+ colors.Accent = c.Accent
+ }
+ if c.Positive != "" {
+ colors.Positive = c.Positive
+ }
+ if c.Negative != "" {
+ colors.Negative = c.Negative
+ }
+ if c.Star != "" {
+ colors.Star = c.Star
+ }
+ }
// Prepare base template data
data := templateData{
@@ -74,11 +104,12 @@ func RenderSVG(stats *ossstats.Stats, opts BadgeOptions) (string, error) {
// Parse and execute template with custom functions
tmpl, err := template.New("badge").Funcs(template.FuncMap{
- "add": func(a, b int) int { return a + b },
- "sub": func(a, b int) int { return a - b },
- "mul": func(a, b int) int { return a * b },
- "mod": func(a, b int) int { return a % b },
- "div": func(a, b int) int { return a / b },
+ "add": func(a, b int) int { return a + b },
+ "sub": func(a, b int) int { return a - b },
+ "mul": func(a, b int) int { return a * b },
+ "mod": func(a, b int) int { return a % b },
+ "div": func(a, b int) int { return a / b },
+ "truncate": truncate,
}).Parse(tmplStr)
if err != nil {
return "", fmt.Errorf("failed to parse template: %w", err)
@@ -175,3 +206,11 @@ func getTemplateStr(
err := fmt.Errorf("unsupported badge variant: %s, and style: %s combinations", variant, style)
return "", err
}
+
+func truncate(maxLen int, s string) string {
+ runes := []rune(s)
+ if len(runes) <= maxLen {
+ return s
+ }
+ return string(runes[:maxLen-1]) + "β¦"
+}
diff --git a/pkg/ossstats/badge/badgeTemplates/defaults.go b/pkg/ossstats/badge/badgeTemplates/defaults.go
index 4b8d8b0..c96e75d 100644
--- a/pkg/ossstats/badge/badgeTemplates/defaults.go
+++ b/pkg/ossstats/badge/badgeTemplates/defaults.go
@@ -262,7 +262,7 @@ const DefaultDetailed = `
font-size="16"
font-weight="bold"
class="repo-name"
- letter-spacing="0em">{{$r.RepoName}}
+ letter-spacing="0em">{{truncate 26 $r.RepoName}}
@{{$r.Owner}}
+ letter-spacing="0em">@{{truncate 28 $r.Owner}}
- {{$r.RepoName}}
+ @{{truncate 52 (printf "%s/%s" $r.Owner $r.RepoName)}}
β
{{$r.Stars}}
diff --git a/pkg/ossstats/badge/badge_test.go b/pkg/ossstats/badge/badge_test.go
index 489eb1d..5854cee 100644
--- a/pkg/ossstats/badge/badge_test.go
+++ b/pkg/ossstats/badge/badge_test.go
@@ -597,6 +597,204 @@ func TestGetThemeColors(t *testing.T) {
}
}
+func TestTruncate(t *testing.T) {
+ tests := []struct {
+ name string
+ maxLen int
+ input string
+ want string
+ }{
+ {
+ name: "short string unchanged",
+ maxLen: 10,
+ input: "hello",
+ want: "hello",
+ },
+ {
+ name: "exact length unchanged",
+ maxLen: 5,
+ input: "hello",
+ want: "hello",
+ },
+ {
+ name: "over limit gets ellipsis",
+ maxLen: 5,
+ input: "hello world",
+ want: "hellβ¦",
+ },
+ {
+ name: "empty string unchanged",
+ maxLen: 10,
+ input: "",
+ want: "",
+ },
+ {
+ name: "multibyte runes truncated correctly",
+ maxLen: 5,
+ input: "ζ₯ζ¬θͺγγΉγι·γεε",
+ want: "ζ₯ζ¬θͺγβ¦",
+ },
+ {
+ name: "limit 1 yields only ellipsis",
+ maxLen: 1,
+ input: "ab",
+ want: "β¦",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := truncate(tt.maxLen, tt.input)
+ if got != tt.want {
+ t.Errorf("truncate(%d, %q) = %q, want %q", tt.maxLen, tt.input, got, tt.want)
+ }
+ })
+ }
+
+}
+
+func TestRenderSVG_DetailedTextTruncation(t *testing.T) {
+ tests := []struct {
+ name string
+ repoName string
+ owner string
+ wantRepoName string
+ wantOwner string
+ badgeVariant BadgeVariant
+ }{
+ {
+ name: "short names pass through unchanged",
+ repoName: "short-repo",
+ owner: "shortowner",
+ wantRepoName: "short-repo",
+ wantOwner: "@shortowner",
+ badgeVariant: VariantDefault,
+ },
+ {
+ name: "long repo name is truncated",
+ repoName: "this-repo-name-is-way-too-long-to-fit",
+ owner: "owner",
+ wantRepoName: "this-repo-name-is-way-tooβ¦",
+ wantOwner: "@owner",
+ badgeVariant: VariantDefault,
+ },
+ {
+ name: "long owner name is truncated",
+ repoName: "repo",
+ owner: "this-owner-name-is-way-too-long-to-fit",
+ wantRepoName: "repo",
+ wantOwner: "@this-owner-name-is-way-too-β¦",
+ badgeVariant: VariantDefault,
+ },
+ {
+ name: "both long names are truncated",
+ repoName: "a-very-long-repository-name-here",
+ owner: "a-very-long-organization-name-here",
+ wantRepoName: "a-very-long-repository-naβ¦",
+ wantOwner: "@a-very-long-organization-naβ¦",
+ badgeVariant: VariantDefault,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ stats := &ossstats.Stats{
+ Username: "testuser",
+ Summary: ossstats.Summary{
+ TotalProjects: 1,
+ TotalPRsMerged: 1,
+ },
+ Contributions: []ossstats.Contribution{
+ {
+ RepoName: tt.repoName,
+ Owner: tt.owner,
+ Stars: 100,
+ PRsMerged: 1,
+ },
+ },
+ }
+
+ opts := BadgeOptions{
+ Style: StyleDetailed,
+ Variant: tt.badgeVariant,
+ Theme: ThemeGithubDark,
+ Limit: 1,
+ }
+
+ svg, err := RenderSVG(stats, opts)
+ if err != nil {
+ t.Fatalf("RenderSVG() unexpected error: %v", err)
+ }
+
+ if !strings.Contains(svg, tt.wantRepoName) {
+ t.Errorf("SVG missing expected repo name %q", tt.wantRepoName)
+ }
+ if !strings.Contains(svg, tt.wantOwner) {
+ t.Errorf("SVG missing expected owner %q", tt.wantOwner)
+ }
+ })
+ }
+}
+
+func TestRenderSVG_TextBasedDetailedTextTruncation(t *testing.T) {
+ tests := []struct {
+ name string
+ repoName string
+ owner string
+ wantRepoName string
+ wantOwner string
+ badgeVariant BadgeVariant
+ }{
+ {
+ name: "both long names are truncated",
+ repoName: "a-veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-very-very-very-long-repository-name-here",
+ owner: "testowner",
+ wantRepoName: "@testowner/a-veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-verβ¦",
+ wantOwner: "@testowner",
+ badgeVariant: VariantTextBased,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ stats := &ossstats.Stats{
+ Username: "testuser",
+ Summary: ossstats.Summary{
+ TotalProjects: 1,
+ TotalPRsMerged: 1,
+ },
+ Contributions: []ossstats.Contribution{
+ {
+ RepoName: tt.repoName,
+ Owner: tt.owner,
+ Stars: 100,
+ PRsMerged: 1,
+ },
+ },
+ }
+
+ opts := BadgeOptions{
+ Style: StyleDetailed,
+ Variant: tt.badgeVariant,
+ Theme: ThemeGithubDark,
+ Limit: 1,
+ }
+
+ svg, err := RenderSVG(stats, opts)
+ if err != nil {
+ t.Fatalf("RenderSVG() unexpected error: %v", err)
+ }
+
+ if !strings.Contains(svg, tt.wantRepoName) {
+ t.Errorf("SVG missing expected repo name %q", tt.wantRepoName)
+ }
+ if !strings.Contains(svg, tt.wantOwner) {
+ t.Errorf("SVG missing expected owner %q", tt.wantOwner)
+ }
+ })
+ }
+}
+
func TestRenderSVG_CompactBadgeContent(t *testing.T) {
stats := &ossstats.Stats{
Username: "testuser",
diff --git a/pkg/ossstats/badge/types.go b/pkg/ossstats/badge/types.go
index 70b7284..c351eb9 100644
--- a/pkg/ossstats/badge/types.go
+++ b/pkg/ossstats/badge/types.go
@@ -2,9 +2,10 @@ package badge
// BadgeOptions contains all configuration for badge generation
type BadgeOptions struct {
- Style BadgeStyle
- Variant BadgeVariant
- Theme BadgeTheme
- SortBy SortBy // For detailed badge - how to sort contributions (default: prs)
- Limit int // For detailed badge - max contributions to show (default: 5)
+ Style BadgeStyle
+ Variant BadgeVariant
+ Theme BadgeTheme
+ SortBy SortBy // For detailed badge - how to sort contributions (default: prs)
+ Limit int // For detailed badge - max contributions to show (default: 5)
+ CustomColors *ThemeColors // Optional per-color overrides (applied on top of Theme)
}