Refactoring pass number 2. - staticgit - A git static site generator, the site you are viewing now!
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit 884ed087159dcf350421c692b8c530b7db260c28
 (DIR) parent 91bdb33c51f86b1de9afbcc1f73122c300049226
 (HTM) Author: Jay Scott <me@jay.scot>
       Date:   Fri, 12 Jul 2024 08:26:18 +0100
       
       Refactoring pass number 2.
       
       Diffstat:
         M main.go                             |     466 ++++++++++++++++++++++++++++++-
         D sealgit.go                          |     460 -------------------------------
       
       2 files changed, 454 insertions(+), 472 deletions(-)
       ---
 (DIR) diff --git a/main.go b/main.go
       @@ -3,15 +3,462 @@ package main
        import (
                "flag"
                "fmt"
       +        "html/template"
                "log"
                "os"
       +        "path/filepath"
       +        "regexp"
       +        "sort"
       +        "strings"
       +
       +        "github.com/go-git/go-git/v5"
       +        "github.com/go-git/go-git/v5/plumbing"
       +        "github.com/go-git/go-git/v5/plumbing/object"
       +)
       +
       +type BranchInfo struct {
       +        Name           string
       +        LastCommit     string
       +        LastCommitDate string
       +}
       +
       +type CommitInfo struct {
       +        Hash    string
       +        Author  string
       +        Date    string
       +        Message string
       +}
       +
       +type RepoInfo struct {
       +        Name        string
       +        Description string
       +        LastCommit  string
       +}
       +
       +const (
       +        baseTemplate = `
       +<!DOCTYPE html>
       +<html>
       +<head>
       +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
       +<meta name="viewport" content="width=device-width, initial-scale=1" />
       +<title>{{.Title}}</title>
       +<link rel="icon" type="image/png" href="{{.IconPath}}favicon.png" />
       +<link rel="stylesheet" type="text/css" href="{{.IconPath}}style.css" />
       +</head>
       +<body>
       +<table>
       +<tr><td><img src="{{.IconPath}}logo.png" alt="" width="32" height="32" /></td>
       +<td><span class="desc">{{.Title}}</span></td></tr><tr><td></td><td>
       +</td></tr>
       +</table>
       +<hr/>
       +<div id="content">
       +{{template "content" .}}
       +</div>
       +</body>
       +</html>
       +`
       +
       +        branchesContent = `
       +{{define "content"}}
       +<h1>{{.RepoName}} - Branches</h1>
       +<table>
       +<thead>
       +<tr><td><b>Branch Name</b></td><td><b>Last Commit</b></td><td><b>Last Commit Date</b></td></tr>
       +</thead>
       +<tbody>
       +{{range .Branches}}
       +<tr><td>{{.Name}}</td><td>{{.LastCommit}}</td><td>{{.LastCommitDate}}</td></tr>
       +{{end}}
       +</tbody>
       +</table>
       +{{end}}
       +`
       +
       +        commitHistoryContent = `
       +{{define "content"}}
       +<h1>{{.RepoName}} - Commit History</h1>
       +<table>
       +<thead>
       +<tr><td><b>Hash</b></td><td><b>Author</b></td><td><b>Date</b></td><td><b>Message</b></td></tr>
       +</thead>
       +<tbody>
       +{{range .Commits}}
       +<tr><td>{{.Hash}}</td><td>{{.Author}}</td><td>{{.Date}}</td><td>{{.Message}}</td></tr>
       +{{end}}
       +</tbody>
       +</table>
       +{{end}}
       +`
       +
       +        indexContent = `
       +{{define "content"}}
       +<table id="index">
       +<thead>
       +<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Last commit</b></td><td><b>Links</b></td></tr>
       +</thead>
       +<tbody>
       +{{range $group, $repos := .Repos}}
       +<tr><td colspan="4"><b>{{if eq $group ""}} {{else}}<hr>{{end}}</b></td></tr>
       +  {{range $repos}}
       +  <tr>
       +    <td>{{.Name}}</td>
       +    <td>{{.Description}}</td>
       +    <td>| {{.LastCommit}} | </td>
       +    <td>
       +      <a href="{{.Name}}/README.html">readme</a> |
       +      <a href="{{.Name}}/commits.html">commits</a> |
       +      <a href="{{.Name}}/branches.html">branches</a>
       +    </td>
       +  </tr>
       +  {{end}}
       +{{end}}
       +</tbody>
       +</table>
       +{{end}}
       +`
       +        readmeContent = `
       +{{define "content"}}
       +<h1>{{.RepoName}}</h1>
       +<pre>{{.ReadmeContent}}</pre>
       +{{end}}
       +`
       +)
       +
       +var (
       +        reposPath  string
       +        groupFlag  bool
       +        ignoreDirs map[string]bool
       +        outputRoot string
       +
       +        branchesTmpl      = template.Must(template.New("base").Parse(baseTemplate + branchesContent))
       +        commitHistoryTmpl = template.Must(template.New("base").Parse(baseTemplate + commitHistoryContent))
       +        indexTmpl         = template.Must(template.New("base").Parse(baseTemplate + indexContent))
       +        readmeTmpl        = template.Must(template.New("base").Parse(baseTemplate + readmeContent))
        )
        
       +func generateIndexHTML(repoInfos []RepoInfo) error {
       +        groupedRepos := groupRepos(repoInfos)
       +        indexOutputPath := filepath.Join(outputRoot, "index.html")
       +
       +        indexFile, err := os.Create(indexOutputPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to create index HTML file: %w", err)
       +        }
       +        defer indexFile.Close()
       +
       +        return indexTmpl.Execute(indexFile, struct {
       +                Title    string
       +                IconPath string
       +                Repos    map[string][]RepoInfo
       +        }{
       +                Title:    "git clone git@git.jay.scot:<reponame>",
       +                IconPath: "./",
       +                Repos:    groupedRepos,
       +        })
       +}
       +
       +func getBranchInfo(repo *git.Repository) ([]BranchInfo, error) {
       +        branches, err := repo.Branches()
       +        if err != nil {
       +                return nil, fmt.Errorf("failed to get branches: %w", err)
       +        }
       +
       +        var branchInfos []BranchInfo
       +        err = branches.ForEach(func(branch *plumbing.Reference) error {
       +                commit, err := repo.CommitObject(branch.Hash())
       +                if err != nil {
       +                        return fmt.Errorf("failed to get commit for branch %s: %w", branch.Name().Short(), err)
       +                }
       +
       +                branchInfos = append(branchInfos, BranchInfo{
       +                        Name:           branch.Name().Short(),
       +                        LastCommit:     commit.Hash.String()[:7],
       +                        LastCommitDate: commit.Author.When.Format("02 Jan 2006 15:04:05"),
       +                })
       +                return nil
       +        })
       +
       +        if err != nil {
       +                return nil, fmt.Errorf("failed to iterate over branches: %w", err)
       +        }
       +
       +        return branchInfos, nil
       +}
       +
       +func getCommitHistory(repo *git.Repository) ([]CommitInfo, error) {
       +        ref, err := repo.Head()
       +        if err != nil {
       +                return nil, fmt.Errorf("failed to get HEAD reference: %w", err)
       +        }
       +
       +        commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
       +        if err != nil {
       +                return nil, fmt.Errorf("failed to get commit log: %w", err)
       +        }
       +
       +        var commitHistory []CommitInfo
       +        err = commits.ForEach(func(c *object.Commit) error {
       +                commitHistory = append(commitHistory, CommitInfo{
       +                        Hash:    c.Hash.String()[:7],
       +                        Author:  c.Author.Name,
       +                        Date:    c.Author.When.Format("02 Jan 2006 15:04:05"),
       +                        Message: strings.Split(c.Message, "\n")[0], // Get only the first line of the commit message
       +                })
       +                return nil
       +        })
       +
       +        if err != nil {
       +                return nil, fmt.Errorf("failed to iterate over commits: %w", err)
       +        }
       +
       +        return commitHistory, nil
       +}
       +
       +func getDescription(repoPath string) string {
       +        descPath := filepath.Join(repoPath, "description")
       +        description, _ := os.ReadFile(descPath)
       +        return strings.TrimSpace(string(description))
       +}
       +
       +func getReadme(repoPath string) (string, error) {
       +        readmeFiles := []string{"README.md", "README.txt", "README"}
       +        repo, err := git.PlainOpen(repoPath)
       +        if err != nil {
       +                return "", fmt.Errorf("failed to open git repository: %w", err)
       +        }
       +
       +        headRef, err := repo.Head()
       +        if err != nil {
       +                return "", fmt.Errorf("failed to get HEAD reference: %w", err)
       +        }
       +
       +        commit, err := repo.CommitObject(headRef.Hash())
       +        if err != nil {
       +                return "", fmt.Errorf("failed to get commit object: %w", err)
       +        }
       +
       +        tree, err := commit.Tree()
       +        if err != nil {
       +                return "", fmt.Errorf("failed to get tree: %w", err)
       +        }
       +
       +        for _, fileName := range readmeFiles {
       +                file, err := tree.File(fileName)
       +                if err != nil {
       +                        continue
       +                }
       +
       +                content, err := file.Contents()
       +                if err != nil {
       +                        return "", fmt.Errorf("failed to read file contents: %w", err)
       +                }
       +
       +                return content, nil
       +        }
       +
       +        return "No README found!", nil
       +}
       +
       +func getRepoInfo(repoPath string) (RepoInfo, error) {
       +        repo, err := git.PlainOpen(repoPath)
       +        if err != nil {
       +                return RepoInfo{}, fmt.Errorf("failed to open repository: %w", err)
       +        }
       +
       +        headRef, err := repo.Head()
       +        if err != nil {
       +                return RepoInfo{}, fmt.Errorf("failed to get HEAD reference: %w", err)
       +        }
       +
       +        commit, err := repo.CommitObject(headRef.Hash())
       +        if err != nil {
       +                return RepoInfo{}, fmt.Errorf("failed to get commit object: %w", err)
       +        }
       +
       +        description := getDescription(repoPath)
       +
       +        return RepoInfo{
       +                Name:        filepath.Base(repoPath),
       +                Description: description,
       +                LastCommit:  commit.Committer.When.Format("02 Jan 2006"),
       +        }, nil
       +}
       +
       +func groupRepos(repos []RepoInfo) map[string][]RepoInfo {
       +        groupedRepos := make(map[string][]RepoInfo)
       +        for _, repo := range repos {
       +                group := getGroup(repo.Description)
       +                groupedRepos[group] = append(groupedRepos[group], repo)
       +        }
       +
       +        for _, repoList := range groupedRepos {
       +                sort.Slice(repoList, func(i, j int) bool {
       +                        return strings.ToLower(repoList[i].Name) < strings.ToLower(repoList[j].Name)
       +                })
       +        }
       +
       +        return groupedRepos
       +}
       +
       +func getGroup(description string) string {
       +        if groupFlag {
       +                groupRegex := regexp.MustCompile(`\[(.*?)\]`)
       +                matches := groupRegex.FindStringSubmatch(description)
       +                if len(matches) > 1 {
       +                        return matches[1]
       +                }
       +        }
       +        return ""
       +}
       +
       +func parseIgnoreDirs(ignoreDirs string) map[string]bool {
       +        ignoreMap := make(map[string]bool)
       +        for _, dir := range strings.Split(ignoreDirs, ",") {
       +                if trimmedDir := strings.TrimSpace(dir); trimmedDir != "" {
       +                        ignoreMap[trimmedDir] = true
       +                }
       +        }
       +        return ignoreMap
       +}
       +
       +func processBranches(repoName, repoPath, outputDir string) error {
       +        repo, err := git.PlainOpen(repoPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to open git repository: %w", err)
       +        }
       +
       +        branches, err := getBranchInfo(repo)
       +        if err != nil {
       +                return fmt.Errorf("failed to get branch information: %w", err)
       +        }
       +
       +        outputPath := filepath.Join(outputDir, "branches.html")
       +
       +        f, err := os.Create(outputPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to create branches HTML file: %w", err)
       +        }
       +        defer f.Close()
       +
       +        return branchesTmpl.Execute(f, struct {
       +                Title    string
       +                IconPath string
       +                RepoName string
       +                Branches []BranchInfo
       +        }{
       +                Title:    repoName + " - Branches",
       +                IconPath: "../",
       +                RepoName: repoName,
       +                Branches: branches,
       +        })
       +}
       +
       +func processCommitHistory(repoName, repoPath, outputDir string) error {
       +        repo, err := git.PlainOpen(repoPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to open git repository: %w", err)
       +        }
       +
       +        commits, err := getCommitHistory(repo)
       +        if err != nil {
       +                return fmt.Errorf("failed to get commit history: %w", err)
       +        }
       +
       +        outputPath := filepath.Join(outputDir, "commits.html")
       +
       +        f, err := os.Create(outputPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to create commit history HTML file: %w", err)
       +        }
       +        defer f.Close()
       +
       +        return commitHistoryTmpl.Execute(f, struct {
       +                Title    string
       +                IconPath string
       +                RepoName string
       +                Commits  []CommitInfo
       +        }{
       +                Title:    repoName + " - History",
       +                IconPath: "../",
       +                RepoName: repoName,
       +                Commits:  commits,
       +        })
       +}
       +
       +func processReadme(repoName, repoPath, outputDir string) error {
       +        readme, err := getReadme(repoPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to get README: %w", err)
       +        }
       +
       +        outputPath := filepath.Join(outputDir, "README.html")
       +
       +        f, err := os.Create(outputPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to create README HTML file: %w", err)
       +        }
       +        defer f.Close()
       +
       +        return readmeTmpl.Execute(f, struct {
       +                Title         string
       +                IconPath      string
       +                RepoName      string
       +                ReadmeContent string
       +        }{
       +                Title:         repoName + " - Readme!",
       +                IconPath:      "../",
       +                RepoName:      repoName,
       +                ReadmeContent: readme,
       +        })
       +}
       +
       +func processRepositories() error {
       +        repos, err := os.ReadDir(reposPath)
       +        if err != nil {
       +                return fmt.Errorf("failed to read repos directory: %w", err)
       +        }
       +
       +        var repoInfos []RepoInfo
       +        for _, r := range repos {
       +                if r.IsDir() && !ignoreDirs[r.Name()] {
       +                        repoPath := filepath.Join(reposPath, r.Name())
       +                        repoInfo, err := getRepoInfo(repoPath)
       +                        if err != nil {
       +                                fmt.Printf("Failed to get info for repo %s: %v\n", r.Name(), err)
       +                                continue
       +                        }
       +                        repoInfos = append(repoInfos, repoInfo)
       +
       +                        outputDir := filepath.Join(outputRoot, r.Name())
       +                        if err := os.MkdirAll(outputDir, 0755); err != nil {
       +                                fmt.Printf("Failed to create output directory for repo %s: %v\n", r.Name(), err)
       +                                continue
       +                        }
       +
       +                        if err := processReadme(r.Name(), repoPath, outputDir); err != nil {
       +                                fmt.Printf("Failed to process README for repo %s: %v\n", r.Name(), err)
       +                        }
       +
       +                        if err := processCommitHistory(r.Name(), repoPath, outputDir); err != nil {
       +                                fmt.Printf("Failed to process commit history for repo %s: %v\n", r.Name(), err)
       +                        }
       +
       +                        if err := processBranches(r.Name(), repoPath, outputDir); err != nil {
       +                                fmt.Printf("Failed to process branches for repo %s: %v\n", r.Name(), err)
       +                        }
       +                }
       +        }
       +
       +        return generateIndexHTML(repoInfos)
       +}
       +
        func main() {
       -        reposPath := flag.String("p", "", "Path to the git repositories (required)")
       -        groupFlag := flag.Bool("g", false, "Group repositories based on description tags")
       -        ignoreDirs := flag.String("i", "", "Directories to ignore (comma-separated)")
       -        outputRoot := flag.String("o", ".", "Root path where output directories will be created")
       +        flag.StringVar(&reposPath, "p", "", "Path to the git repositories (required)")
       +        flag.BoolVar(&groupFlag, "g", false, "Group repositories based on description tags")
       +        ignoreFlag := flag.String("i", "", "Directories to ignore (comma-separated)")
       +        flag.StringVar(&outputRoot, "o", ".", "Root path where output directories will be created")
        
                flag.Usage = func() {
                        fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0])
       @@ -23,19 +470,14 @@ func main() {
        
                flag.Parse()
        
       -        if *reposPath == "" {
       +        if reposPath == "" {
                        flag.Usage()
                        os.Exit(1)
                }
        
       -        cfg := &Config{
       -                ReposPath:  *reposPath,
       -                GroupFlag:  *groupFlag,
       -                IgnoreDirs: parseIgnoreDirs(*ignoreDirs),
       -                OutputRoot: *outputRoot,
       -        }
       +        ignoreDirs = parseIgnoreDirs(*ignoreFlag)
        
       -        if err := processRepositories(cfg); err != nil {
       +        if err := processRepositories(); err != nil {
                        log.Fatalf("Error processing repositories: %v", err)
                }
        }
 (DIR) diff --git a/sealgit.go b/sealgit.go
       @@ -1,460 +0,0 @@
       -package main
       -
       -import (
       -        "fmt"
       -        "html/template"
       -        "os"
       -        "path/filepath"
       -        "regexp"
       -        "sort"
       -        "strings"
       -
       -        git "github.com/go-git/go-git/v5"
       -        "github.com/go-git/go-git/v5/plumbing"
       -        "github.com/go-git/go-git/v5/plumbing/object"
       -)
       -
       -type BranchInfo struct {
       -        Name           string
       -        LastCommit     string
       -        LastCommitDate string
       -}
       -
       -type CommitInfo struct {
       -        Hash    string
       -        Author  string
       -        Date    string
       -        Message string
       -}
       -
       -type Config struct {
       -        ReposPath  string
       -        GroupFlag  bool
       -        IgnoreDirs map[string]bool
       -        OutputRoot string
       -}
       -
       -type RepoInfo struct {
       -        Name        string
       -        Description string
       -        LastCommit  string
       -        Group       string
       -}
       -
       -const (
       -        baseTemplate = `
       -<!DOCTYPE html>
       -<html>
       -<head>
       -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
       -<meta name="viewport" content="width=device-width, initial-scale=1" />
       -<title>{{.Title}}</title>
       -<link rel="icon" type="image/png" href="{{.IconPath}}favicon.png" />
       -<link rel="stylesheet" type="text/css" href="{{.IconPath}}style.css" />
       -</head>
       -<body>
       -<table>
       -<tr><td><img src="{{.IconPath}}logo.png" alt="" width="32" height="32" /></td>
       -<td><span class="desc">{{.Title}}</span></td></tr><tr><td></td><td>
       -</td></tr>
       -</table>
       -<hr/>
       -<div id="content">
       -{{template "content" .}}
       -</div>
       -</body>
       -</html>
       -`
       -
       -        branchesContent = `
       -{{define "content"}}
       -<h1>{{.RepoName}} - Branches</h1>
       -<table>
       -<thead>
       -<tr><td><b>Branch Name</b></td><td><b>Last Commit</b></td><td><b>Last Commit Date</b></td></tr>
       -</thead>
       -<tbody>
       -{{range .Branches}}
       -<tr><td>{{.Name}}</td><td>{{.LastCommit}}</td><td>{{.LastCommitDate}}</td></tr>
       -{{end}}
       -</tbody>
       -</table>
       -{{end}}
       -`
       -
       -        commitHistoryContent = `
       -{{define "content"}}
       -<h1>{{.RepoName}} - Commit History</h1>
       -<table>
       -<thead>
       -<tr><td><b>Hash</b></td><td><b>Author</b></td><td><b>Date</b></td><td><b>Message</b></td></tr>
       -</thead>
       -<tbody>
       -{{range .Commits}}
       -<tr><td>{{.Hash}}</td><td>{{.Author}}</td><td>{{.Date}}</td><td>{{.Message}}</td></tr>
       -{{end}}
       -</tbody>
       -</table>
       -{{end}}
       -`
       -
       -        indexContent = `
       -{{define "content"}}
       -<table id="index">
       -<thead>
       -<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Last commit</b></td><td><b>Links</b></td></tr>
       -</thead>
       -<tbody>
       -{{range $group, $repos := .Repos}}
       -<tr><td colspan="4"><b>{{if eq $group ""}} {{else}}<hr>{{end}}</b></td></tr>
       -  {{range $repos}}
       -  <tr>
       -    <td>{{.Name}}</td>
       -    <td>{{.Description}}</td>
       -    <td>| {{.LastCommit}} | </td>
       -    <td>
       -      <a href="{{.Name}}/README.html">readme</a> |
       -      <a href="{{.Name}}/commits.html">commits</a> |
       -      <a href="{{.Name}}/branches.html">branches</a>
       -    </td>
       -  </tr>
       -  {{end}}
       -{{end}}
       -</tbody>
       -</table>
       -{{end}}
       -`
       -        readmeContent = `
       -{{define "content"}}
       -<h1>{{.RepoName}}</h1>
       -<pre>{{.ReadmeContent}}</pre>
       -{{end}}
       -`
       -)
       -
       -var (
       -        branchesTmpl      = template.Must(template.New("base").Parse(baseTemplate + branchesContent))
       -        commitHistoryTmpl = template.Must(template.New("base").Parse(baseTemplate + commitHistoryContent))
       -        indexTmpl         = template.Must(template.New("base").Parse(baseTemplate + indexContent))
       -        readmeTmpl        = template.Must(template.New("base").Parse(baseTemplate + readmeContent))
       -)
       -
       -func generateIndexHTML(cfg *Config, repoInfos []RepoInfo) error {
       -        groupedRepos := groupRepos(repoInfos, cfg.GroupFlag)
       -        indexOutputPath := filepath.Join(cfg.OutputRoot, "index.html")
       -
       -        indexFile, err := os.Create(indexOutputPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to create index HTML file: %w", err)
       -        }
       -        defer indexFile.Close()
       -
       -        return indexTmpl.Execute(indexFile, struct {
       -                Title    string
       -                IconPath string
       -                Repos    map[string][]RepoInfo
       -        }{
       -                Title:    "git clone git@git.jay.scot:<reponame>",
       -                IconPath: "./",
       -                Repos:    groupedRepos,
       -        })
       -}
       -
       -func getBranchInfo(repo *git.Repository) ([]BranchInfo, error) {
       -        branches, err := repo.Branches()
       -        if err != nil {
       -                return nil, fmt.Errorf("failed to get branches: %w", err)
       -        }
       -
       -        var branchInfos []BranchInfo
       -        err = branches.ForEach(func(branch *plumbing.Reference) error {
       -                commit, err := repo.CommitObject(branch.Hash())
       -                if err != nil {
       -                        return fmt.Errorf("failed to get commit for branch %s: %w", branch.Name().Short(), err)
       -                }
       -
       -                branchInfos = append(branchInfos, BranchInfo{
       -                        Name:           branch.Name().Short(),
       -                        LastCommit:     commit.Hash.String()[:7],
       -                        LastCommitDate: commit.Author.When.Format("02 Jan 2006 15:04:05"),
       -                })
       -                return nil
       -        })
       -
       -        if err != nil {
       -                return nil, fmt.Errorf("failed to iterate over branches: %w", err)
       -        }
       -
       -        return branchInfos, nil
       -}
       -
       -func getCommitHistory(repo *git.Repository) ([]CommitInfo, error) {
       -        ref, err := repo.Head()
       -        if err != nil {
       -                return nil, fmt.Errorf("failed to get HEAD reference: %w", err)
       -        }
       -
       -        commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
       -        if err != nil {
       -                return nil, fmt.Errorf("failed to get commit log: %w", err)
       -        }
       -
       -        var commitHistory []CommitInfo
       -        err = commits.ForEach(func(c *object.Commit) error {
       -                commitHistory = append(commitHistory, CommitInfo{
       -                        Hash:    c.Hash.String()[:7],
       -                        Author:  c.Author.Name,
       -                        Date:    c.Author.When.Format("02 Jan 2006 15:04:05"),
       -                        Message: strings.Split(c.Message, "\n")[0], // Get only the first line of the commit message
       -                })
       -                return nil
       -        })
       -
       -        if err != nil {
       -                return nil, fmt.Errorf("failed to iterate over commits: %w", err)
       -        }
       -
       -        return commitHistory, nil
       -}
       -
       -func getDescriptionAndGroup(repoPath string, groupFlag bool) (string, string) {
       -        descPath := filepath.Join(repoPath, "description")
       -        description, _ := os.ReadFile(descPath)
       -        desc := strings.TrimSpace(string(description))
       -        groupRegex := regexp.MustCompile(`\[(.*?)\]`)
       -
       -        var group string
       -        if groupFlag {
       -                matches := groupRegex.FindStringSubmatch(desc)
       -                if len(matches) > 1 {
       -                        group = matches[1]
       -                }
       -        }
       -
       -        return desc, group
       -}
       -
       -func getReadme(repoPath string) (string, error) {
       -        readmeFiles := []string{"README.md", "README.txt", "README"}
       -        repo, err := git.PlainOpen(repoPath)
       -        if err != nil {
       -                return "", fmt.Errorf("failed to open git repository: %w", err)
       -        }
       -
       -        headRef, err := repo.Head()
       -        if err != nil {
       -                return "", fmt.Errorf("failed to get HEAD reference: %w", err)
       -        }
       -
       -        commit, err := repo.CommitObject(headRef.Hash())
       -        if err != nil {
       -                return "", fmt.Errorf("failed to get commit object: %w", err)
       -        }
       -
       -        tree, err := commit.Tree()
       -        if err != nil {
       -                return "", fmt.Errorf("failed to get tree: %w", err)
       -        }
       -
       -        for _, fileName := range readmeFiles {
       -                file, err := tree.File(fileName)
       -                if err != nil {
       -                        continue
       -                }
       -
       -                content, err := file.Contents()
       -                if err != nil {
       -                        return "", fmt.Errorf("failed to read file contents: %w", err)
       -                }
       -
       -                return content, nil
       -        }
       -
       -        return "No README found!", nil
       -}
       -
       -func getRepoInfo(repoPath string, groupFlag bool) (RepoInfo, error) {
       -        repo, err := git.PlainOpen(repoPath)
       -        if err != nil {
       -                return RepoInfo{}, fmt.Errorf("failed to open repository: %w", err)
       -        }
       -
       -        headRef, err := repo.Head()
       -        if err != nil {
       -                return RepoInfo{}, fmt.Errorf("failed to get HEAD reference: %w", err)
       -        }
       -
       -        commit, err := repo.CommitObject(headRef.Hash())
       -        if err != nil {
       -                return RepoInfo{}, fmt.Errorf("failed to get commit object: %w", err)
       -        }
       -
       -        description, group := getDescriptionAndGroup(repoPath, groupFlag)
       -
       -        return RepoInfo{
       -                Name:        filepath.Base(repoPath),
       -                Description: description,
       -                LastCommit:  commit.Committer.When.Format("02 Jan 2006"),
       -                Group:       group,
       -        }, nil
       -}
       -
       -func groupRepos(repos []RepoInfo, groupFlag bool) map[string][]RepoInfo {
       -        groupedRepos := make(map[string][]RepoInfo)
       -        for _, repo := range repos {
       -                group := ""
       -                if groupFlag {
       -                        group = repo.Group
       -                }
       -                groupedRepos[group] = append(groupedRepos[group], repo)
       -        }
       -
       -        for _, repoList := range groupedRepos {
       -                sort.Slice(repoList, func(i, j int) bool {
       -                        return strings.ToLower(repoList[i].Name) < strings.ToLower(repoList[j].Name)
       -                })
       -        }
       -
       -        return groupedRepos
       -}
       -
       -func parseIgnoreDirs(ignoreDirs string) map[string]bool {
       -        ignoreMap := make(map[string]bool)
       -        for _, dir := range strings.Split(ignoreDirs, ",") {
       -                if trimmedDir := strings.TrimSpace(dir); trimmedDir != "" {
       -                        ignoreMap[trimmedDir] = true
       -                }
       -        }
       -        return ignoreMap
       -}
       -
       -func processBranches(cfg *Config, repoName, repoPath, outputDir string) error {
       -        repo, err := git.PlainOpen(repoPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to open git repository: %w", err)
       -        }
       -
       -        branches, err := getBranchInfo(repo)
       -        if err != nil {
       -                return fmt.Errorf("failed to get branch information: %w", err)
       -        }
       -
       -        outputPath := filepath.Join(outputDir, "branches.html")
       -
       -        f, err := os.Create(outputPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to create branches HTML file: %w", err)
       -        }
       -        defer f.Close()
       -
       -        return branchesTmpl.Execute(f, struct {
       -                Title    string
       -                IconPath string
       -                RepoName string
       -                Branches []BranchInfo
       -        }{
       -                Title:    repoName + " - Branches",
       -                IconPath: "../",
       -                RepoName: repoName,
       -                Branches: branches,
       -        })
       -}
       -
       -func processCommitHistory(cfg *Config, repoName, repoPath, outputDir string) error {
       -        repo, err := git.PlainOpen(repoPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to open git repository: %w", err)
       -        }
       -
       -        commits, err := getCommitHistory(repo)
       -        if err != nil {
       -                return fmt.Errorf("failed to get commit history: %w", err)
       -        }
       -
       -        outputPath := filepath.Join(outputDir, "commits.html")
       -
       -        f, err := os.Create(outputPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to create commit history HTML file: %w", err)
       -        }
       -        defer f.Close()
       -
       -        return commitHistoryTmpl.Execute(f, struct {
       -                Title    string
       -                IconPath string
       -                RepoName string
       -                Commits  []CommitInfo
       -        }{
       -                Title:    repoName + " - History",
       -                IconPath: "../",
       -                RepoName: repoName,
       -                Commits:  commits,
       -        })
       -}
       -
       -func processReadme(cfg *Config, repoName, repoPath, outputDir string) error {
       -        readme, err := getReadme(repoPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to get README: %w", err)
       -        }
       -
       -        outputPath := filepath.Join(outputDir, "README.html")
       -
       -        f, err := os.Create(outputPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to create README HTML file: %w", err)
       -        }
       -        defer f.Close()
       -
       -        return readmeTmpl.Execute(f, struct {
       -                Title         string
       -                IconPath      string
       -                RepoName      string
       -                ReadmeContent string
       -        }{
       -                Title:         repoName + " - Readme!",
       -                IconPath:      "../",
       -                RepoName:      repoName,
       -                ReadmeContent: readme,
       -        })
       -}
       -
       -func processRepositories(cfg *Config) error {
       -        repos, err := os.ReadDir(cfg.ReposPath)
       -        if err != nil {
       -                return fmt.Errorf("failed to read repos directory: %w", err)
       -        }
       -
       -        var repoInfos []RepoInfo
       -        for _, r := range repos {
       -                if r.IsDir() && !cfg.IgnoreDirs[r.Name()] {
       -                        repoPath := filepath.Join(cfg.ReposPath, r.Name())
       -                        repoInfo, err := getRepoInfo(repoPath, cfg.GroupFlag)
       -                        if err != nil {
       -                                fmt.Printf("Failed to get info for repo %s: %v\n", r.Name(), err)
       -                                continue
       -                        }
       -                        repoInfos = append(repoInfos, repoInfo)
       -
       -                        outputDir := filepath.Join(cfg.OutputRoot, r.Name())
       -                        if err := os.MkdirAll(outputDir, 0755); err != nil {
       -                                fmt.Printf("Failed to create output directory for repo %s: %v\n", r.Name(), err)
       -                                continue
       -                        }
       -
       -                        if err := processReadme(cfg, r.Name(), repoPath, outputDir); err != nil {
       -                                fmt.Printf("Failed to process README for repo %s: %v\n", r.Name(), err)
       -                        }
       -
       -                        if err := processCommitHistory(cfg, r.Name(), repoPath, outputDir); err != nil {
       -                                fmt.Printf("Failed to process commit history for repo %s: %v\n", r.Name(), err)
       -                        }
       -
       -                        if err := processBranches(cfg, r.Name(), repoPath, outputDir); err != nil {
       -                                fmt.Printf("Failed to process branches for repo %s: %v\n", r.Name(), err)
       -                        }
       -                }
       -        }
       -
       -        return generateIndexHTML(cfg, repoInfos)
       -}