Remove frontmatter interface

pull/144/head
Henrique Dias 2017-07-01 08:50:42 +01:00
parent 3d25185a55
commit 1ae2afb998
No known key found for this signature in database
GPG Key ID: 936F5EB68D786730
5 changed files with 36 additions and 524 deletions

73
file.go
View File

@ -1,7 +1,6 @@
package filemanager
import (
"bytes"
"context"
"crypto/md5"
"crypto/sha1"
@ -22,7 +21,6 @@ import (
"time"
"github.com/hacdias/filemanager/frontmatter"
"github.com/spf13/hugo/parser"
)
var (
@ -54,9 +52,10 @@ type file struct {
// Stores the content of a text file.
Content string `json:"content,omitempty"`
Editor *editor `json:"editor,omitempty"`
*listing `json:",omitempty"`
Metadata string `json:"metadata,omitempty"`
Language string `json:"language,omitempty"`
}
// A listing is the context used to fill out a template.
@ -75,20 +74,6 @@ type listing struct {
Display string `json:"display"`
}
// editor contains the information to fill the editor template.
type editor struct {
// Indicates if the content has only frontmatter, only content, or both.
Mode string `json:"type"`
// File content language.
Language string `json:"language"`
// This indicates if the editor should be visual or not.
Visual bool `json:"visual"`
FrontMatter struct {
Content *frontmatter.Content `json:"content"`
Rune rune `json:"rune"`
} `json:"frontmatter"`
}
// getInfo gets the file information and, in case of error, returns the
// respective HTTP error code
func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
@ -181,41 +166,19 @@ func (i *file) getListing(c *requestContext, r *http.Request) error {
// getEditor gets the editor based on a Info struct
func (i *file) getEditor(r *http.Request) error {
var err error
i.Language = editorLanguage(i.Extension)
// Create a new editor variable and set the mode
e := &editor{
Language: editorLanguage(i.Extension),
// If the editor will hold only content, leave now.
if editorMode(i.Language) == "content" {
return nil
}
e.Mode = editorMode(e.Language)
if e.Mode == "frontmatter-only" || e.Mode == "complete" {
e.Visual = true
}
if r.URL.Query().Get("visual") == "false" {
e.Mode = "content-only"
}
hasRune := frontmatter.HasRune(i.Content)
if e.Mode == "frontmatter-only" && !hasRune {
e.FrontMatter.Rune, err = frontmatter.StringFormatToRune(e.Language)
if err != nil {
goto Error
}
i.Content = frontmatter.AppendRune(i.Content, e.FrontMatter.Rune)
hasRune = true
}
if e.Mode == "frontmatter-only" && hasRune {
e.FrontMatter.Content, _, err = frontmatter.Pretty([]byte(i.Content))
if err != nil {
goto Error
}
// If the file doesn't have any kind of metadata, leave now.
if !frontmatter.HasRune(i.Language) {
return nil
}
/*
if e.Mode == "complete" && hasRune {
var page parser.Page
content := []byte(i.Content)
@ -236,14 +199,8 @@ func (i *file) getEditor(r *http.Request) error {
if e.Mode == "complete" && !hasRune {
err = errors.New("Complete but without rune")
}
} */
Error:
if e.Mode == "content-only" || err != nil {
e.Mode = "content-only"
}
i.Editor = e
return nil
}
@ -463,13 +420,11 @@ var textExtensions = [...]string{
func editorMode(language string) string {
switch language {
case "json", "toml", "yaml":
return "frontmatter-only"
case "markdown", "asciidoc", "rst":
return "complete"
return "content+metadata"
}
return "content-only"
return "content"
}
func editorLanguage(mode string) string {

View File

@ -1,276 +0,0 @@
package frontmatter
import (
"bytes"
"encoding/json"
"errors"
"log"
"reflect"
"sort"
"strconv"
"strings"
"gopkg.in/yaml.v2"
"github.com/BurntSushi/toml"
"github.com/hacdias/filemanager/variables"
"github.com/spf13/cast"
)
const (
mainName = "#MAIN#"
objectType = "object"
arrayType = "array"
)
var mainTitle = ""
// Pretty creates a new FrontMatter object
func Pretty(content []byte) (*Content, string, error) {
data, err := Unmarshal(content)
if err != nil {
return &Content{}, "", err
}
kind := reflect.ValueOf(data).Kind()
if kind == reflect.Invalid {
return &Content{}, "", nil
}
object := new(Block)
object.Type = objectType
object.Name = mainName
if kind == reflect.Map {
object.Type = objectType
} else if kind == reflect.Slice || kind == reflect.Array {
object.Type = arrayType
}
return rawToPretty(data, object), mainTitle, nil
}
// Unmarshal returns the data of the frontmatter
func Unmarshal(content []byte) (interface{}, error) {
mark := rune(content[0])
var data interface{}
switch mark {
case '-':
// If it's YAML
if err := yaml.Unmarshal(content, &data); err != nil {
return nil, err
}
case '+':
// If it's TOML
content = bytes.Replace(content, []byte("+"), []byte(""), -1)
if _, err := toml.Decode(string(content), &data); err != nil {
return nil, err
}
case '{', '[':
// If it's JSON
if err := json.Unmarshal(content, &data); err != nil {
return nil, err
}
default:
return nil, errors.New("Invalid frontmatter type")
}
return data, nil
}
// Marshal encodes the interface in a specific format
func Marshal(data interface{}, mark rune) ([]byte, error) {
b := new(bytes.Buffer)
switch mark {
case '+':
enc := toml.NewEncoder(b)
err := enc.Encode(data)
if err != nil {
return nil, err
}
return b.Bytes(), nil
case '{':
by, err := json.MarshalIndent(data, "", " ")
if err != nil {
return nil, err
}
b.Write(by)
_, err = b.Write([]byte("\n"))
if err != nil {
return nil, err
}
return b.Bytes(), nil
case '-':
by, err := yaml.Marshal(data)
if err != nil {
return nil, err
}
b.Write(by)
_, err = b.Write([]byte("..."))
if err != nil {
return nil, err
}
return b.Bytes(), nil
default:
return nil, errors.New("Unsupported Format provided")
}
}
// Content is the block content
type Content struct {
Other interface{} `json:"other"`
Fields []*Block `json:"fields"`
Arrays []*Block `json:"arrays"`
Objects []*Block `json:"objects"`
}
// Block is a block
type Block struct {
Name string `json:"name"`
Title string `json:"title"`
Type string `json:"type"`
HTMLType string `json:"htmlType"`
Content *Content `json:"content"`
Parent *Block `json:"-"`
}
func rawToPretty(config interface{}, parent *Block) *Content {
objects := []*Block{}
arrays := []*Block{}
fields := []*Block{}
cnf := map[string]interface{}{}
kind := reflect.TypeOf(config)
switch kind {
case reflect.TypeOf(map[interface{}]interface{}{}):
for key, value := range config.(map[interface{}]interface{}) {
cnf[key.(string)] = value
}
case reflect.TypeOf([]map[string]interface{}{}):
for index, value := range config.([]map[string]interface{}) {
cnf[strconv.Itoa(index)] = value
}
case reflect.TypeOf([]map[interface{}]interface{}{}):
for index, value := range config.([]map[interface{}]interface{}) {
cnf[strconv.Itoa(index)] = value
}
case reflect.TypeOf([]interface{}{}):
for index, value := range config.([]interface{}) {
cnf[strconv.Itoa(index)] = value
}
default:
cnf = config.(map[string]interface{})
}
for name, element := range cnf {
if variables.IsMap(element) {
objects = append(objects, handleObjects(element, parent, name))
} else if variables.IsSlice(element) {
arrays = append(arrays, handleArrays(element, parent, name))
} else {
if name == "title" && parent.Name == mainName {
mainTitle = element.(string)
}
fields = append(fields, handleFlatValues(element, parent, name))
}
}
sort.Sort(sortByTitle(fields))
sort.Sort(sortByTitle(arrays))
sort.Sort(sortByTitle(objects))
return &Content{
Fields: fields,
Arrays: arrays,
Objects: objects,
}
}
type sortByTitle []*Block
func (f sortByTitle) Len() int { return len(f) }
func (f sortByTitle) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f sortByTitle) Less(i, j int) bool {
return strings.ToLower(f[i].Name) < strings.ToLower(f[j].Name)
}
func handleObjects(content interface{}, parent *Block, name string) *Block {
c := new(Block)
c.Parent = parent
c.Type = objectType
c.Title = name
if parent.Name == mainName {
c.Name = c.Title
} else if parent.Type == arrayType {
c.Name = parent.Name + "[" + name + "]"
} else {
c.Name = parent.Name + "." + c.Title
}
c.Content = rawToPretty(content, c)
return c
}
func handleArrays(content interface{}, parent *Block, name string) *Block {
c := new(Block)
c.Parent = parent
c.Type = arrayType
c.Title = name
if parent.Name == mainName {
c.Name = name
} else {
c.Name = parent.Name + "." + name
}
c.Content = rawToPretty(content, c)
return c
}
func handleFlatValues(content interface{}, parent *Block, name string) *Block {
c := new(Block)
c.Parent = parent
switch content.(type) {
case bool:
c.Type = "boolean"
case int, float32, float64:
c.Type = "number"
default:
c.Type = "string"
}
c.Content = &Content{Other: content}
switch strings.ToLower(name) {
case "description":
c.HTMLType = "textarea"
case "date", "publishdate":
c.HTMLType = "datetime"
c.Content = &Content{Other: cast.ToTime(content)}
default:
c.HTMLType = "text"
}
if parent.Type == arrayType {
c.Name = parent.Name + "[]"
c.Title = content.(string)
} else if parent.Type == objectType {
c.Title = name
c.Name = parent.Name + "." + name
if parent.Name == mainName {
c.Name = name
}
} else {
log.Panic("Parent type not allowed in handleFlatValues.")
}
return c
}

View File

@ -1,7 +1,6 @@
package frontmatter
import (
"errors"
"strings"
)
@ -27,31 +26,3 @@ func AppendRune(frontmatter string, mark rune) string {
return frontmatter
}
// RuneToStringFormat converts the rune to a string with the format
func RuneToStringFormat(mark rune) (string, error) {
switch mark {
case '-':
return "yaml", nil
case '+':
return "toml", nil
case '{', '}':
return "json", nil
default:
return "", errors.New("Unsupported format type")
}
}
// StringFormatToRune converts the format name to its rune
func StringFormatToRune(format string) (rune, error) {
switch format {
case "yaml":
return '-', nil
case "toml":
return '+', nil
case "json":
return '{', nil
default:
return '0', errors.New("Unsupported format type")
}
}

10
http.go
View File

@ -173,21 +173,21 @@ func serveWebDAV(c *requestContext, w http.ResponseWriter, r *http.Request) (int
}
}
// Preprocess the PUT request if it's the case
// Execute beforeSave if it is a PUT request.
if r.Method == http.MethodPut {
if err = c.fm.BeforeSave(r, c.fm, c.us); err != nil {
return http.StatusInternalServerError, err
}
if put(c, w, r) != nil {
return http.StatusInternalServerError, err
}
}
c.fm.handler.ServeHTTP(w, r)
// Execute afterSave if it is a PUT request.
if r.Method == http.MethodPut {
if err = c.fm.AfterSave(r, c.fm, c.us); err != nil {
return http.StatusInternalServerError, err
}
}
return 0, nil
}

138
put.go
View File

@ -1,138 +0,0 @@
package filemanager
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/hacdias/filemanager/frontmatter"
)
// put is used to update a file that was edited
func put(c *requestContext, w http.ResponseWriter, r *http.Request) (err error) {
var (
data = map[string]interface{}{}
file []byte
kind string
rawBuffer = new(bytes.Buffer)
)
kind = r.Header.Get("kind")
rawBuffer.ReadFrom(r.Body)
if kind != "" {
err = json.Unmarshal(rawBuffer.Bytes(), &data)
if err != nil {
return
}
}
switch kind {
case "frontmatter-only":
if file, err = parseFrontMatterOnlyFile(data, r.URL.Path); err != nil {
return
}
case "content-only":
mainContent := data["content"].(string)
mainContent = strings.TrimSpace(mainContent)
file = []byte(mainContent)
case "complete":
var mark rune
if v := r.Header.Get("Rune"); v != "" {
var n int
n, err = strconv.Atoi(v)
if err != nil {
return err
}
mark = rune(n)
}
if file, err = parseCompleteFile(data, r.URL.Path, mark); err != nil {
return
}
default:
file = rawBuffer.Bytes()
}
// Overwrite the request Body
r.Body = ioutil.NopCloser(bytes.NewReader(file))
return
}
// parseFrontMatterOnlyFile parses a frontmatter only file
func parseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, error) {
frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".")
f, err := parseFrontMatter(data, frontmatter)
fString := string(f)
// If it's toml or yaml, strip frontmatter identifier
if frontmatter == "toml" {
fString = strings.TrimSuffix(fString, "+++\n")
fString = strings.TrimPrefix(fString, "+++\n")
}
if frontmatter == "yaml" {
fString = strings.TrimSuffix(fString, "---\n")
fString = strings.TrimPrefix(fString, "---\n")
}
f = []byte(fString)
return f, err
}
// parseFrontMatter is the frontmatter parser
func parseFrontMatter(data interface{}, front string) ([]byte, error) {
var mark rune
switch front {
case "toml":
mark = '+'
case "json":
mark = '{'
case "yaml":
mark = '-'
default:
return nil, errors.New("Unsupported Format provided")
}
return frontmatter.Marshal(data, mark)
}
// parseCompleteFile parses a complete file
func parseCompleteFile(data map[string]interface{}, filename string, mark rune) ([]byte, error) {
mainContent := ""
if _, ok := data["content"]; ok {
// The main content of the file
mainContent = data["content"].(string)
mainContent = "\n\n" + strings.TrimSpace(mainContent) + "\n"
// Removes the main content from the rest of the frontmatter
delete(data, "content")
}
if _, ok := data["date"]; ok {
data["date"] = data["date"].(string) + ":00"
}
front, err := frontmatter.Marshal(data, mark)
if err != nil {
return []byte{}, err
}
front = []byte(frontmatter.AppendRune(string(front), mark))
// Generates the final file
f := new(bytes.Buffer)
f.Write(front)
f.Write([]byte(mainContent))
return f.Bytes(), nil
}