From 1ae2afb9985b82c7c101395c8a65fd122200c808 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sat, 1 Jul 2017 08:50:42 +0100 Subject: [PATCH] Remove frontmatter interface --- file.go | 103 ++++---------- frontmatter/frontmatter.go | 276 ------------------------------------- frontmatter/runes.go | 29 ---- http.go | 14 +- put.go | 138 ------------------- 5 files changed, 36 insertions(+), 524 deletions(-) delete mode 100644 frontmatter/frontmatter.go delete mode 100644 put.go diff --git a/file.go b/file.go index 4a0d9d22..f3ecf05c 100644 --- a/file.go +++ b/file.go @@ -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,69 +166,41 @@ 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 the file doesn't have any kind of metadata, leave now. + if !frontmatter.HasRune(i.Language) { + return nil } - if r.URL.Query().Get("visual") == "false" { - e.Mode = "content-only" - } + /* + if e.Mode == "complete" && hasRune { + var page parser.Page + content := []byte(i.Content) + // Starts a new buffer and parses the file using Hugo's functions - hasRune := frontmatter.HasRune(i.Content) + buffer := bytes.NewBuffer(content) + page, err = parser.ReadFrom(buffer) - 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 err != nil { + goto Error + } - if e.Mode == "frontmatter-only" && hasRune { - e.FrontMatter.Content, _, err = frontmatter.Pretty([]byte(i.Content)) - if err != nil { - goto Error - } - } - - if e.Mode == "complete" && hasRune { - var page parser.Page - content := []byte(i.Content) - // Starts a new buffer and parses the file using Hugo's functions - - buffer := bytes.NewBuffer(content) - page, err = parser.ReadFrom(buffer) - - if err != nil { - goto Error + // Parses the page content and the frontmatter + i.Content = strings.TrimSpace(string(page.Content())) + e.FrontMatter.Rune = rune(content[0]) + e.FrontMatter.Content, _, err = frontmatter.Pretty(page.FrontMatter()) } - // Parses the page content and the frontmatter - i.Content = strings.TrimSpace(string(page.Content())) - e.FrontMatter.Rune = rune(content[0]) - e.FrontMatter.Content, _, err = frontmatter.Pretty(page.FrontMatter()) - } + if e.Mode == "complete" && !hasRune { + err = errors.New("Complete but without rune") + } */ - 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 { diff --git a/frontmatter/frontmatter.go b/frontmatter/frontmatter.go deleted file mode 100644 index c9e6b337..00000000 --- a/frontmatter/frontmatter.go +++ /dev/null @@ -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 -} diff --git a/frontmatter/runes.go b/frontmatter/runes.go index c6bff60c..2e20fddc 100644 --- a/frontmatter/runes.go +++ b/frontmatter/runes.go @@ -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") - } -} diff --git a/http.go b/http.go index 79fc2875..94af960d 100644 --- a/http.go +++ b/http.go @@ -173,20 +173,20 @@ 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) - if err = c.fm.AfterSave(r, c.fm, c.us); err != nil { - return http.StatusInternalServerError, err + + // 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 diff --git a/put.go b/put.go deleted file mode 100644 index 3100b0ca..00000000 --- a/put.go +++ /dev/null @@ -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 -}