tree.go

  1package pgit
  2
  3import (
  4	"fmt"
  5	"html/template"
  6	"os"
  7	"path/filepath"
  8	"sort"
  9	"strings"
 10	"time"
 11
 12	git "github.com/gogs/git-module"
 13)
 14
 15type TreeRoot struct {
 16	Path   string
 17	Items  []*TreeItem
 18	Crumbs []*Breadcrumb
 19}
 20
 21type TreeWalker struct {
 22	treeItem           chan *TreeItem
 23	tree               chan *TreeRoot
 24	HideTreeLastCommit bool
 25	PageData           *PageData
 26	Repo               *git.Repository
 27	Config             *Config
 28}
 29
 30type Breadcrumb struct {
 31	Text   string
 32	URL    template.URL
 33	IsLast bool
 34}
 35
 36func (tw *TreeWalker) CalcBreadcrumbs(curpath string) []*Breadcrumb {
 37	if curpath == "" {
 38		return []*Breadcrumb{}
 39	}
 40	parts := strings.Split(curpath, string(os.PathSeparator))
 41	rootURL := tw.Config.CompileURL(
 42		GetTreeBaseDir(*tw.PageData.RevData),
 43		"index.html",
 44	)
 45
 46	crumbs := make([]*Breadcrumb, len(parts)+1)
 47	crumbs[0] = &Breadcrumb{
 48		URL:  rootURL,
 49		Text: tw.PageData.Repo.RepoName,
 50	}
 51
 52	cur := ""
 53	for idx, d := range parts {
 54		crumb := filepath.Join(GetFileBaseDir(*tw.PageData.RevData), cur, d)
 55		crumbUrl := tw.Config.CompileURL(crumb, "index.html")
 56		crumbs[idx+1] = &Breadcrumb{
 57			Text: d,
 58			URL:  crumbUrl,
 59		}
 60		if idx == len(parts)-1 {
 61			crumbs[idx+1].IsLast = true
 62		}
 63		cur = filepath.Join(cur, d)
 64	}
 65
 66	return crumbs
 67}
 68
 69func FilenameToDevIcon(filename string) string {
 70	ext := filepath.Ext(filename)
 71	extMapper := map[string]string{
 72		".html": "html5",
 73		".go":   "go",
 74		".py":   "python",
 75		".css":  "css3",
 76		".js":   "javascript",
 77		".md":   "markdown",
 78		".ts":   "typescript",
 79		".tsx":  "react",
 80		".jsx":  "react",
 81	}
 82
 83	nameMapper := map[string]string{
 84		"Makefile":   "cmake",
 85		"Dockerfile": "docker",
 86	}
 87
 88	icon := extMapper[ext]
 89	if icon == "" {
 90		icon = nameMapper[filename]
 91	}
 92
 93	return fmt.Sprintf("devicon-%s-original", icon)
 94}
 95
 96func (tw *TreeWalker) NewTreeItem(entry *git.TreeEntry, curpath string, crumbs []*Breadcrumb) *TreeItem {
 97	typ := entry.Type()
 98	fname := filepath.Join(curpath, entry.Name())
 99	item := &TreeItem{
100		Size:   ToPretty(entry.Size()),
101		Name:   entry.Name(),
102		Path:   fname,
103		Entry:  entry,
104		URL:    tw.Config.GetFileURL(*tw.PageData.RevData, fname),
105		Crumbs: crumbs,
106		Author: &git.Signature{
107			Name: "unknown",
108		},
109	}
110
111	if !tw.HideTreeLastCommit {
112		id := (*tw.PageData.RevData).ID()
113		lastCommits, err := tw.Repo.RevList([]string{id}, git.RevListOptions{
114			Path:           item.Path,
115			CommandOptions: git.CommandOptions{Args: []string{"-1"}},
116		})
117		Bail(err)
118
119		if len(lastCommits) > 0 {
120			lc := lastCommits[0]
121			item.CommitURL = tw.Config.GetCommitURL(lc.ID.String())
122			item.CommitID = GetShortID(lc.ID.String())
123			item.Summary = lc.Summary()
124			item.When = lc.Author.When.Format(time.DateOnly)
125			item.WhenISO = lc.Author.When.UTC().Format(time.RFC3339)
126			item.WhenDisplay = FormatDateForDisplay(lc.Author.When)
127			item.Author = lc.Author
128		}
129	}
130
131	fpath := tw.Config.GetFileURL(*tw.PageData.RevData, fmt.Sprintf("%s.html", fname))
132	switch typ {
133	case git.ObjectTree:
134		item.IsDir = true
135		fpath = tw.Config.CompileURL(
136			filepath.Join(
137				GetFileBaseDir(*tw.PageData.RevData),
138				curpath,
139				entry.Name(),
140			),
141			"index.html",
142		)
143	case git.ObjectBlob:
144		item.Icon = FilenameToDevIcon(item.Name)
145	}
146	item.URL = fpath
147
148	return item
149}
150
151func (tw *TreeWalker) Walk(tree *git.Tree, curpath string) {
152	entries, err := tree.Entries()
153	Bail(err)
154
155	crumbs := tw.CalcBreadcrumbs(curpath)
156	treeEntries := []*TreeItem{}
157	for _, entry := range entries {
158		typ := entry.Type()
159		item := tw.NewTreeItem(entry, curpath, crumbs)
160
161		switch typ {
162		case git.ObjectTree:
163			item.IsDir = true
164			re, _ := tree.Subtree(entry.Name())
165			tw.Walk(re, item.Path)
166			treeEntries = append(treeEntries, item)
167			tw.treeItem <- item
168		case git.ObjectBlob:
169			treeEntries = append(treeEntries, item)
170			tw.treeItem <- item
171		}
172	}
173
174	sort.Slice(treeEntries, func(i, j int) bool {
175		nameI := treeEntries[i].Name
176		nameJ := treeEntries[j].Name
177		if treeEntries[i].IsDir && treeEntries[j].IsDir {
178			return nameI < nameJ
179		}
180
181		if treeEntries[i].IsDir && !treeEntries[j].IsDir {
182			return true
183		}
184
185		if !treeEntries[i].IsDir && treeEntries[j].IsDir {
186			return false
187		}
188
189		return nameI < nameJ
190	})
191
192	fpath := filepath.Join(
193		GetFileBaseDir(*tw.PageData.RevData),
194		curpath,
195	)
196	if curpath == "" {
197		fpath = GetTreeBaseDir(*tw.PageData.RevData)
198	}
199
200	tw.tree <- &TreeRoot{
201		Path:   fpath,
202		Items:  treeEntries,
203		Crumbs: crumbs,
204	}
205
206	if curpath == "" {
207		close(tw.tree)
208		close(tw.treeItem)
209	}
210}