Commit 0fccd370 authored by Abiola Ibrahim's avatar Abiola Ibrahim

markdown: documentation done. awaiting test

parent 2c7de8f3
...@@ -61,10 +61,10 @@ type Config struct { ...@@ -61,10 +61,10 @@ type Config struct {
// Map of registered templates // Map of registered templates
Templates map[string]string Templates map[string]string
// Static files // Map of request URL to static files generated
StaticFiles map[string]string StaticFiles map[string]string
// Static files directory // Directory to store static files
StaticDir string StaticDir string
} }
......
...@@ -17,12 +17,17 @@ var ( ...@@ -17,12 +17,17 @@ var (
// Metadata stores a page's metadata // Metadata stores a page's metadata
type Metadata struct { type Metadata struct {
// Page title
Title string Title string
// Page template
Template string Template string
// Variables to be used with Template
Variables map[string]interface{} Variables map[string]interface{}
} }
// Load loads parsed metadata into m // load loads parsed values in parsedMap into Metadata
func (m *Metadata) load(parsedMap map[string]interface{}) { func (m *Metadata) load(parsedMap map[string]interface{}) {
if template, ok := parsedMap["title"]; ok { if template, ok := parsedMap["title"]; ok {
m.Title, _ = template.(string) m.Title, _ = template.(string)
...@@ -35,14 +40,19 @@ func (m *Metadata) load(parsedMap map[string]interface{}) { ...@@ -35,14 +40,19 @@ func (m *Metadata) load(parsedMap map[string]interface{}) {
} }
} }
// MetadataParser parses the page metadata // MetadataParser is a an interface that must be satisfied by each parser
// into Metadata
type MetadataParser interface { type MetadataParser interface {
// Identifiers // Opening identifier
Opening() []byte Opening() []byte
// Closing identifier
Closing() []byte Closing() []byte
// Parse the metadata
Parse([]byte) error Parse([]byte) error
// Parsed metadata.
// Should be called after a call to Parse returns no error
Metadata() Metadata Metadata() Metadata
} }
...@@ -51,7 +61,7 @@ type JSONMetadataParser struct { ...@@ -51,7 +61,7 @@ type JSONMetadataParser struct {
metadata Metadata metadata Metadata
} }
// Parse parses b into metadata // Parse the metadata
func (j *JSONMetadataParser) Parse(b []byte) error { func (j *JSONMetadataParser) Parse(b []byte) error {
m := make(map[string]interface{}) m := make(map[string]interface{})
if err := json.Unmarshal(b, &m); err != nil { if err := json.Unmarshal(b, &m); err != nil {
...@@ -61,7 +71,8 @@ func (j *JSONMetadataParser) Parse(b []byte) error { ...@@ -61,7 +71,8 @@ func (j *JSONMetadataParser) Parse(b []byte) error {
return nil return nil
} }
// Metadata returns the metadata parsed by this parser // Parsed metadata.
// Should be called after a call to Parse returns no error
func (j *JSONMetadataParser) Metadata() Metadata { func (j *JSONMetadataParser) Metadata() Metadata {
return j.metadata return j.metadata
} }
...@@ -81,7 +92,7 @@ type TOMLMetadataParser struct { ...@@ -81,7 +92,7 @@ type TOMLMetadataParser struct {
metadata Metadata metadata Metadata
} }
// Parse parses b into metadata // Parse the metadata
func (t *TOMLMetadataParser) Parse(b []byte) error { func (t *TOMLMetadataParser) Parse(b []byte) error {
m := make(map[string]interface{}) m := make(map[string]interface{})
if err := toml.Unmarshal(b, &m); err != nil { if err := toml.Unmarshal(b, &m); err != nil {
...@@ -91,7 +102,8 @@ func (t *TOMLMetadataParser) Parse(b []byte) error { ...@@ -91,7 +102,8 @@ func (t *TOMLMetadataParser) Parse(b []byte) error {
return nil return nil
} }
// Metadata returns the metadata parsed by this parser // Parsed metadata.
// Should be called after a call to Parse returns no error
func (t *TOMLMetadataParser) Metadata() Metadata { func (t *TOMLMetadataParser) Metadata() Metadata {
return t.metadata return t.metadata
} }
...@@ -106,12 +118,12 @@ func (t *TOMLMetadataParser) Closing() []byte { ...@@ -106,12 +118,12 @@ func (t *TOMLMetadataParser) Closing() []byte {
return []byte("+++") return []byte("+++")
} }
// YAMLMetadataParser is the MetdataParser for YAML // YAMLMetadataParser is the MetadataParser for YAML
type YAMLMetadataParser struct { type YAMLMetadataParser struct {
metadata Metadata metadata Metadata
} }
// Parse parses b into metadata // Parse the metadata
func (y *YAMLMetadataParser) Parse(b []byte) error { func (y *YAMLMetadataParser) Parse(b []byte) error {
m := make(map[string]interface{}) m := make(map[string]interface{})
if err := yaml.Unmarshal(b, &m); err != nil { if err := yaml.Unmarshal(b, &m); err != nil {
...@@ -121,7 +133,8 @@ func (y *YAMLMetadataParser) Parse(b []byte) error { ...@@ -121,7 +133,8 @@ func (y *YAMLMetadataParser) Parse(b []byte) error {
return nil return nil
} }
// Metadata returns the metadata parsed by this parser // Parsed metadata.
// Should be called after a call to Parse returns no error
func (y *YAMLMetadataParser) Metadata() Metadata { func (y *YAMLMetadataParser) Metadata() Metadata {
return y.metadata return y.metadata
} }
......
...@@ -19,9 +19,9 @@ const ( ...@@ -19,9 +19,9 @@ const (
StaticDir = ".caddy_static" StaticDir = ".caddy_static"
) )
// process the contents of a page. // process processes the contents of a page.
// It parses the metadata if any and uses the template if found // It parses the metadata (if any) and uses the template (if found)
func (md Markdown) process(c Config, fpath string, b []byte) ([]byte, error) { func (md Markdown) process(c Config, requestPath string, b []byte) ([]byte, error) {
metadata, markdown, err := extractMetadata(b) metadata, markdown, err := extractMetadata(b)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -46,10 +46,11 @@ func (md Markdown) process(c Config, fpath string, b []byte) ([]byte, error) { ...@@ -46,10 +46,11 @@ func (md Markdown) process(c Config, fpath string, b []byte) ([]byte, error) {
// process markdown // process markdown
markdown = blackfriday.Markdown(markdown, c.Renderer, 0) markdown = blackfriday.Markdown(markdown, c.Renderer, 0)
// set it as body for template // set it as body for template
metadata.Variables["body"] = string(markdown) metadata.Variables["body"] = string(markdown)
return md.processTemplate(c, fpath, tmpl, metadata) return md.processTemplate(c, requestPath, tmpl, metadata)
} }
// extractMetadata extracts metadata content from a page. // extractMetadata extracts metadata content from a page.
...@@ -60,12 +61,12 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) { ...@@ -60,12 +61,12 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) {
reader := bytes.NewBuffer(b) reader := bytes.NewBuffer(b)
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
var parser MetadataParser var parser MetadataParser
// if scanner.Scan() &&
// Read first line // Read first line
if scanner.Scan() { if scanner.Scan() {
line := scanner.Bytes() line := scanner.Bytes()
parser = findParser(line) parser = findParser(line)
// if no parser found // if no parser found,
// assume metadata not present // assume metadata not present
if parser == nil { if parser == nil {
return metadata, b, nil return metadata, b, nil
...@@ -78,7 +79,8 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) { ...@@ -78,7 +79,8 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) {
// Read remaining lines until closing identifier is found // Read remaining lines until closing identifier is found
for scanner.Scan() { for scanner.Scan() {
line := scanner.Bytes() line := scanner.Bytes()
// closing identifier found
// if closing identifier found
if bytes.Equal(bytes.TrimSpace(line), parser.Closing()) { if bytes.Equal(bytes.TrimSpace(line), parser.Closing()) {
// parse the metadata // parse the metadata
err := parser.Parse(buf.Bytes()) err := parser.Parse(buf.Bytes())
...@@ -97,10 +99,12 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) { ...@@ -97,10 +99,12 @@ func extractMetadata(b []byte) (metadata Metadata, markdown []byte, err error) {
buf.Write(line) buf.Write(line)
buf.WriteString("\r\n") buf.WriteString("\r\n")
} }
// closing identifier not found
return metadata, nil, fmt.Errorf("Metadata not closed. '%v' not found", string(parser.Closing())) return metadata, nil, fmt.Errorf("Metadata not closed. '%v' not found", string(parser.Closing()))
} }
// findParser locates the parser for an opening identifier // findParser finds the parser using line that contains opening identifier
func findParser(line []byte) MetadataParser { func findParser(line []byte) MetadataParser {
line = bytes.TrimSpace(line) line = bytes.TrimSpace(line)
for _, parser := range parsers { for _, parser := range parsers {
...@@ -111,13 +115,16 @@ func findParser(line []byte) MetadataParser { ...@@ -111,13 +115,16 @@ func findParser(line []byte) MetadataParser {
return nil return nil
} }
func (md Markdown) processTemplate(c Config, fpath string, tmpl []byte, metadata Metadata) ([]byte, error) { // processTemplate processes a template given a requestPath,
// if template is specified // template (tmpl) and metadata
// replace parse the template func (md Markdown) processTemplate(c Config, requestPath string, tmpl []byte, metadata Metadata) ([]byte, error) {
if tmpl != nil { // if template is not specified,
tmpl = defaultTemplate(c, metadata, fpath) // use the default template
if tmpl == nil {
tmpl = defaultTemplate(c, metadata, requestPath)
} }
// process the template
b := &bytes.Buffer{} b := &bytes.Buffer{}
t, err := template.New("").Parse(string(tmpl)) t, err := template.New("").Parse(string(tmpl))
if err != nil { if err != nil {
...@@ -128,7 +135,7 @@ func (md Markdown) processTemplate(c Config, fpath string, tmpl []byte, metadata ...@@ -128,7 +135,7 @@ func (md Markdown) processTemplate(c Config, fpath string, tmpl []byte, metadata
} }
// generate static page // generate static page
if err = md.generatePage(c, fpath, b.Bytes()); err != nil { if err = md.generatePage(c, requestPath, b.Bytes()); err != nil {
// if static page generation fails, // if static page generation fails,
// nothing fatal, only log the error. // nothing fatal, only log the error.
log.Println(err) log.Println(err)
...@@ -138,40 +145,10 @@ func (md Markdown) processTemplate(c Config, fpath string, tmpl []byte, metadata ...@@ -138,40 +145,10 @@ func (md Markdown) processTemplate(c Config, fpath string, tmpl []byte, metadata
} }
func defaultTemplate(c Config, metadata Metadata, fpath string) []byte { // generatePage generates a static html page from the markdown in content.
// else, use default template func (md Markdown) generatePage(c Config, requestPath string, content []byte) error {
var scripts, styles bytes.Buffer // should not happen,
for _, style := range c.Styles { // must be set on Markdown init.
styles.WriteString(strings.Replace(cssTemplate, "{{url}}", style, 1))
styles.WriteString("\r\n")
}
for _, script := range c.Scripts {
scripts.WriteString(strings.Replace(jsTemplate, "{{url}}", script, 1))
scripts.WriteString("\r\n")
}
// Title is first line (length-limited), otherwise filename
title := metadata.Title
if title == "" {
title = filepath.Base(fpath)
if body, _ := metadata.Variables["body"].([]byte); len(body) > 128 {
title = string(body[:128])
} else if len(body) > 0 {
title = string(body)
}
}
html := []byte(htmlTemplate)
html = bytes.Replace(html, []byte("{{title}}"), []byte(title), 1)
html = bytes.Replace(html, []byte("{{css}}"), styles.Bytes(), 1)
html = bytes.Replace(html, []byte("{{js}}"), scripts.Bytes(), 1)
return html
}
func (md Markdown) generatePage(c Config, fpath string, content []byte) error {
// should not happen
// must be set on init
if c.StaticDir == "" { if c.StaticDir == "" {
return fmt.Errorf("Static directory not set") return fmt.Errorf("Static directory not set")
} }
...@@ -184,12 +161,14 @@ func (md Markdown) generatePage(c Config, fpath string, content []byte) error { ...@@ -184,12 +161,14 @@ func (md Markdown) generatePage(c Config, fpath string, content []byte) error {
} }
} }
filePath := filepath.Join(c.StaticDir, fpath) filePath := filepath.Join(c.StaticDir, requestPath)
// If it is index file, use the directory instead // If it is index file, use the directory instead
if md.IsIndexFile(filepath.Base(fpath)) { if md.IsIndexFile(filepath.Base(requestPath)) {
filePath, _ = filepath.Split(filePath) filePath, _ = filepath.Split(filePath)
} }
// Create the directory in case it is not existing
if err := os.MkdirAll(filePath, os.FileMode(0755)); err != nil { if err := os.MkdirAll(filePath, os.FileMode(0755)); err != nil {
return err return err
} }
...@@ -201,10 +180,41 @@ func (md Markdown) generatePage(c Config, fpath string, content []byte) error { ...@@ -201,10 +180,41 @@ func (md Markdown) generatePage(c Config, fpath string, content []byte) error {
return err return err
} }
c.StaticFiles[fpath] = filePath c.StaticFiles[requestPath] = filePath
return nil return nil
} }
// defaultTemplate constructs a default template.
func defaultTemplate(c Config, metadata Metadata, requestPath string) []byte {
var scripts, styles bytes.Buffer
for _, style := range c.Styles {
styles.WriteString(strings.Replace(cssTemplate, "{{url}}", style, 1))
styles.WriteString("\r\n")
}
for _, script := range c.Scripts {
scripts.WriteString(strings.Replace(jsTemplate, "{{url}}", script, 1))
scripts.WriteString("\r\n")
}
// Title is first line (length-limited), otherwise filename
title := metadata.Title
if title == "" {
title = filepath.Base(requestPath)
if body, _ := metadata.Variables["body"].([]byte); len(body) > 128 {
title = string(body[:128])
} else if len(body) > 0 {
title = string(body)
}
}
html := []byte(htmlTemplate)
html = bytes.Replace(html, []byte("{{title}}"), []byte(title), 1)
html = bytes.Replace(html, []byte("{{css}}"), styles.Bytes(), 1)
html = bytes.Replace(html, []byte("{{js}}"), scripts.Bytes(), 1)
return html
}
const ( const (
htmlTemplate = `<!DOCTYPE html> htmlTemplate = `<!DOCTYPE html>
<html> <html>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment