summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-29 17:02:21 +0300
committerPaul Buetow <paul@buetow.org>2026-05-29 17:02:21 +0300
commita956a672859e92190149506197ad0fa682e964e2 (patch)
treea5f168aef3302ad9d633f1bca56f16b320fe3e19 /internal
parent649a41dc0f0cbfa697408db2f006d106ba655933 (diff)
feat(showcase): uq move cgit host and output dir into config
Diffstat (limited to 'internal')
-rw-r--r--internal/config/config.go60
-rw-r--r--internal/config/config_test.go49
-rw-r--r--internal/showcase/showcase.go33
-rw-r--r--internal/showcase/showcase_test.go64
4 files changed, 186 insertions, 20 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index c2084b1..afccf33 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -8,6 +8,10 @@ import (
"strings"
)
+const (
+ defaultShowcaseCgitHost = "https://cgit.f3s.buetow.org"
+)
+
// Organization represents a git organization with its host and name
type Organization struct {
Host string `json:"host"`
@@ -27,6 +31,8 @@ type Config struct {
WorkDir string `json:"work_dir,omitempty"` // Working directory for cloning repositories
ExcludeFromShowcase []string `json:"exclude_from_showcase,omitempty"` // Repository names to exclude from showcase
ShowcaseStatsBranches map[string]string `json:"showcase_stats_branches,omitempty"` // Repository names mapped to the branch used for showcase stats/code snippets
+ ShowcaseOutputDir string `json:"showcase_output_dir,omitempty"` // Directory where showcase files and assets are written
+ ShowcaseCgitHost string `json:"showcase_cgit_host,omitempty"` // Base URL for cgit links in showcase output
// SkipReleases maps a repository name to a list of tag names for which
// releases should NOT be created on any platform (GitHub/Codeberg)
SkipReleases map[string][]string `json:"skip_releases,omitempty"`
@@ -77,13 +83,20 @@ func Load(path string) (*Config, error) {
cfg.WorkDir = filepath.Join(home, "git", "gitsyncer-workdir")
}
- // Expand home directory in WorkDir if needed
- if strings.HasPrefix(cfg.WorkDir, "~/") {
- home, err := os.UserHomeDir()
+ // Expand home directory in WorkDir if needed.
+ expandedWorkDir, err := expandHomePath(cfg.WorkDir)
+ if err != nil {
+ return nil, err
+ }
+ cfg.WorkDir = expandedWorkDir
+
+ // Expand home directory in showcase output directory if configured.
+ if strings.TrimSpace(cfg.ShowcaseOutputDir) != "" {
+ expandedShowcaseOutputDir, err := expandHomePath(cfg.ShowcaseOutputDir)
if err != nil {
- return nil, fmt.Errorf("failed to get home directory: %w", err)
+ return nil, err
}
- cfg.WorkDir = filepath.Join(home, cfg.WorkDir[2:])
+ cfg.ShowcaseOutputDir = expandedShowcaseOutputDir
}
return &cfg, nil
@@ -195,3 +208,40 @@ func (o *Organization) IsSSH() bool {
return !o.IsGitHub() && !o.IsCodeberg() && !strings.HasPrefix(o.Host, "file://") &&
(strings.Contains(o.Host, "@") || strings.Contains(o.Host, ":"))
}
+
+// GetShowcaseOutputDir returns the configured showcase output directory when
+// present, otherwise the default output location.
+func (c *Config) GetShowcaseOutputDir() (string, error) {
+ if c != nil && strings.TrimSpace(c.ShowcaseOutputDir) != "" {
+ return expandHomePath(c.ShowcaseOutputDir)
+ }
+
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", fmt.Errorf("failed to get home directory: %w", err)
+ }
+ return filepath.Join(home, "git", "foo.zone-content", "gemtext", "about"), nil
+}
+
+// GetShowcaseCgitHost returns the configured cgit host, or the default host.
+func (c *Config) GetShowcaseCgitHost() string {
+ if c != nil {
+ host := strings.TrimSpace(c.ShowcaseCgitHost)
+ if host != "" {
+ return strings.TrimRight(host, "/")
+ }
+ }
+ return defaultShowcaseCgitHost
+}
+
+func expandHomePath(path string) (string, error) {
+ if strings.HasPrefix(path, "~/") {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", fmt.Errorf("failed to get home directory: %w", err)
+ }
+ return filepath.Join(home, path[2:]), nil
+ }
+
+ return path, nil
+}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 45b8024..eb81626 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -1,6 +1,7 @@
package config
import (
+ "path/filepath"
"strings"
"testing"
)
@@ -68,3 +69,51 @@ func TestFindOrganization_ReturnsPointerToStoredElement(t *testing.T) {
t.Fatalf("Organizations[0].Name = %q, want %q", cfg.Organizations[0].Name, "after")
}
}
+
+func TestGetShowcaseOutputDir_Default(t *testing.T) {
+ homeDir := t.TempDir()
+ t.Setenv("HOME", homeDir)
+
+ cfg := &Config{}
+ got, err := cfg.GetShowcaseOutputDir()
+ if err != nil {
+ t.Fatalf("GetShowcaseOutputDir() error = %v", err)
+ }
+
+ want := filepath.Join(homeDir, "git", "foo.zone-content", "gemtext", "about")
+ if got != want {
+ t.Fatalf("GetShowcaseOutputDir() = %q, want %q", got, want)
+ }
+}
+
+func TestGetShowcaseOutputDir_OverrideAndExpandHome(t *testing.T) {
+ homeDir := t.TempDir()
+ t.Setenv("HOME", homeDir)
+
+ cfg := &Config{
+ ShowcaseOutputDir: "~/custom/showcase",
+ }
+ got, err := cfg.GetShowcaseOutputDir()
+ if err != nil {
+ t.Fatalf("GetShowcaseOutputDir() error = %v", err)
+ }
+
+ want := filepath.Join(homeDir, "custom", "showcase")
+ if got != want {
+ t.Fatalf("GetShowcaseOutputDir() = %q, want %q", got, want)
+ }
+}
+
+func TestGetShowcaseCgitHost_DefaultAndOverride(t *testing.T) {
+ t.Parallel()
+
+ defaultCfg := &Config{}
+ if got := defaultCfg.GetShowcaseCgitHost(); got != "https://cgit.f3s.buetow.org" {
+ t.Fatalf("GetShowcaseCgitHost() default = %q, want %q", got, "https://cgit.f3s.buetow.org")
+ }
+
+ customCfg := &Config{ShowcaseCgitHost: "https://example.test/cgit/"}
+ if got := customCfg.GetShowcaseCgitHost(); got != "https://example.test/cgit" {
+ t.Fatalf("GetShowcaseCgitHost() override = %q, want %q", got, "https://example.test/cgit")
+ }
+}
diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go
index e32d144..ace9227 100644
--- a/internal/showcase/showcase.go
+++ b/internal/showcase/showcase.go
@@ -302,15 +302,20 @@ func (g *Generator) getRepositories() ([]string, error) {
}
func (g *Generator) buildProjectLinks(repoName string) (string, string, string) {
+ cfg := g.config
+ if cfg == nil {
+ cfg = &config.Config{}
+ }
+
codebergURL := ""
githubURL := ""
- cgitURL := fmt.Sprintf("https://cgit.f3s.buetow.org/%s/", repoName)
+ cgitURL := fmt.Sprintf("%s/%s/", cfg.GetShowcaseCgitHost(), repoName)
- if codebergOrg := g.config.FindCodebergOrg(); codebergOrg != nil {
+ if codebergOrg := cfg.FindCodebergOrg(); codebergOrg != nil {
codebergURL = fmt.Sprintf("https://codeberg.org/%s/%s", codebergOrg.Name, repoName)
}
- if githubOrg := g.config.FindGitHubOrg(); githubOrg != nil {
+ if githubOrg := cfg.FindGitHubOrg(); githubOrg != nil {
githubURL = fmt.Sprintf("https://github.com/%s/%s", githubOrg.Name, repoName)
}
@@ -519,7 +524,7 @@ func (g *Generator) resolveSummary(
func (g *Generator) collectAssets(repoName, repoPath, statsRepoPath string, metadata *RepoMetadata) ([]string, string, string, error) {
// Always extract images from README (not cached)
fmt.Printf("Extracting images from README...\n")
- showcaseDir, err := showcaseOutputDir()
+ showcaseDir, err := g.showcaseOutputDir()
if err != nil {
return nil, "", "", err
}
@@ -545,20 +550,18 @@ func (g *Generator) collectAssets(repoName, repoPath, statsRepoPath string, meta
return images, codeSnippet, codeLanguage, nil
}
-// showcaseOutputDir returns the canonical directory where showcase output files
-// (Gemtext, SVG, images) are written. Centralised here so all writers agree on
-// the path and a future change only needs to touch one place.
-func showcaseOutputDir() (string, error) {
- home, err := os.UserHomeDir()
- if err != nil {
- return "", fmt.Errorf("failed to get home directory: %w", err)
+// showcaseOutputDir returns the configured showcase output directory,
+// falling back to defaults when not configured.
+func (g *Generator) showcaseOutputDir() (string, error) {
+ if g.config == nil {
+ return (&config.Config{}).GetShowcaseOutputDir()
}
- return filepath.Join(home, "git", "foo.zone-content", "gemtext", "about"), nil
+ return g.config.GetShowcaseOutputDir()
}
// writeShowcaseFile writes the showcase content to the target file
func (g *Generator) writeShowcaseFile(content string) error {
- targetDir, err := showcaseOutputDir()
+ targetDir, err := g.showcaseOutputDir()
if err != nil {
return err
}
@@ -582,7 +585,7 @@ func (g *Generator) writeShowcaseFile(content string) error {
// writeRankHistorySVGFile generates an interactive SVG rank history graph and
// writes it to the same directory as the showcase Gemtext file.
func (g *Generator) writeRankHistorySVGFile(summaries []ProjectSummary) error {
- targetDir, err := showcaseOutputDir()
+ targetDir, err := g.showcaseOutputDir()
if err != nil {
return err
}
@@ -706,7 +709,7 @@ func (g *Generator) verifyImages(summary *ProjectSummary) error {
return nil
}
- showcaseDir, err := showcaseOutputDir()
+ showcaseDir, err := g.showcaseOutputDir()
if err != nil {
return err
}
diff --git a/internal/showcase/showcase_test.go b/internal/showcase/showcase_test.go
index dac32b5..09d707f 100644
--- a/internal/showcase/showcase_test.go
+++ b/internal/showcase/showcase_test.go
@@ -167,6 +167,32 @@ func TestFormatGemtext_IncludesCgitLink(t *testing.T) {
}
}
+func TestBuildProjectLinks_DefaultCgitHost(t *testing.T) {
+ t.Parallel()
+
+ g := &Generator{config: &config.Config{}}
+ _, _, cgitURL := g.buildProjectLinks("cpuinfo")
+
+ if cgitURL != "https://cgit.f3s.buetow.org/cpuinfo/" {
+ t.Fatalf("buildProjectLinks() cgit URL = %q, want %q", cgitURL, "https://cgit.f3s.buetow.org/cpuinfo/")
+ }
+}
+
+func TestBuildProjectLinks_ConfiguredCgitHost(t *testing.T) {
+ t.Parallel()
+
+ g := &Generator{
+ config: &config.Config{
+ ShowcaseCgitHost: "https://cgit.example.net/git/",
+ },
+ }
+ _, _, cgitURL := g.buildProjectLinks("cpuinfo")
+
+ if cgitURL != "https://cgit.example.net/git/cpuinfo/" {
+ t.Fatalf("buildProjectLinks() cgit URL = %q, want %q", cgitURL, "https://cgit.example.net/git/cpuinfo/")
+ }
+}
+
func TestFormatGemtext_ZeroProjectsReleasePercentagesAreZero(t *testing.T) {
t.Parallel()
@@ -366,6 +392,44 @@ func TestCollectAssets_ContinuesWhenSnippetExtractionFails(t *testing.T) {
}
}
+func TestCollectAssets_UsesConfiguredShowcaseOutputDir(t *testing.T) {
+ repoName := "demo"
+ repoPath := filepath.Join(t.TempDir(), repoName)
+ if err := os.MkdirAll(repoPath, 0755); err != nil {
+ t.Fatalf("mkdir repo: %v", err)
+ }
+
+ if err := os.WriteFile(filepath.Join(repoPath, "README.md"), []byte("![screenshot](shot.png)"), 0644); err != nil {
+ t.Fatalf("write README.md: %v", err)
+ }
+ if err := os.WriteFile(filepath.Join(repoPath, "shot.png"), []byte("png"), 0644); err != nil {
+ t.Fatalf("write shot.png: %v", err)
+ }
+
+ customOutputDir := filepath.Join(t.TempDir(), "custom-showcase-output")
+ g := &Generator{
+ config: &config.Config{
+ ShowcaseOutputDir: customOutputDir,
+ },
+ }
+
+ images, snippet, language, err := g.collectAssets(repoName, repoPath, repoPath, nil)
+ if err != nil {
+ t.Fatalf("collectAssets() error = %v", err)
+ }
+ if len(images) != 1 || images[0] != filepath.Join("showcase", repoName, "image-1.png") {
+ t.Fatalf("collectAssets() images = %#v", images)
+ }
+ if snippet != "" || language != "" {
+ t.Fatalf("collectAssets() snippet/language = %q/%q, want empty", snippet, language)
+ }
+
+ copiedPath := filepath.Join(customOutputDir, images[0])
+ if _, err := os.Stat(copiedPath); err != nil {
+ t.Fatalf("expected copied image at %s: %v", copiedPath, err)
+ }
+}
+
func TestSelectSummaryTool_DefaultPrefersOpencode(t *testing.T) {
t.Parallel()