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}