Commit 95ae423
Changed files (7)
internal
generator
pool
progress_bar
templates
internal/generator/blob.go
@@ -2,25 +2,30 @@ package generator
import (
"bytes"
- "context"
"fmt"
"html/template"
"os"
"path/filepath"
- "runtime"
"strings"
- "sync"
+ "github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
+ "github.com/yuin/goldmark"
"mokhan.ca/antonmedv/gitmal/internal/git"
"mokhan.ca/antonmedv/gitmal/internal/links"
- "mokhan.ca/antonmedv/gitmal/internal/progress_bar"
+ "mokhan.ca/antonmedv/gitmal/internal/pool"
"mokhan.ca/antonmedv/gitmal/internal/templates"
)
+type blobWorker struct {
+ md goldmark.Markdown
+ formatter *html.Formatter
+ style *chroma.Style
+}
+
func GenerateBlobs(files []git.Blob, params Params) error {
formatterOptions := []html.Option{
html.WithLineNumbers(true),
@@ -30,220 +35,134 @@ func GenerateBlobs(files []git.Blob, params Params) error {
}
css := cssSyntax(formatterOptions)
-
dirsSet := links.BuildDirSet(files)
filesSet := links.BuildFileSet(files)
- // Bounded worker pool
- workers := runtime.NumCPU()
- if workers < 1 {
- workers = 1
- }
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- jobs := make(chan git.Blob)
- errCh := make(chan error, 1)
- var wg sync.WaitGroup
-
- p := progress_bar.NewProgressBar("blobs for "+params.Ref.String(), len(files))
-
- workerFn := func() {
- defer wg.Done()
-
- md := createMarkdown()
- formatter := html.New(formatterOptions...)
- style := styles.Get("github")
-
- check := func(err error) bool {
+ return pool.RunWithInit(files,
+ func() *blobWorker {
+ return &blobWorker{
+ md: createMarkdown(),
+ formatter: html.New(formatterOptions...),
+ style: styles.Get("github"),
+ }
+ },
+ func(w *blobWorker, blob git.Blob) error {
+ var content string
+ data, isBin, err := git.BlobContent(params.Ref, blob.Path, params.RepoDir)
if err != nil {
- select {
- case errCh <- err:
- cancel()
- default:
- }
- return true
+ return err
}
- return false
- }
-
- for {
- select {
- case <-ctx.Done():
- return
- case blob, ok := <-jobs:
- if !ok {
- return
- }
- func() {
- var content string
- data, isBin, err := git.BlobContent(params.Ref, blob.Path, params.RepoDir)
- if check(err) {
- return
- }
-
- isImg := isImage(blob.Path)
- if !isBin {
- content = string(data)
- }
-
- outPath := filepath.Join(params.OutputDir, "blob", params.Ref.DirName(), blob.Path) + ".html"
- if err := os.MkdirAll(filepath.Dir(outPath), 0o755); check(err) {
- return
- }
-
- f, err := os.Create(outPath)
- if check(err) {
- return
- }
- defer func() {
- _ = f.Close()
- }()
-
- depth := 0
- if strings.Contains(blob.Path, "/") {
- depth = len(strings.Split(blob.Path, "/")) - 1
- }
- rootHref := strings.Repeat("../", depth+2)
-
- if isMarkdown(blob.Path) {
- var b bytes.Buffer
- if err := md.Convert([]byte(content), &b); check(err) {
- return
- }
-
- contentHTML := links.Resolve(
- b.String(),
- blob.Path,
- rootHref,
- params.Ref.DirName(),
- dirsSet,
- filesSet,
- )
-
- err = templates.MarkdownTemplate.ExecuteTemplate(f, "layout.gohtml", templates.MarkdownParams{
- LayoutParams: templates.LayoutParams{
- Title: fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref),
- CSSMarkdown: cssMarkdown(),
- Name: params.Name,
- RootHref: rootHref,
- CurrentRefDir: params.Ref.DirName(),
- Selected: "code",
- },
- HeaderParams: templates.HeaderParams{
- Ref: params.Ref,
- Breadcrumbs: breadcrumbs(params.Name, blob.Path, true),
- },
- Blob: blob,
- Content: template.HTML(contentHTML),
- })
- if check(err) {
- return
- }
- } else {
-
- var contentHTML template.HTML
- if !isBin {
- var b bytes.Buffer
- lx := lexers.Match(blob.Path)
- if lx == nil {
- lx = lexers.Fallback
- }
- iterator, _ := lx.Tokenise(nil, content)
- if err := formatter.Format(&b, style, iterator); check(err) {
- return
- }
- contentHTML = template.HTML(b.String())
-
- } else if isImg {
-
- rawPath := filepath.Join(params.OutputDir, "raw", params.Ref.DirName(), blob.Path)
- if err := os.MkdirAll(filepath.Dir(rawPath), 0o755); check(err) {
- return
- }
+ isImg := isImage(blob.Path)
+ if !isBin {
+ content = string(data)
+ }
- rf, err := os.Create(rawPath)
- if check(err) {
- return
- }
- defer func() {
- _ = rf.Close()
- }()
+ outPath := filepath.Join(params.OutputDir, "blob", params.Ref.DirName(), blob.Path) + ".html"
+ if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
+ return err
+ }
- if _, err := rf.Write(data); check(err) {
- return
- }
+ f, err := os.Create(outPath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
- relativeRawPath := filepath.Join(rootHref, "raw", params.Ref.DirName(), blob.Path)
- contentHTML = template.HTML(fmt.Sprintf(`<img src="%s" alt="%s" />`, relativeRawPath, blob.FileName))
- }
+ depth := 0
+ if strings.Contains(blob.Path, "/") {
+ depth = len(strings.Split(blob.Path, "/")) - 1
+ }
+ rootHref := strings.Repeat("../", depth+2)
- err = templates.BlobTemplate.ExecuteTemplate(f, "layout.gohtml", templates.BlobParams{
- LayoutParams: templates.LayoutParams{
- Title: fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref),
- Name: params.Name,
- RootHref: rootHref,
- CurrentRefDir: params.Ref.DirName(),
- Selected: "code",
- },
- HeaderParams: templates.HeaderParams{
- Ref: params.Ref,
- Breadcrumbs: breadcrumbs(params.Name, blob.Path, true),
- },
- CSS: css,
- Blob: blob,
- IsBinary: isBin,
- IsImage: isImg,
- Content: contentHTML,
- })
- if check(err) {
- return
- }
- }
- }()
+ if isMarkdown(blob.Path) {
+ var b bytes.Buffer
+ if err := w.md.Convert([]byte(content), &b); err != nil {
+ return err
+ }
- p.Inc()
+ contentHTML := links.Resolve(
+ b.String(),
+ blob.Path,
+ rootHref,
+ params.Ref.DirName(),
+ dirsSet,
+ filesSet,
+ )
+
+ return templates.MarkdownTemplate.ExecuteTemplate(f, "layout.gohtml", templates.MarkdownParams{
+ LayoutParams: templates.LayoutParams{
+ Title: fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref),
+ CSSMarkdown: cssMarkdown(),
+ Name: params.Name,
+ RootHref: rootHref,
+ CurrentRefDir: params.Ref.DirName(),
+ Selected: "code",
+ },
+ HeaderParams: templates.HeaderParams{
+ Ref: params.Ref,
+ Breadcrumbs: breadcrumbs(params.Name, blob.Path, true),
+ },
+ Blob: blob,
+ Content: template.HTML(contentHTML),
+ })
}
- }
- }
- // Start workers
- wg.Add(workers)
- for i := 0; i < workers; i++ {
- go workerFn()
- }
+ var contentHTML template.HTML
+ if !isBin {
+ var b bytes.Buffer
+ lx := lexers.Match(blob.Path)
+ if lx == nil {
+ lx = lexers.Fallback
+ }
+ iterator, _ := lx.Tokenise(nil, content)
+ if err := w.formatter.Format(&b, w.style, iterator); err != nil {
+ return err
+ }
+ contentHTML = template.HTML(b.String())
+ } else if isImg {
+ rawPath := filepath.Join(params.OutputDir, "raw", params.Ref.DirName(), blob.Path)
+ if err := os.MkdirAll(filepath.Dir(rawPath), 0o755); err != nil {
+ return err
+ }
- // Feed jobs
- go func() {
- defer close(jobs)
- for _, b := range files {
- select {
- case <-ctx.Done():
- return
- case jobs <- b:
- }
- }
- }()
+ rf, err := os.Create(rawPath)
+ if err != nil {
+ return err
+ }
- // Wait for workers
- doneCh := make(chan struct{})
- go func() {
- wg.Wait()
- close(doneCh)
- }()
+ if _, err := rf.Write(data); err != nil {
+ rf.Close()
+ return err
+ }
+ if err := rf.Close(); err != nil {
+ return err
+ }
- var runErr error
- select {
- case runErr = <-errCh:
- // error occurred, wait workers to finish
- <-doneCh
- case <-doneCh:
- }
+ relativeRawPath := filepath.Join(rootHref, "raw", params.Ref.DirName(), blob.Path)
+ contentHTML = template.HTML(fmt.Sprintf(`<img src="%s" alt="%s" />`, relativeRawPath, blob.FileName))
+ }
- p.Done()
- return runErr
+ return templates.BlobTemplate.ExecuteTemplate(f, "layout.gohtml", templates.BlobParams{
+ LayoutParams: templates.LayoutParams{
+ Title: fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref),
+ Name: params.Name,
+ RootHref: rootHref,
+ CurrentRefDir: params.Ref.DirName(),
+ Selected: "code",
+ },
+ HeaderParams: templates.HeaderParams{
+ Ref: params.Ref,
+ Breadcrumbs: breadcrumbs(params.Name, blob.Path, true),
+ },
+ CSS: css,
+ Blob: blob,
+ IsBinary: isBin,
+ IsImage: isImg,
+ Content: contentHTML,
+ })
+ },
+ )
}
func cssSyntax(opts []html.Option) template.CSS {
internal/generator/commit.go
@@ -2,15 +2,12 @@ package generator
import (
"bytes"
- "context"
"fmt"
"html/template"
"os"
"path/filepath"
- "runtime"
"sort"
"strings"
- "sync"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters/html"
@@ -19,7 +16,7 @@ import (
"mokhan.ca/antonmedv/gitmal/internal/git"
"mokhan.ca/antonmedv/gitmal/internal/gitdiff"
- "mokhan.ca/antonmedv/gitmal/internal/progress_bar"
+ "mokhan.ca/antonmedv/gitmal/internal/pool"
"mokhan.ca/antonmedv/gitmal/internal/templates"
)
@@ -34,75 +31,9 @@ func GenerateCommits(commits map[string]git.Commit, params Params) error {
list = append(list, c)
}
- workers := runtime.NumCPU()
- if workers < 1 {
- workers = 1
- }
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- jobs := make(chan git.Commit)
- errCh := make(chan error, 1)
- var wg sync.WaitGroup
-
- p := progress_bar.NewProgressBar("commits", len(list))
-
- workerFn := func() {
- defer wg.Done()
- for {
- select {
- case <-ctx.Done():
- return
- case c, ok := <-jobs:
- if !ok {
- return
- }
- if err := generateCommitPage(c, params); err != nil {
- select {
- case errCh <- err:
- cancel()
- default:
- }
- return
- }
- p.Inc()
- }
- }
- }
-
- wg.Add(workers)
- for i := 0; i < workers; i++ {
- go workerFn()
- }
-
- go func() {
- defer close(jobs)
- for _, c := range list {
- select {
- case <-ctx.Done():
- return
- case jobs <- c:
- }
- }
- }()
-
- done := make(chan struct{})
- go func() {
- wg.Wait()
- close(done)
- }()
-
- var err error
- select {
- case err = <-errCh:
- cancel()
- <-done
- case <-done:
- }
-
- p.Done()
- return err
+ return pool.Run(list, func(c git.Commit) error {
+ return generateCommitPage(c, params)
+ })
}
func generateCommitPage(commit git.Commit, params Params) error {
internal/generator/commits_list.go
@@ -7,7 +7,6 @@ import (
"slices"
"mokhan.ca/antonmedv/gitmal/internal/git"
- "mokhan.ca/antonmedv/gitmal/internal/progress_bar"
"mokhan.ca/antonmedv/gitmal/internal/templates"
)
@@ -24,8 +23,6 @@ func GenerateLogForBranch(allCommits []git.Commit, params Params) error {
return err
}
- p := progress_bar.NewProgressBar("commits for "+params.Ref.String(), totalPages)
-
page := 1
for pageCommits := range slices.Chunk(allCommits, commitsPerPage) {
for i := range pageCommits {
@@ -91,10 +88,7 @@ func GenerateLogForBranch(allCommits []git.Commit, params Params) error {
}
page++
- p.Inc()
}
- p.Done()
-
return nil
}
internal/generator/list.go
@@ -1,19 +1,16 @@
package generator
import (
- "context"
"fmt"
"html/template"
"os"
"path/filepath"
- "runtime"
"sort"
"strings"
- "sync"
"mokhan.ca/antonmedv/gitmal/internal/git"
"mokhan.ca/antonmedv/gitmal/internal/links"
- "mokhan.ca/antonmedv/gitmal/internal/progress_bar"
+ "mokhan.ca/antonmedv/gitmal/internal/pool"
"mokhan.ca/antonmedv/gitmal/internal/templates"
)
@@ -56,7 +53,6 @@ func GenerateLists(files []git.Blob, params Params) error {
ensureDir(cur).files = append(ensureDir(cur).files, b)
}
- // Prepare jobs slice to have stable iteration order (optional)
type job struct {
dirPath string
di *dirInfo
@@ -65,184 +61,95 @@ func GenerateLists(files []git.Blob, params Params) error {
for dp, di := range dirs {
jobsSlice = append(jobsSlice, job{dirPath: dp, di: di})
}
- // Sort by dirPath for determinism
sort.Slice(jobsSlice, func(i, j int) bool { return jobsSlice[i].dirPath < jobsSlice[j].dirPath })
- // Worker pool similar to generateBlobs
- workers := runtime.NumCPU()
- if workers < 1 {
- workers = 1
- }
+ return pool.Run(jobsSlice, func(jb job) error {
+ dirPath := jb.dirPath
+ di := jb.di
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ outDir := filepath.Join(params.OutputDir, "blob", params.Ref.DirName())
+ if dirPath != "" {
+ outDir = filepath.Join(outDir, filepath.FromSlash(dirPath))
+ }
+ if err := os.MkdirAll(outDir, 0o755); err != nil {
+ return err
+ }
- jobCh := make(chan job)
- errCh := make(chan error, 1)
- var wg sync.WaitGroup
+ dirNames := make([]string, 0, len(di.subdirs))
+ for name := range di.subdirs {
+ dirNames = append(dirNames, name)
+ }
- p := progress_bar.NewProgressBar("lists for "+params.Ref.String(), len(jobsSlice))
+ sort.Strings(dirNames)
+ sort.Slice(di.files, func(i, j int) bool {
+ return di.files[i].FileName < di.files[j].FileName
+ })
+
+ subdirEntries := make([]templates.ListEntry, 0, len(dirNames))
+ for _, name := range dirNames {
+ subdirEntries = append(subdirEntries, templates.ListEntry{
+ Name: name + "/",
+ Href: name + "/index.html",
+ IsDir: true,
+ })
+ }
- check := func(err error) bool {
- if err != nil {
- select {
- case errCh <- err:
- cancel()
- default:
- }
- return true
+ fileEntries := make([]templates.ListEntry, 0, len(di.files))
+ for _, b := range di.files {
+ fileEntries = append(fileEntries, templates.ListEntry{
+ Name: b.FileName + "",
+ Href: b.FileName + ".html",
+ Mode: b.Mode,
+ Size: humanizeSize(b.Size),
+ })
}
- return false
- }
- workerFn := func() {
- defer wg.Done()
- for {
- select {
- case <-ctx.Done():
- return
- case jb, ok := <-jobCh:
- if !ok {
- return
- }
- func() {
- dirPath := jb.dirPath
- di := jb.di
-
- outDir := filepath.Join(params.OutputDir, "blob", params.Ref.DirName())
- if dirPath != "" {
- // convert forward slash path into OS path
- outDir = filepath.Join(outDir, filepath.FromSlash(dirPath))
- }
- if err := os.MkdirAll(outDir, 0o755); check(err) {
- return
- }
-
- // Build entries
- dirNames := make([]string, 0, len(di.subdirs))
- for name := range di.subdirs {
- dirNames = append(dirNames, name)
- }
-
- // Sort for stable output
- sort.Strings(dirNames)
- sort.Slice(di.files, func(i, j int) bool {
- return di.files[i].FileName < di.files[j].FileName
- })
-
- subdirEntries := make([]templates.ListEntry, 0, len(dirNames))
- for _, name := range dirNames {
- subdirEntries = append(subdirEntries, templates.ListEntry{
- Name: name + "/",
- Href: name + "/index.html",
- IsDir: true,
- })
- }
-
- fileEntries := make([]templates.ListEntry, 0, len(di.files))
- for _, b := range di.files {
- fileEntries = append(fileEntries, templates.ListEntry{
- Name: b.FileName + "",
- Href: b.FileName + ".html",
- Mode: b.Mode,
- Size: humanizeSize(b.Size),
- })
- }
-
- // Title and current path label
- title := fmt.Sprintf("%s/%s at %s", params.Name, dirPath, params.Ref)
- if dirPath == "" {
- title = fmt.Sprintf("%s at %s", params.Name, params.Ref)
- }
-
- f, err := os.Create(filepath.Join(outDir, "index.html"))
- if check(err) {
- return
- }
- defer func() {
- _ = f.Close()
- }()
-
- // parent link is not shown for root
- parent := "../index.html"
- if dirPath == "" {
- parent = ""
- }
-
- depth := 0
- if dirPath != "" {
- depth = len(strings.Split(dirPath, "/"))
- }
- rootHref := strings.Repeat("../", depth+2)
-
- readmeHTML := readme(di.files, dirsSet, filesSet, params, rootHref)
- var CSSMarkdown template.CSS
- if readmeHTML != "" {
- CSSMarkdown = cssMarkdown()
- }
-
- err = templates.ListTemplate.ExecuteTemplate(f, "layout.gohtml", templates.ListParams{
- LayoutParams: templates.LayoutParams{
- Title: title,
- Name: params.Name,
- CSSMarkdown: CSSMarkdown,
- RootHref: rootHref,
- CurrentRefDir: params.Ref.DirName(),
- Selected: "code",
- },
- HeaderParams: templates.HeaderParams{
- Ref: params.Ref,
- Breadcrumbs: breadcrumbs(params.Name, dirPath, false),
- },
- Ref: params.Ref,
- ParentHref: parent,
- Dirs: subdirEntries,
- Files: fileEntries,
- Readme: readmeHTML,
- })
- if check(err) {
- return
- }
- }()
-
- p.Inc()
- }
+ title := fmt.Sprintf("%s/%s at %s", params.Name, dirPath, params.Ref)
+ if dirPath == "" {
+ title = fmt.Sprintf("%s at %s", params.Name, params.Ref)
}
- }
- // Start workers
- wg.Add(workers)
- for i := 0; i < workers; i++ {
- go workerFn()
- }
+ f, err := os.Create(filepath.Join(outDir, "index.html"))
+ if err != nil {
+ return err
+ }
+ defer f.Close()
- // Feed jobs
- go func() {
- defer close(jobCh)
- for _, jb := range jobsSlice {
- select {
- case <-ctx.Done():
- return
- case jobCh <- jb:
- }
+ parent := "../index.html"
+ if dirPath == "" {
+ parent = ""
}
- }()
-
- // Wait for workers or first error
- doneCh := make(chan struct{})
- go func() {
- wg.Wait()
- close(doneCh)
- }()
-
- var runErr error
- select {
- case runErr = <-errCh:
- <-doneCh
- case <-doneCh:
- }
- p.Done()
+ depth := 0
+ if dirPath != "" {
+ depth = len(strings.Split(dirPath, "/"))
+ }
+ rootHref := strings.Repeat("../", depth+2)
+
+ readmeHTML := readme(di.files, dirsSet, filesSet, params, rootHref)
+ var CSSMarkdown template.CSS
+ if readmeHTML != "" {
+ CSSMarkdown = cssMarkdown()
+ }
- return runErr
+ return templates.ListTemplate.ExecuteTemplate(f, "layout.gohtml", templates.ListParams{
+ LayoutParams: templates.LayoutParams{
+ Title: title,
+ Name: params.Name,
+ CSSMarkdown: CSSMarkdown,
+ RootHref: rootHref,
+ CurrentRefDir: params.Ref.DirName(),
+ Selected: "code",
+ },
+ HeaderParams: templates.HeaderParams{
+ Ref: params.Ref,
+ Breadcrumbs: breadcrumbs(params.Name, dirPath, false),
+ },
+ Ref: params.Ref,
+ ParentHref: parent,
+ Dirs: subdirEntries,
+ Files: fileEntries,
+ Readme: readmeHTML,
+ })
+ })
}
internal/pool/pool.go
@@ -0,0 +1,83 @@
+package pool
+
+import (
+ "context"
+ "runtime"
+ "sync"
+)
+
+func Run[T any](items []T, fn func(T) error) error {
+ return RunWithInit(items, func() struct{} { return struct{}{} }, func(_ struct{}, item T) error {
+ return fn(item)
+ })
+}
+
+func RunWithInit[T, W any](items []T, init func() W, fn func(W, T) error) error {
+ if len(items) == 0 {
+ return nil
+ }
+
+ workers := runtime.NumCPU()
+ if workers < 1 {
+ workers = 1
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ jobs := make(chan T)
+ errCh := make(chan error, 1)
+ var wg sync.WaitGroup
+
+ wg.Add(workers)
+ for i := 0; i < workers; i++ {
+ go func() {
+ defer wg.Done()
+ w := init()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case item, ok := <-jobs:
+ if !ok {
+ return
+ }
+ if err := fn(w, item); err != nil {
+ select {
+ case errCh <- err:
+ cancel()
+ default:
+ }
+ return
+ }
+ }
+ }
+ }()
+ }
+
+ go func() {
+ defer close(jobs)
+ for _, item := range items {
+ select {
+ case <-ctx.Done():
+ return
+ case jobs <- item:
+ }
+ }
+ }()
+
+ done := make(chan struct{})
+ go func() {
+ wg.Wait()
+ close(done)
+ }()
+
+ var err error
+ select {
+ case err = <-errCh:
+ <-done
+ case <-done:
+ }
+
+ return err
+}
internal/progress_bar/progress_bar.go
@@ -1,84 +0,0 @@
-package progress_bar
-
-import (
- "fmt"
- "os"
- "strings"
- "sync"
- "sync/atomic"
- "time"
-)
-
-type ProgressBar struct {
- label string
- total int64
- current int64
- stop chan struct{}
- wg sync.WaitGroup
-}
-
-func NewProgressBar(label string, total int) *ProgressBar {
- p := &ProgressBar{label: label, total: int64(total), stop: make(chan struct{})}
- p.wg.Add(1)
- go func() {
- defer p.wg.Done()
- ticker := time.NewTicker(100 * time.Millisecond)
- defer ticker.Stop()
- // initial draw
- p.draw(atomic.LoadInt64(&p.current))
- for {
- select {
- case <-p.stop:
- return
- case <-ticker.C:
- cur := atomic.LoadInt64(&p.current)
- if cur > p.total {
- cur = p.total
- }
- p.draw(cur)
- }
- }
- }()
- return p
-}
-
-func (p *ProgressBar) Inc() {
- for {
- cur := atomic.LoadInt64(&p.current)
- if cur >= p.total {
- return
- }
- if atomic.CompareAndSwapInt64(&p.current, cur, cur+1) {
- return
- }
- // retry on race
- }
-}
-
-func (p *ProgressBar) Done() {
- atomic.StoreInt64(&p.current, p.total)
- close(p.stop)
- p.wg.Wait()
- p.draw(p.total)
- _, _ = fmt.Fprintln(os.Stderr)
-}
-
-func (p *ProgressBar) draw(current int64) {
- if p.total <= 0 {
- return
- }
- percent := 0
- if p.total > 0 {
- percent = int(current * 100 / p.total)
- }
- barLen := 24
- filled := 0
- if p.total > 0 {
- filled = int(current * int64(barLen) / p.total)
- }
- if filled > barLen {
- filled = barLen
- }
- bar := strings.Repeat("#", filled) + strings.Repeat(" ", barLen-filled)
- _, _ = fmt.Fprintf(os.Stderr, "\r[%s] %4d/%-4d (%3d%%) %s", bar, current, p.total, percent, p.label)
-}
internal/templates/templates.go
@@ -195,4 +195,3 @@ type FileView struct {
HasChanges bool
HTML HTML // pre-rendered HTML for diff of this file
}
-