diff options
| author | Paul Buetow <paul@buetow.org> | 2025-07-07 23:25:10 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-07-07 23:25:10 +0300 |
| commit | 4526c8a171dbe40762c116e5b8a404f20131d2b1 (patch) | |
| tree | ea3d544cbad995dabb616f4b6136e6e24a097524 /internal/cli | |
| parent | 64095a2c8d5a3a72c55d7bd0737c5542a5aeee09 (diff) | |
feat: add comprehensive showcase generation with metadata and images
- Add --showcase flag to generate project showcases using Claude
- Extract repository metadata (languages, commits, LOC, dates, license)
- Support image extraction from README files (local and remote)
- Add caching with --force flag to regenerate
- Add exclude_from_showcase config option
- Add standalone showcase mode (--showcase without sync)
- Sort projects by recent activity (avg age of last 100 commits)
- Output in Gemini Gemtext template format (.gmi.tpl)
- Fix backup location fetching when --backup flag not set
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal/cli')
| -rw-r--r-- | internal/cli/flags.go | 4 | ||||
| -rw-r--r-- | internal/cli/showcase_handler.go | 35 | ||||
| -rw-r--r-- | internal/cli/showcase_only_handler.go | 112 |
3 files changed, 151 insertions, 0 deletions
diff --git a/internal/cli/flags.go b/internal/cli/flags.go index 322ad5a..7640398 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -25,6 +25,8 @@ type Flags struct { Clean bool DeleteRepo string Backup bool + Showcase bool + Force bool } // ParseFlags parses command-line flags and returns the flags struct @@ -50,6 +52,8 @@ func ParseFlags() *Flags { flag.BoolVar(&f.Clean, "clean", false, "delete all repositories in work directory (with confirmation)") flag.StringVar(&f.DeleteRepo, "delete-repo", "", "delete specified repository from all configured organizations (with confirmation)") flag.BoolVar(&f.Backup, "backup", false, "enable syncing to backup locations") + flag.BoolVar(&f.Showcase, "showcase", false, "generate project showcase using Claude after syncing") + flag.BoolVar(&f.Force, "force", false, "force regeneration of cached data") flag.Parse() diff --git a/internal/cli/showcase_handler.go b/internal/cli/showcase_handler.go new file mode 100644 index 0000000..a3dfd26 --- /dev/null +++ b/internal/cli/showcase_handler.go @@ -0,0 +1,35 @@ +package cli + +import ( + "fmt" + "log" + + "codeberg.org/snonux/gitsyncer/internal/config" + "codeberg.org/snonux/gitsyncer/internal/showcase" +) + +// HandleShowcase handles the showcase generation after syncing +func HandleShowcase(cfg *config.Config, flags *Flags) int { + // Determine which repositories to process + var repoFilter []string + if flags.SyncRepo != "" { + // Only process the specific repository that was synced + repoFilter = []string{flags.SyncRepo} + fmt.Printf("\nGenerating showcase for %s...\n", flags.SyncRepo) + } else { + // Process all repositories for --sync-all or public sync operations + fmt.Println("\nGenerating project showcase for all repositories...") + } + + // Create showcase generator + generator := showcase.New(cfg, flags.WorkDir) + + // Generate showcase with optional filter + if err := generator.GenerateShowcase(repoFilter, flags.Force); err != nil { + log.Printf("ERROR: Failed to generate showcase: %v\n", err) + return 1 + } + + fmt.Println("Showcase generated successfully!") + return 0 +}
\ No newline at end of file diff --git a/internal/cli/showcase_only_handler.go b/internal/cli/showcase_only_handler.go new file mode 100644 index 0000000..ff2a82a --- /dev/null +++ b/internal/cli/showcase_only_handler.go @@ -0,0 +1,112 @@ +package cli + +import ( + "fmt" + "log" + + "codeberg.org/snonux/gitsyncer/internal/codeberg" + "codeberg.org/snonux/gitsyncer/internal/config" + "codeberg.org/snonux/gitsyncer/internal/github" + "codeberg.org/snonux/gitsyncer/internal/showcase" + "codeberg.org/snonux/gitsyncer/internal/sync" +) + +// HandleShowcaseOnly handles showcase generation without syncing +// It will clone repositories if they don't exist locally, but won't sync changes +func HandleShowcaseOnly(cfg *config.Config, flags *Flags) int { + // Get all repositories from all sources + allRepos, err := getAllRepositories(cfg) + if err != nil { + log.Printf("ERROR: Failed to get repositories: %v\n", err) + return 1 + } + + if len(allRepos) == 0 { + fmt.Println("No repositories found") + return 1 + } + + fmt.Printf("Found %d repositories total\n", len(allRepos)) + + // Create a minimal syncer just for cloning + syncer := sync.New(cfg, flags.WorkDir) + syncer.SetBackupEnabled(false) // Never use backup in showcase-only mode + + // Ensure repositories are cloned (but not synced) + fmt.Println("\nEnsuring repositories are cloned locally...") + for _, repo := range allRepos { + if err := syncer.EnsureRepositoryCloned(repo); err != nil { + fmt.Printf("WARNING: Failed to clone %s: %v\n", repo, err) + // Continue with other repos + } + } + + // Generate showcase for all repositories + fmt.Println("\nGenerating showcase for all repositories...") + generator := showcase.New(cfg, flags.WorkDir) + + // Pass empty filter to process all repos + if err := generator.GenerateShowcase(nil, flags.Force); err != nil { + log.Printf("ERROR: Failed to generate showcase: %v\n", err) + return 1 + } + + fmt.Println("Showcase generation completed!") + return 0 +} + +// getAllRepositories collects all unique repository names from all sources +func getAllRepositories(cfg *config.Config) ([]string, error) { + repoMap := make(map[string]bool) + + // Add configured repositories + for _, repo := range cfg.Repositories { + repoMap[repo] = true + } + + // Add Codeberg public repos if configured + if codebergOrg := cfg.FindCodebergOrg(); codebergOrg != nil { + fmt.Printf("Fetching public repositories from Codeberg user/org: %s...\n", codebergOrg.Name) + client := codeberg.NewClient(codebergOrg.Name, codebergOrg.CodebergToken) + + repos, err := client.ListPublicRepos() + if err != nil { + // Try as user + repos, err = client.ListUserPublicRepos() + if err != nil { + fmt.Printf("Warning: Failed to fetch Codeberg repos: %v\n", err) + } + } + + for _, repo := range repos { + repoMap[repo.Name] = true + } + } + + // Add GitHub public repos if configured + if githubOrg := cfg.FindGitHubOrg(); githubOrg != nil { + fmt.Printf("Fetching public repositories from GitHub user/org: %s...\n", githubOrg.Name) + client := github.NewClient(githubOrg.GitHubToken, githubOrg.Name) + + if client.HasToken() { + repos, err := client.ListPublicRepos() + if err != nil { + fmt.Printf("Warning: Failed to fetch GitHub repos: %v\n", err) + } else { + for _, repo := range repos { + repoMap[repo.Name] = true + } + } + } else { + fmt.Println("Warning: No GitHub token found, skipping GitHub repos") + } + } + + // Convert map to slice + var allRepos []string + for repo := range repoMap { + allRepos = append(allRepos, repo) + } + + return allRepos, nil +}
\ No newline at end of file |
