···3131 Files []*RepoTree_TreeEntry `json:"files" cborgen:"files"`
3232 // parent: The parent path in the tree
3333 Parent *string `json:"parent,omitempty" cborgen:"parent,omitempty"`
3434+ // readme: Readme for this file tree
3535+ Readme *RepoTree_Readme `json:"readme,omitempty" cborgen:"readme,omitempty"`
3436 // ref: The git reference used
3537 Ref string `json:"ref" cborgen:"ref"`
3838+}
3939+4040+// RepoTree_Readme is a "readme" in the sh.tangled.repo.tree schema.
4141+type RepoTree_Readme struct {
4242+ // contents: Contents of the readme file
4343+ Contents string `json:"contents" cborgen:"contents"`
4444+ // filename: Name of the readme file
4545+ Filename string `json:"filename" cborgen:"filename"`
3646}
37473848// RepoTree_TreeEntry is a "treeEntry" in the sh.tangled.repo.tree schema.
+15-17
appview/pages/markup/format.go
···11package markup
2233-import "strings"
33+import (
44+ "regexp"
55+)
4657type Format string
68···1012)
11131214var FileTypes map[Format][]string = map[Format][]string{
1313- FormatMarkdown: []string{".md", ".markdown", ".mdown", ".mkdn", ".mkd"},
1515+ FormatMarkdown: {".md", ".markdown", ".mdown", ".mkdn", ".mkd"},
1416}
15171616-// ReadmeFilenames contains the list of common README filenames to search for,
1717-// in order of preference. Only includes well-supported formats.
1818-var ReadmeFilenames = []string{
1919- "README.md", "readme.md",
2020- "README",
2121- "readme",
2222- "README.markdown",
2323- "readme.markdown",
2424- "README.txt",
2525- "readme.txt",
1818+var FileTypePatterns = map[Format]*regexp.Regexp{
1919+ FormatMarkdown: regexp.MustCompile(`(?i)\.(md|markdown|mdown|mkdn|mkd)$`),
2020+}
2121+2222+var ReadmePattern = regexp.MustCompile(`(?i)^readme(\.(md|markdown|txt))?$`)
2323+2424+func IsReadmeFile(filename string) bool {
2525+ return ReadmePattern.MatchString(filename)
2626}
27272828func GetFormat(filename string) Format {
2929- for format, extensions := range FileTypes {
3030- for _, extension := range extensions {
3131- if strings.HasSuffix(filename, extension) {
3232- return format
3333- }
2929+ for format, pattern := range FileTypePatterns {
3030+ if pattern.MatchString(filename) {
3131+ return format
3432 }
3533 }
3634 // default format
···484484 if xrpcResp.Dotdot != nil {
485485 result.DotDot = *xrpcResp.Dotdot
486486 }
487487+ if xrpcResp.Readme != nil {
488488+ result.ReadmeFileName = xrpcResp.Readme.Filename
489489+ result.Readme = xrpcResp.Readme.Contents
490490+ }
487491488492 // redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
489493 // so we can safely redirect to the "parent" (which is the same file).
+24
knotserver/xrpc/repo_tree.go
···44 "net/http"
55 "path/filepath"
66 "time"
77+ "unicode/utf8"
7889 "tangled.org/core/api/tangled"
1010+ "tangled.org/core/appview/pages/markup"
911 "tangled.org/core/knotserver/git"
1012 xrpcerr "tangled.org/core/xrpc/errors"
1113)
···4345 return
4446 }
45474848+ // if any of these files are a readme candidate, pass along its blob contents too
4949+ var readmeFileName string
5050+ var readmeContents string
5151+ for _, file := range files {
5252+ if markup.IsReadmeFile(file.Name) {
5353+ contents, err := gr.RawContent(filepath.Join(path, file.Name))
5454+ if err != nil {
5555+ x.Logger.Error("failed to read contents of file", "path", path, "file", file.Name)
5656+ }
5757+5858+ if utf8.Valid(contents) {
5959+ readmeFileName = file.Name
6060+ readmeContents = string(contents)
6161+ break
6262+ }
6363+ }
6464+ }
6565+4666 // convert NiceTree -> tangled.RepoTree_TreeEntry
4767 treeEntries := make([]*tangled.RepoTree_TreeEntry, len(files))
4868 for i, file := range files {
···83103 Parent: parentPtr,
84104 Dotdot: dotdotPtr,
85105 Files: treeEntries,
106106+ Readme: &tangled.RepoTree_Readme{
107107+ Filename: readmeFileName,
108108+ Contents: readmeContents,
109109+ },
86110 }
8711188112 writeJson(w, response)