diff options
| author | mo khan <mo@mokhan.ca> | 2026-01-30 18:24:30 -0700 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2026-01-30 18:24:30 -0700 |
| commit | 5fdc5d8bde0feb99ef450e23c3c9924727951bcb (patch) | |
| tree | 52c80b99eb9bdeb2157919f034427bfa7c9a5218 /cmd/gitmal | |
| parent | feee7d43ef63ae607c6fd4cca88a356a93553ebe (diff) | |
feat: add cmd
Diffstat (limited to 'cmd/gitmal')
| -rw-r--r-- | cmd/gitmal/main.go | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/cmd/gitmal/main.go b/cmd/gitmal/main.go new file mode 100644 index 0000000..0c29a32 --- /dev/null +++ b/cmd/gitmal/main.go @@ -0,0 +1,266 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "runtime/pprof" + "strings" + + "mokhan.ca/antonmedv/gitmal/internal/generator" + "mokhan.ca/antonmedv/gitmal/internal/git" + + flag "github.com/spf13/pflag" +) + +var ( + flagOwner string + flagName string + flagOutput string + flagBranches string + flagDefaultBranch string + flagTheme string + flagPreviewThemes bool + flagMinify bool + flagGzip bool +) + +func main() { + if _, ok := os.LookupEnv("GITMAL_PPROF"); ok { + f, err := os.Create("cpu.prof") + if err != nil { + panic(err) + } + err = pprof.StartCPUProfile(f) + if err != nil { + panic(err) + } + defer f.Close() + defer pprof.StopCPUProfile() + memProf, err := os.Create("mem.prof") + if err != nil { + panic(err) + } + defer memProf.Close() + defer pprof.WriteHeapProfile(memProf) + } + + _, noFiles := os.LookupEnv("NO_FILES") + _, noCommitsList := os.LookupEnv("NO_COMMITS_LIST") + + flag.StringVar(&flagOwner, "owner", "", "Project owner") + flag.StringVar(&flagName, "name", "", "Project name") + flag.StringVar(&flagOutput, "output", "output", "Output directory for generated HTML files") + flag.StringVar(&flagBranches, "branches", "", "Regex for branches to include") + flag.StringVar(&flagDefaultBranch, "default-branch", "", "Default branch to use (autodetect master or main)") + flag.StringVar(&flagTheme, "theme", "github", "Style theme") + flag.BoolVar(&flagPreviewThemes, "preview-themes", false, "Preview available themes") + flag.BoolVar(&flagMinify, "minify", false, "Minify all generated HTML files") + flag.BoolVar(&flagGzip, "gzip", false, "Compress all generated HTML files") + flag.Usage = usage + flag.Parse() + + input := "." + args := flag.Args() + if len(args) == 1 { + input = args[0] + } + if len(args) > 1 { + panic("Multiple repos not supported yet") + } + + if flagPreviewThemes { + generator.PreviewThemes() + os.Exit(0) + } + + outputDir, err := filepath.Abs(flagOutput) + if err != nil { + panic(err) + } + + absInput, err := filepath.Abs(input) + if err != nil { + panic(err) + } + input = absInput + + if flagName == "" { + flagName = filepath.Base(input) + flagName = strings.TrimSuffix(flagName, ".git") + } + + themeColor, ok := generator.ThemeStyles[flagTheme] + if !ok { + panic("Invalid theme: " + flagTheme) + } + + branchesFilter, err := regexp.Compile(flagBranches) + if err != nil { + panic(err) + } + + branches, err := git.Branches(input, branchesFilter, flagDefaultBranch) + if err != nil { + panic(err) + } + + tags, err := git.Tags(input) + if err != nil { + panic(err) + } + + if flagDefaultBranch == "" { + if generator.ContainsBranch(branches, "master") { + flagDefaultBranch = "master" + } else if generator.ContainsBranch(branches, "main") { + flagDefaultBranch = "main" + } else { + generator.Echo("No default branch found. Specify one using --default-branch flag.") + os.Exit(1) + } + } + + if !generator.ContainsBranch(branches, flagDefaultBranch) { + generator.Echo(fmt.Sprintf("Default branch %q not found.", flagDefaultBranch)) + generator.Echo("Specify a valid branch using --default-branch flag.") + os.Exit(1) + } + + if yes, a, b := generator.HasConflictingBranchNames(branches); yes { + generator.Echo(fmt.Sprintf("Conflicting branchs %q and %q, both want to use %q dir name.", a, b, a.DirName())) + os.Exit(1) + } + + params := generator.Params{ + Owner: flagOwner, + Name: flagName, + RepoDir: input, + OutputDir: outputDir, + Style: flagTheme, + Dark: themeColor == "dark", + DefaultRef: git.NewRef(flagDefaultBranch), + } + + commits := make(map[string]git.Commit) + commitsFor := make(map[git.Ref][]git.Commit, len(branches)) + + for _, branch := range branches { + commitsFor[branch], err = git.Commits(branch, params.RepoDir) + if err != nil { + panic(err) + } + + for _, commit := range commitsFor[branch] { + if alreadyExisting, ok := commits[commit.Hash]; ok && alreadyExisting.Branch == params.DefaultRef { + continue + } + commit.Branch = branch + commits[commit.Hash] = commit + } + } + + for _, tag := range tags { + commitsForTag, err := git.Commits(git.NewRef(tag.Name), params.RepoDir) + if err != nil { + panic(err) + } + for _, commit := range commitsForTag { + if alreadyExisting, ok := commits[commit.Hash]; ok && !alreadyExisting.Branch.IsEmpty() { + continue + } + commits[commit.Hash] = commit + } + } + + generator.Echo(fmt.Sprintf("> %s: %d branches, %d tags, %d commits", params.Name, len(branches), len(tags), len(commits))) + + if err := generator.GenerateBranches(branches, flagDefaultBranch, params); err != nil { + panic(err) + } + + if err := generator.GenerateBranchesJSON(branches, commitsFor, params); err != nil { + panic(err) + } + + var defaultBranchFiles []git.Blob + + for i, branch := range branches { + generator.Echo(fmt.Sprintf("> [%d/%d] %s@%s", i+1, len(branches), params.Name, branch)) + params.Ref = branch + + if !noFiles { + files, err := git.Files(params.Ref, params.RepoDir) + if err != nil { + panic(err) + } + + if branch.String() == flagDefaultBranch { + defaultBranchFiles = files + } + + if err := generator.GenerateBlobs(files, params); err != nil { + panic(err) + } + + if err := generator.GenerateLists(files, params); err != nil { + panic(err) + } + } + + if !noCommitsList { + if err := generator.GenerateLogForBranch(commitsFor[branch], params); err != nil { + panic(err) + } + + if err := generator.GenerateCommitsJSON(commitsFor[branch], params); err != nil { + panic(err) + } + + if err := generator.GenerateCommitsAtom(commitsFor[branch], params); err != nil { + panic(err) + } + } + } + + params.Ref = git.NewRef(flagDefaultBranch) + + generator.Echo("> generating commits...") + if err := generator.GenerateCommits(commits, params); err != nil { + panic(err) + } + + if err := generator.GenerateTags(tags, params); err != nil { + panic(err) + } + + if err := generator.GenerateTagsAtom(tags, params); err != nil { + panic(err) + } + + if err := generator.GenerateReleasesAtom(tags, params); err != nil { + panic(err) + } + + if !noFiles { + if len(defaultBranchFiles) == 0 { + panic("No files found for default branch") + } + if err := generator.GenerateIndex(defaultBranchFiles, params); err != nil { + panic(err) + } + } + + if flagMinify || flagGzip { + generator.Echo("> post-processing HTML...") + if err := generator.PostProcessHTML(params.OutputDir, flagMinify, flagGzip); err != nil { + panic(err) + } + } +} + +func usage() { + fmt.Fprintf(os.Stderr, "Usage: gitmal [options] [path ...]\n") + flag.PrintDefaults() +} |
