233 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
package frontmatter
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"log"
 | 
						|
	"reflect"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"gopkg.in/yaml.v2"
 | 
						|
 | 
						|
	"github.com/BurntSushi/toml"
 | 
						|
	"github.com/hacdias/caddy-filemanager/utils/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()
 | 
						|
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
// Content is the block content
 | 
						|
type Content struct {
 | 
						|
	Other   interface{}
 | 
						|
	Fields  []*Block
 | 
						|
	Arrays  []*Block
 | 
						|
	Objects []*Block
 | 
						|
}
 | 
						|
 | 
						|
// Block is a block
 | 
						|
type Block struct {
 | 
						|
	Name     string
 | 
						|
	Title    string
 | 
						|
	Type     string
 | 
						|
	HTMLType string
 | 
						|
	Content  *Content
 | 
						|
	Parent   *Block
 | 
						|
}
 | 
						|
 | 
						|
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 reflect.ValueOf(content).Kind() {
 | 
						|
	case reflect.Bool:
 | 
						|
		c.Type = "boolean"
 | 
						|
	case reflect.Int, reflect.Float32, reflect.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
 | 
						|
}
 |