Skip to content

Commit ab65e05

Browse files
Enable support for rendering all markdown files. (#175)
1 parent 0da6386 commit ab65e05

File tree

7 files changed

+91
-36
lines changed

7 files changed

+91
-36
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ Flags:
9292
--disable-redirects disable redirection file handling
9393
--ensure-unexpired-jwt enable time validation for JWT claims "exp" and "nbf"
9494
--etag-max-size string maximum size for etag header generation, where bigger size = more memory usage (default "5M")
95+
--render-all-markdown if enabled, all Markdown files will be rendered using the same rendering as the directory listing READMEs
9596
--gzip enable gzip compression for supported content-types
9697
-h, --help help for http-server
98+
--hide-files-in-markdown hide file and directory listing in markdown rendering
9799
--hide-links hide the links to this project's source code visible in the header and footer
98100
--jwt-key string signing key for JWT authentication
99101
--markdown-before-dir render markdown content before the directory listing

app.go

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func run() error {
124124
flags.StringVar(&srv.CustomNotFoundPage, "custom-404", "", "custom \"page not found\" to serve")
125125
flags.IntVar(&srv.CustomNotFoundStatusCode, "custom-404-code", 0, "custtom status code for pages not found")
126126
flags.BoolVar(&srv.HideFilesInMarkdown, "hide-files-in-markdown", false, "hide file and directory listing in markdown rendering")
127+
flags.BoolVar(&srv.FullMarkdownRender, "render-all-markdown", false, "if enabled, all Markdown files will be rendered using the same rendering as the directory listing READMEs")
127128

128129
//nolint:wrapcheck // no need to wrap this error
129130
return rootCmd.Execute()

internal/server/handlers.go

+44-2
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,52 @@ func (s *Server) showOrRender(w http.ResponseWriter, r *http.Request) {
7676
return
7777
}
7878

79-
// If the path is not a directory, then it's a file, so we can render it
79+
// If the path is not a directory, then it's a file, so we can render it,
80+
// let's check first if it's a markdown file
81+
if ext := strings.ToLower(filepath.Ext(currentPath)); ext == ".md" || ext == ".markdown" {
82+
s.serveMarkdown(currentPath, w, r)
83+
return
84+
}
85+
8086
s.serveFile(0, currentPath, w, r)
8187
}
8288

89+
func (s *Server) serveMarkdown(requestedPath string, w http.ResponseWriter, r *http.Request) {
90+
// Find if among the files there's a markdown readme
91+
var markdownContent bytes.Buffer
92+
if err := s.renderMarkdownFile(requestedPath, &markdownContent); err != nil {
93+
s.printWarningf("unable to generate markdown: %s", err)
94+
httpError(http.StatusInternalServerError, w, "unable to generate markdown for current directory -- see application logs for more information")
95+
return
96+
}
97+
98+
// Define the parent directory
99+
parent := getParentURL(s.PathPrefix, r.URL.Path)
100+
101+
// Render the directory listing
102+
content := map[string]any{
103+
"DirectoryRootPath": s.PathPrefix,
104+
"PageTitle": s.PageTitle,
105+
"CurrentPath": r.URL.Path,
106+
"CacheBuster": s.cacheBuster,
107+
"RequestedPath": requestedPath,
108+
"IsRoot": s.PathPrefix == r.URL.Path,
109+
"UpDirectory": parent,
110+
"Files": nil,
111+
"ShouldRenderFiles": false, // we don't want file rendering for non index pages
112+
"IsSingleMarkdownRender": true, // we want to differentiate single markdown render pages
113+
"HideLinks": s.HideLinks,
114+
"MarkdownContent": markdownContent.String(),
115+
"MarkdownBeforeDir": s.MarkdownBeforeDir,
116+
}
117+
118+
if err := s.templates.ExecuteTemplate(w, "app.tmpl", content); err != nil {
119+
s.printWarningf("unable to render directory listing: %s", err)
120+
httpError(http.StatusInternalServerError, w, "unable to render directory listing -- see application logs for more information")
121+
return
122+
}
123+
}
124+
83125
func (s *Server) walk(requestedPath string, w http.ResponseWriter, r *http.Request) {
84126
// Append index.html or index.htm to the path and see if the index
85127
// file exists, if so, return it instead
@@ -148,7 +190,7 @@ func (s *Server) walk(requestedPath string, w http.ResponseWriter, r *http.Reque
148190

149191
// Find if among the files there's a markdown readme
150192
var markdownContent bytes.Buffer
151-
if err := s.generateMarkdown(requestedPath, files, &markdownContent); err != nil {
193+
if err := s.findAndGenerateMarkdown(requestedPath, files, &markdownContent); err != nil {
152194
s.printWarningf("unable to generate markdown: %s", err)
153195
httpError(http.StatusInternalServerError, w, "unable to generate markdown for current directory -- see application logs for more information")
154196
return

internal/server/markdown.go

+37-31
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,12 @@ import (
2222
// in the directory listing page.
2323
var allowedIndexFiles = []string{"README.md", "README.markdown", "readme.md", "readme.markdown", "index.md", "index.markdown"}
2424

25-
// generateMarkdown generates the markdown needed to render the content
26-
// in the directory listing page
27-
func (s *Server) generateMarkdown(pathLocation string, files []os.FileInfo, placeholder *bytes.Buffer) error {
28-
// Check if markdown is enabled or not, if not, don't bother running
29-
// the rest of the code
30-
if s.DisableMarkdown {
31-
return nil
32-
}
33-
34-
// Find a file name among the available options that can be rendered
35-
var foundFilename string
36-
for _, f := range files {
37-
for _, allowed := range allowedIndexFiles {
38-
if f.Name() == allowed {
39-
foundFilename = allowed
40-
break
41-
}
42-
}
43-
}
44-
45-
// If we couldn't find one, we exit
46-
if foundFilename == "" {
47-
return nil
48-
}
49-
25+
// renderMarkdownFile renders a markdown file from a given location
26+
func (s *Server) renderMarkdownFile(location string, v *bytes.Buffer) error {
5027
// Generate a full path then open the file
51-
fullpath := path.Join(pathLocation, foundFilename)
52-
f, err := os.Open(fullpath)
28+
f, err := os.Open(location)
5329
if err != nil {
54-
return fmt.Errorf("unable to open markdown file %q: %w", fullpath, err)
30+
return fmt.Errorf("unable to open markdown file %q: %w", location, err)
5531
}
5632

5733
// Close the file when we're done
@@ -60,7 +36,7 @@ func (s *Server) generateMarkdown(pathLocation string, files []os.FileInfo, plac
6036
// Copy the file contents to an intermediate buffer
6137
var buf bytes.Buffer
6238
if _, err := io.Copy(&buf, f); err != nil {
63-
return fmt.Errorf("unable to read markdown file %q: %w", fullpath, err)
39+
return fmt.Errorf("unable to read markdown file %q: %w", location, err)
6440
}
6541

6642
// Configure goldmark
@@ -84,13 +60,43 @@ func (s *Server) generateMarkdown(pathLocation string, files []os.FileInfo, plac
8460
)
8561

8662
// Render the markdown
87-
if err := md.Convert(buf.Bytes(), placeholder); err != nil {
88-
return fmt.Errorf("unable to render markdown file %q: %w", fullpath, err)
63+
if err := md.Convert(buf.Bytes(), v); err != nil {
64+
return fmt.Errorf("unable to render markdown file %q: %w", location, err)
8965
}
9066

9167
return nil
9268
}
9369

70+
// generateMarkdown generates the markdown needed to render the content
71+
// in the directory listing page
72+
func (s *Server) findAndGenerateMarkdown(pathLocation string, files []os.FileInfo, placeholder *bytes.Buffer) error {
73+
// Check if markdown is enabled or not, if not, don't bother running
74+
// the rest of the code
75+
if s.DisableMarkdown {
76+
return nil
77+
}
78+
79+
// Find a file name among the available options that can be rendered
80+
var foundFilename string
81+
for _, f := range files {
82+
for _, allowed := range allowedIndexFiles {
83+
if f.Name() == allowed {
84+
foundFilename = allowed
85+
break
86+
}
87+
}
88+
}
89+
90+
// If we couldn't find one, we exit
91+
if foundFilename == "" {
92+
return nil
93+
}
94+
95+
// Generate the full path of the found file
96+
fullpath := path.Join(pathLocation, foundFilename)
97+
return s.renderMarkdownFile(fullpath, placeholder)
98+
}
99+
94100
// generateBannerMarkdown generates the markdown needed to render the banner
95101
// in the directory listing page.
96102
func (s *Server) generateBannerMarkdown() (string, error) {

internal/server/server.go

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Server struct {
4040
DisableMarkdown bool
4141
MarkdownBeforeDir bool
4242
HideFilesInMarkdown bool
43+
FullMarkdownRender bool
4344

4445
// Redirection handling
4546
DisableRedirects bool

internal/server/startup.go

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ func (s *Server) PrintStartup() {
7878
fmt.Fprintln(s.LogOutput, startupPrefix, "Markdown rendering before directory listing enabled")
7979
}
8080

81+
if s.FullMarkdownRender {
82+
fmt.Fprintln(s.LogOutput, startupPrefix, "All markdown file rendering enabled")
83+
}
84+
8185
if !s.DisableRedirects {
8286
if s.redirects != nil {
8387
fmt.Fprintf(s.LogOutput, "%s Redirections enabled from %q (found %d redirections)\n", startupPrefix, s.getPathToRedirectionsFile(), len(s.redirects.Rules))

internal/server/templates/listing.tmpl

+2-3
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,16 @@
4545
</div>
4646
{{- end }}
4747

48-
{{- if not .MarkdownBeforeDir }}{{- with .MarkdownContent }}
48+
{{- if not .MarkdownBeforeDir }}{{- if .MarkdownContent }}
4949
{{ template "markdown" . }}
5050
{{- end }}{{- end }}
5151
</div>
5252
</section>
5353

5454
{{- end }}
5555

56-
5756
{{- define "markdown" }}
5857
<div class="card-large">
59-
<div class="markdown-body">{{- . | unsafeHTML }}</div>
58+
<div class="markdown-body">{{- .MarkdownContent | unsafeHTML }}</div>
6059
</div>
6160
{{- end }}

0 commit comments

Comments
 (0)