diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d52f8163..00000000 --- a/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -assets.go -node_modules -.sass-cache -temp -copied_file.txt -builds -release diff --git a/assets/public/css/styles.css b/assets/public/css/styles.css deleted file mode 100644 index 79e570aa..00000000 --- a/assets/public/css/styles.css +++ /dev/null @@ -1,97 +0,0 @@ -.editor input[type="text"], .editor input[type="datetime-local"], .editor textarea { - background-color: #f5f3f3; - border-radius: .5em; - border: 1px solid #ccc; - outline: none; - padding: .5em; - line-height: 1.2em; - width: 100%; - font-size: 1rem; - margin: 0; - transition: all 0.3s; -} -input[type="text"]:focus { - border-bottom: 1px solid #26a69a; -} -.editor .block { - margin-bottom: 1em; -} -.editor fieldset { - border: 0; - margin: 0; - padding: 0; -} -.editor h1 textarea { - font-size: 2em; - font-weight: 400; - resize: none; - overflow: hidden; - padding: 0; - line-height: 1em; - height: 1em; - background-color: transparent; - border: 0; -} -.editor fieldset h3 { - display: inline-block -} -.editor label { - font-size: .8em; - color: #8b8b8b; - font-weight: 500; -} -.editor .options { - column-count: 2; - column-gap: 1em; -} -.editor .block { - break-inside: avoid; - position: relative; - z-index: 0; -} -.editor .block .delete { - position: absolute; - right: 0; - bottom: 1px; - height: 2.5em; - padding: 0 .7em; - background: transparent; - color: rgba(0, 0, 0, 0.5); -} - -/* BUTTONS */ - -.editor button, .editor input[type="submit"] { - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); - border: none; - border-radius: 2px; - height: 36px; - line-height: 36px; - outline: 0; - padding: 0 2rem; - text-transform: uppercase; - text-decoration: none; - color: #fff; - background-color: #26a69a; - text-align: center; - letter-spacing: .5px; - position: relative; - cursor: pointer; - display: inline-block; - overflow: hidden; - vertical-align: middle; - transition: all .3s ease-out; -} - -/* TOOLBAR */ - -.editor #actions { - display: flex; - margin: 1em 0; -} -.editor #actions> div { - flex-basis: 50%; -} -.editor #actions div:nth-child(2) { - text-align: right; -} diff --git a/assets/public/js/application.js b/assets/public/js/application.js index d1e6eb60..ad9a93a7 100644 --- a/assets/public/js/application.js +++ b/assets/public/js/application.js @@ -1,5 +1 @@ -"use strict"; - -var ace = document.createElement('script'); -ace.src = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js'; -document.head.appendChild(ace); +'use strict'; diff --git a/assets/templates/editor.tmpl b/assets/templates/editor.tmpl deleted file mode 100644 index b2e4ae91..00000000 --- a/assets/templates/editor.tmpl +++ /dev/null @@ -1,62 +0,0 @@ -{{ define "content" }} -
-{{ if eq .Class "complete" }} -

- -

- -
-
-{{ template "frontmatter" .FrontMatter }} - -
- -
-{{ if eq .Mode "markdown" }} - -{{ end}} - -
- -
- -{{ else if eq .Class "content-only" }} -
-

{{ .Name }}

- -
-
- -
-
-{{ else }} -
-

{{ .Name }}

- -
-{{ template "frontmatter" .FrontMatter }} - -
-
-{{ end }} - -
-
- -
-
-{{ if and (eq .Class "complete") ( .IsPost ) }} -{{ end }} - -
-
-
- -{{ end }} diff --git a/assets/templates/options.tmpl b/assets/templates/options.tmpl deleted file mode 100644 index e10b039b..00000000 --- a/assets/templates/options.tmpl +++ /dev/null @@ -1,50 +0,0 @@ -{{ define "frontmatter" }} - {{ range $key, $value := . }} - - {{ if or (eq $value.Type "object") (eq $value.Type "array") }} -
-
-

{{ SplitCapitalize $value.Title }}

- - -
- -
- {{ template "frontmatter" $value.Content }} - -
- -
- {{ else }} - - {{ if not (eq $value.Parent.Type "array") }} -
- - - - {{ end }} - - {{ if eq $value.Parent.Type "array" }} -
- {{ end }} - - {{ if eq $value.HTMLType "textarea" }} - - {{ else if eq $value.HTMLType "datetime" }} - - {{ else }} - - {{ end }} - - {{ if not (eq $value.Parent.Type "array") }} -
- {{ end }} - - {{ if eq $value.Parent.Type "array" }} - -
- {{ end }} - - {{ end }} - {{ end }} -{{ end }} diff --git a/binary.go b/binary.go index e8011866..3aabdc3e 100644 --- a/binary.go +++ b/binary.go @@ -1,9 +1,6 @@ // Code generated by go-bindata. // sources: -// assets/public/css/styles.css // assets/public/js/application.js -// assets/templates/editor.tmpl -// assets/templates/options.tmpl // DO NOT EDIT! package hugo @@ -30,24 +27,6 @@ type asset struct { info os.FileInfo } -// publicCssStylesCss reads file data from disk. It returns an error on failure. -func publicCssStylesCss() (*asset, error) { - path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-hugo\\assets\\public\\css\\styles.css" - name := "public/css/styles.css" - bytes, err := bindataRead(path, name) - if err != nil { - return nil, err - } - - fi, err := os.Stat(path) - if err != nil { - err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err) - } - - a := &asset{bytes: bytes, info: fi} - return a, err -} - // publicJsApplicationJs reads file data from disk. It returns an error on failure. func publicJsApplicationJs() (*asset, error) { path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-hugo\\assets\\public\\js\\application.js" @@ -66,42 +45,6 @@ func publicJsApplicationJs() (*asset, error) { return a, err } -// templatesEditorTmpl reads file data from disk. It returns an error on failure. -func templatesEditorTmpl() (*asset, error) { - path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-hugo\\assets\\templates\\editor.tmpl" - name := "templates/editor.tmpl" - bytes, err := bindataRead(path, name) - if err != nil { - return nil, err - } - - fi, err := os.Stat(path) - if err != nil { - err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err) - } - - a := &asset{bytes: bytes, info: fi} - return a, err -} - -// templatesOptionsTmpl reads file data from disk. It returns an error on failure. -func templatesOptionsTmpl() (*asset, error) { - path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-hugo\\assets\\templates\\options.tmpl" - name := "templates/options.tmpl" - bytes, err := bindataRead(path, name) - if err != nil { - return nil, err - } - - fi, err := os.Stat(path) - if err != nil { - err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err) - } - - a := &asset{bytes: bytes, info: fi} - return a, err -} - // Asset loads and returns the asset for the given name. // It returns an error if the asset could not be found or // could not be loaded. @@ -154,10 +97,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "public/css/styles.css": publicCssStylesCss, "public/js/application.js": publicJsApplicationJs, - "templates/editor.tmpl": templatesEditorTmpl, - "templates/options.tmpl": templatesOptionsTmpl, } // AssetDir returns the file names below a certain @@ -201,17 +141,10 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ "public": &bintree{nil, map[string]*bintree{ - "css": &bintree{nil, map[string]*bintree{ - "styles.css": &bintree{publicCssStylesCss, map[string]*bintree{}}, - }}, "js": &bintree{nil, map[string]*bintree{ "application.js": &bintree{publicJsApplicationJs, map[string]*bintree{}}, }}, }}, - "templates": &bintree{nil, map[string]*bintree{ - "editor.tmpl": &bintree{templatesEditorTmpl, map[string]*bintree{}}, - "options.tmpl": &bintree{templatesOptionsTmpl, map[string]*bintree{}}, - }}, }} // RestoreAsset restores an asset under the given directory diff --git a/commands.go b/commands.go deleted file mode 100644 index 080cc187..00000000 --- a/commands.go +++ /dev/null @@ -1,36 +0,0 @@ -package hugo - -import ( - "log" - "os" - "os/exec" -) - -// Run executes an external command -func Run(command string, args []string, path string) error { - cmd := exec.Command(command, args...) - cmd.Dir = path - cmd.Stdout = os.Stderr - cmd.Stderr = os.Stderr - return cmd.Run() -} - -// RunHugo is used to run the static website generator -func RunHugo(c *Config, force bool) { - os.RemoveAll(c.Public) - - // Prevent running if watching is enabled - if b, pos := StringInSlice("--watch", c.Args); b && !force { - if len(c.Args) > pos && c.Args[pos+1] != "false" { - return - } - - if len(c.Args) == pos+1 { - return - } - } - - if err := Run(c.Hugo, c.Args, c.Root); err != nil { - log.Panic(err) - } -} diff --git a/frontmatter.go b/frontmatter.go deleted file mode 100644 index 5fb3090e..00000000 --- a/frontmatter.go +++ /dev/null @@ -1,172 +0,0 @@ -package hugo - -import ( - "log" - "reflect" - "sort" - "strings" - - "github.com/spf13/cast" - "github.com/spf13/hugo/parser" -) - -const ( - mainName = "#MAIN#" - objectType = "object" - arrayType = "array" -) - -var mainTitle = "" - -// Pretty creates a new FrontMatter object -func Pretty(content []byte) (interface{}, string, error) { - frontType := parser.DetectFrontMatter(rune(content[0])) - front, err := frontType.Parse(content) - - if err != nil { - return []string{}, mainTitle, err - } - - object := new(frontmatter) - object.Type = objectType - object.Name = mainName - - return rawToPretty(front, object), mainTitle, nil -} - -type frontmatter struct { - Name string - Title string - Content interface{} - Type string - HTMLType string - Parent *frontmatter -} - -func rawToPretty(config interface{}, parent *frontmatter) interface{} { - objects := []*frontmatter{} - arrays := []*frontmatter{} - fields := []*frontmatter{} - - cnf := map[string]interface{}{} - - if reflect.TypeOf(config) == reflect.TypeOf(map[interface{}]interface{}{}) { - for key, value := range config.(map[interface{}]interface{}) { - cnf[key.(string)] = value - } - } else if reflect.TypeOf(config) == reflect.TypeOf([]interface{}{}) { - for key, value := range config.([]interface{}) { - cnf[string(key)] = value - } - } else { - cnf = config.(map[string]interface{}) - } - - for name, element := range cnf { - if IsMap(element) { - objects = append(objects, handleObjects(element, parent, name)) - } else if 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(objects)) - sort.Sort(sortByTitle(arrays)) - sort.Sort(sortByTitle(fields)) - - settings := []*frontmatter{} - settings = append(settings, fields...) - settings = append(settings, arrays...) - settings = append(settings, objects...) - return settings -} - -type sortByTitle []*frontmatter - -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 *frontmatter, name string) *frontmatter { - c := new(frontmatter) - 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 + "[]" - } else { - c.Name = parent.Name + "[" + c.Title + "]" - } - - c.Content = rawToPretty(content, c) - return c -} - -func handleArrays(content interface{}, parent *frontmatter, name string) *frontmatter { - c := new(frontmatter) - 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 *frontmatter, name string) *frontmatter { - c := new(frontmatter) - 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 - - switch strings.ToLower(name) { - case "description": - c.HTMLType = "textarea" - case "date", "publishdate": - c.HTMLType = "datetime" - c.Content = 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/get.go b/get.go deleted file mode 100644 index ac799485..00000000 --- a/get.go +++ /dev/null @@ -1,174 +0,0 @@ -package hugo - -import ( - "bytes" - "fmt" - "html/template" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/hacdias/caddy-filemanager" - "github.com/spf13/hugo/parser" -) - -type editor struct { - Name string - Class string - IsPost bool - Mode string - Content string - BaseURL string - FrontMatter interface{} -} - -// GET handles the GET method on editor page -func (h Hugo) GET(w http.ResponseWriter, r *http.Request, filename string) (int, error) { - // Check if the file exists. - if _, err := os.Stat(filename); os.IsNotExist(err) { - return http.StatusNotFound, err - } else if os.IsPermission(err) { - return http.StatusForbidden, err - } else if err != nil { - return http.StatusInternalServerError, err - } - - // Open the file and check if there was some error while opening - file, err := ioutil.ReadFile(filename) - if os.IsPermission(err) { - return http.StatusForbidden, err - } else if err != nil { - return http.StatusInternalServerError, err - } - - // Create a new editor variable and set the extension - data := new(editor) - data.Mode = strings.TrimPrefix(filepath.Ext(filename), ".") - data.Name = strings.Replace(filename, h.Config.Root, "", 1) - data.IsPost = false - data.BaseURL = h.Config.BaseURL - data.Mode = sanitizeMode(data.Mode) - - var parserPage parser.Page - - // Handle the content depending on the file extension - switch data.Mode { - case "markdown", "asciidoc", "rst": - if hasFrontMatterRune(file) { - // Starts a new buffer and parses the file using Hugo's functions - buffer := bytes.NewBuffer(file) - parserPage, err = parser.ReadFrom(buffer) - if err != nil { - return http.StatusInternalServerError, err - } - - if strings.Contains(string(parserPage.FrontMatter()), "date") { - data.IsPost = true - } - - // Parses the page content and the frontmatter - data.Content = strings.TrimSpace(string(parserPage.Content())) - data.FrontMatter, data.Name, err = Pretty(parserPage.FrontMatter()) - data.Class = "complete" - } else { - // The editor will handle only content - data.Class = "content-only" - data.Content = string(file) - } - case "json", "toml", "yaml": - // Defines the class and declares an error - data.Class = "frontmatter-only" - - // Checks if the file already has the frontmatter rune and parses it - if hasFrontMatterRune(file) { - data.FrontMatter, _, err = Pretty(file) - } else { - data.FrontMatter, _, err = Pretty(appendFrontMatterRune(file, data.Mode)) - } - - // Check if there were any errors - if err != nil { - return http.StatusInternalServerError, err - } - default: - // The editor will handle only content - data.Class = "content-only" - data.Content = string(file) - } - - // Create the functions map, then the template, check for erros and - // execute the template if there aren't errors - functions := template.FuncMap{ - "SplitCapitalize": SplitCapitalize, - "Defined": Defined, - } - - var code int - - page := &filemanager.Page{ - Info: &filemanager.PageInfo{ - IsDir: false, - Config: &h.FileManager.Configs[0], - Name: data.Name, - Data: data, - }, - } - - templates := []string{"options", "editor"} - for _, t := range templates { - code, err = page.AddTemplate(t, Asset, functions) - if err != nil { - return code, err - } - } - - templates = []string{"actions", "base"} - for _, t := range templates { - code, err = page.AddTemplate(t, filemanager.Asset, nil) - if err != nil { - return code, err - } - } - - code, err = page.PrintAsHTML(w) - fmt.Println(err) - return code, err -} - -func hasFrontMatterRune(file []byte) bool { - return strings.HasPrefix(string(file), "---") || - strings.HasPrefix(string(file), "+++") || - strings.HasPrefix(string(file), "{") -} - -func appendFrontMatterRune(frontmatter []byte, language string) []byte { - switch language { - case "yaml": - return []byte("---\n" + string(frontmatter) + "\n---") - case "toml": - return []byte("+++\n" + string(frontmatter) + "\n+++") - case "json": - return frontmatter - } - - return frontmatter -} - -func sanitizeMode(extension string) string { - switch extension { - case "md", "markdown", "mdown", "mmark": - return "markdown" - case "asciidoc", "adoc", "ad": - return "asciidoc" - case "rst": - return "rst" - case "html", "htm": - return "html" - case "js": - return "javascript" - default: - return extension - } -} diff --git a/hugo.go b/hugo.go index d282973c..684b3a52 100644 --- a/hugo.go +++ b/hugo.go @@ -7,6 +7,7 @@ package hugo import ( + "log" "mime" "net/http" "os" @@ -14,120 +15,54 @@ import ( "strings" "github.com/hacdias/caddy-filemanager" + "github.com/hacdias/caddy-filemanager/utils/variables" + "github.com/hacdias/caddy-hugo/utils/commands" "github.com/mholt/caddy/caddyhttp/httpserver" ) -// AssetsURL is the base url of the assets -const AssetsURL = "/_hugointernal" - -// Hugo contais the next middleware to be run and the configuration -// of the current one. +// Hugo is hugo type Hugo struct { - FileManager *filemanager.FileManager Next httpserver.Handler Config *Config + FileManager *filemanager.FileManager } -// ServeHTTP is the main function of the whole plugin that routes every single -// request to its function. func (h Hugo) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - // Check if the current request if for this plugin if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL) { - // Check if we are asking for the assets if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + AssetsURL) { - return h.ServeAssets(w, r) - } - // If the url matches exactly with /{admin}/settings/, redirect - // to the page of the configuration file - if r.URL.Path == h.Config.BaseURL+"/settings/" { - var frontmatter string - - if _, err := os.Stat(h.Config.Root + "config.yaml"); err == nil { - frontmatter = "yaml" - } - - if _, err := os.Stat(h.Config.Root + "config.json"); err == nil { - frontmatter = "json" - } - - if _, err := os.Stat(h.Config.Root + "config.toml"); err == nil { - frontmatter = "toml" - } - - http.Redirect(w, r, h.Config.BaseURL+"/config."+frontmatter, http.StatusTemporaryRedirect) - return 0, nil - } - - if strings.HasPrefix(r.URL.Path, h.Config.BaseURL+"/api/git/") && r.Method == http.MethodPost { - //return HandleGit(w, r, h.Config) - return 0, nil - } - - if h.ShouldHandle(r) { - filename := strings.Replace(r.URL.Path, h.Config.BaseURL, h.Config.Root, 1) - switch r.Method { - case http.MethodGet: - return h.GET(w, r, filename) - case http.MethodPost: - return h.POST(w, r, filename) - default: - return h.FileManager.ServeHTTP(w, r) - } } return h.FileManager.ServeHTTP(w, r) } + return h.Next.ServeHTTP(w, r) } -var extensions = []string{ - "md", "markdown", "mdown", "mmark", - "asciidoc", "adoc", "ad", - "rst", - "html", "htm", - "js", - "toml", "yaml", "json", -} +// RunHugo is used to run the static website generator +func RunHugo(c *Config, force bool) { + os.RemoveAll(c.Root + "public") -// ShouldHandle checks if this extension should be handled by this plugin -func (h Hugo) ShouldHandle(r *http.Request) bool { - // Checks if the method is get or post - if r.Method != http.MethodGet && r.Method != http.MethodPost { - return false - } + // Prevent running if watching is enabled + if b, pos := variables.StringInSlice("--watch", c.Args); b && !force { + if len(c.Args) > pos && c.Args[pos+1] != "false" { + return + } - // Check if this request is for FileManager assets - if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + filemanager.AssetsURL) { - return false - } - - // If this request requires a raw file or a download, return the FileManager - query := r.URL.Query() - if val, ok := query["raw"]; ok && val[0] == "true" { - return false - } - - if val, ok := query["download"]; ok && val[0] == "true" { - return false - } - - // Check by file extension - extension := strings.TrimPrefix(filepath.Ext(r.URL.Path), ".") - - for _, ext := range extensions { - if ext == extension { - return true + if len(c.Args) == pos+1 { + return } } - return false + if err := commands.Run(c.Hugo, c.Args, c.Root); err != nil { + log.Panic(err) + } } -// ServeAssets provides the needed assets for the front-end -func (h Hugo) ServeAssets(w http.ResponseWriter, r *http.Request) (int, error) { +// serveAssets provides the needed assets for the front-end +func serveAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { // gets the filename to be used with Assets function - filename := strings.Replace(r.URL.Path, h.Config.BaseURL+AssetsURL, "public", 1) + filename := strings.Replace(r.URL.Path, c.BaseURL+AssetsURL, "public", 1) file, err := Asset(filename) if err != nil { return http.StatusNotFound, nil diff --git a/installer/installer.go b/installer/installer.go index af77bb9b..2dc968dd 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -14,6 +14,7 @@ import ( "regexp" "runtime" + "github.com/hacdias/caddy-hugo/utils/files" "github.com/mitchellh/go-homedir" "github.com/pivotal-golang/archiver/extractor" ) @@ -90,7 +91,7 @@ func GetPath() string { // Copy the file fmt.Print("Moving Hugo executable... ") - err = CopyFile(exetorename, hugo) + err = files.CopyFile(exetorename, hugo) if err != nil { fmt.Println(err) os.Exit(-1) @@ -213,27 +214,3 @@ func checkSHA256() { fmt.Println("checked!") } - -// CopyFile is used to copy a file -func CopyFile(old, new string) error { - // Open the file and create a new one - r, err := os.Open(old) - if err != nil { - return err - } - defer r.Close() - - w, err := os.Create(new) - if err != nil { - return err - } - defer w.Close() - - // Copy the content - _, err = io.Copy(w, r) - if err != nil { - return err - } - - return nil -} diff --git a/post.go b/post.go deleted file mode 100644 index ecdf5744..00000000 --- a/post.go +++ /dev/null @@ -1,200 +0,0 @@ -package hugo - -import ( - "bytes" - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "path/filepath" - "strings" - "time" - - "github.com/robfig/cron" - "github.com/spf13/cast" - "github.com/spf13/hugo/parser" -) - -type info struct { - ContentType string - Schedule bool - Regenerate bool - Content map[string]interface{} -} - -type response struct { - Message string `json:"message"` -} - -// POST handles the POST method on editor page -func (h Hugo) POST(w http.ResponseWriter, r *http.Request, filename string) (int, error) { - var data info - - // Get the JSON information sent using a buffer - rawBuffer := new(bytes.Buffer) - rawBuffer.ReadFrom(r.Body) - err := json.Unmarshal(rawBuffer.Bytes(), &data) - - if err != nil { - return RespondJSON(w, &response{"Error decrypting json."}, http.StatusInternalServerError, err) - } - - // Initializes the file content to write - var file []byte - var code int - - switch data.ContentType { - case "frontmatter-only": - file, code, err = parseFrontMatterOnlyFile(data, filename) - if err != nil { - return RespondJSON(w, &response{err.Error()}, code, err) - } - case "content-only": - // The main content of the file - mainContent := data.Content["content"].(string) - mainContent = strings.TrimSpace(mainContent) - - file = []byte(mainContent) - case "complete": - file, code, err = parseCompleteFile(data, filename, h.Config) - if err != nil { - return RespondJSON(w, &response{err.Error()}, code, err) - } - default: - return RespondJSON(w, &response{"Invalid content type."}, http.StatusBadRequest, nil) - } - - // Write the file - err = ioutil.WriteFile(filename, file, 0666) - - if err != nil { - return RespondJSON(w, &response{err.Error()}, http.StatusInternalServerError, err) - } - - if data.Regenerate { - go RunHugo(h.Config, false) - } - - return RespondJSON(w, nil, http.StatusOK, nil) -} - -func parseFrontMatterOnlyFile(data info, filename string) ([]byte, int, error) { - frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".") - var mark rune - - switch frontmatter { - case "toml": - mark = rune('+') - case "json": - mark = rune('{') - case "yaml": - mark = rune('-') - default: - return []byte{}, http.StatusBadRequest, errors.New("Can't define the frontmatter.") - } - - f, err := parser.InterfaceToFrontMatter(data.Content, mark) - 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) - - if err != nil { - return []byte{}, http.StatusInternalServerError, err - } - - return f, http.StatusOK, nil -} - -func parseCompleteFile(data info, filename string, c *Config) ([]byte, int, error) { - // The main content of the file - mainContent := data.Content["content"].(string) - mainContent = "\n\n" + strings.TrimSpace(mainContent) + "\n" - - // Removes the main content from the rest of the frontmatter - delete(data.Content, "content") - - if _, ok := data.Content["date"]; ok { - data.Content["date"] = data.Content["date"].(string) + ":00" - } - - // Schedule the post - if data.Schedule { - t := cast.ToTime(data.Content["date"]) - - scheduler := cron.New() - scheduler.AddFunc(t.In(time.Now().Location()).Format("05 04 15 02 01 *"), func() { - // Set draft to false - data.Content["draft"] = false - - // Converts the frontmatter in JSON - jsonFrontmatter, err := json.Marshal(data.Content) - - if err != nil { - return - } - - // Indents the json - frontMatterBuffer := new(bytes.Buffer) - json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ") - - // Generates the final file - f := new(bytes.Buffer) - f.Write(frontMatterBuffer.Bytes()) - f.Write([]byte(mainContent)) - file := f.Bytes() - - // Write the file - if err = ioutil.WriteFile(filename, file, 0666); err != nil { - return - } - - go RunHugo(c, false) - }) - scheduler.Start() - } - - // Converts the frontmatter in JSON - jsonFrontmatter, err := json.Marshal(data.Content) - - if err != nil { - return []byte{}, http.StatusInternalServerError, err - } - - // Indents the json - frontMatterBuffer := new(bytes.Buffer) - json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ") - - // Generates the final file - f := new(bytes.Buffer) - f.Write(frontMatterBuffer.Bytes()) - f.Write([]byte(mainContent)) - return f.Bytes(), http.StatusOK, nil -} - -func RespondJSON(w http.ResponseWriter, message interface{}, code int, err error) (int, error) { - if message == nil { - message = map[string]string{} - } - - msg, msgErr := json.Marshal(message) - - if msgErr != nil { - return 500, msgErr - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - w.Write(msg) - return 0, err -} diff --git a/setup.go b/setup.go index 8152cc88..b6f43708 100644 --- a/setup.go +++ b/setup.go @@ -5,15 +5,18 @@ import ( "log" "net/http" "os" - "path/filepath" "strings" "github.com/hacdias/caddy-filemanager" + "github.com/hacdias/caddy-filemanager/config" "github.com/hacdias/caddy-hugo/installer" + "github.com/hacdias/caddy-hugo/utils/commands" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/httpserver" ) +const AssetsURL = "/_hugointernal" + func init() { caddy.RegisterPlugin("hugo", caddy.Plugin{ ServerType: "http", @@ -25,7 +28,7 @@ func init() { // middleware thing. func setup(c *caddy.Controller) error { cnf := httpserver.GetConfig(c) - conf, _ := ParseHugo(c, cnf.Root) + conf, _ := parse(c, cnf.Root) // Checks if there is an Hugo website in the path that is provided. // If not, a new website will be created. @@ -44,7 +47,7 @@ func setup(c *caddy.Controller) error { } if create { - err := Run(conf.Hugo, []string{"new", "site", conf.Root, "--force"}, ".") + err := commands.Run(conf.Hugo, []string{"new", "site", conf.Root, "--force"}, ".") if err != nil { log.Panic(err) } @@ -59,8 +62,8 @@ func setup(c *caddy.Controller) error { Config: conf, FileManager: &filemanager.FileManager{ Next: next, - Configs: []filemanager.Config{ - filemanager.Config{ + Configs: []config.Config{ + config.Config{ HugoEnabled: true, PathScope: conf.Root, Root: http.Dir(conf.Root), @@ -76,35 +79,27 @@ func setup(c *caddy.Controller) error { return nil } -// Config contains the configuration of hugo plugin +// Config is a configuration for managing a particular hugo website. type Config struct { - Args []string // Hugo arguments - Git bool // Is this site a git repository - BaseURL string // Admin URL to listen on - Hugo string // Hugo executable path - Root string // Hugo website path - Public string // Public content path - Styles string // Admin stylesheet + Public string // Public content path + Root string // Hugo files path + Hugo string // Hugo executable location + Styles string // Admin styles path + Args []string // Hugo arguments + BaseURL string // BaseURL of admin interface + FileManager *filemanager.FileManager } -// ParseHugo parses the configuration file -func ParseHugo(c *caddy.Controller, root string) (*Config, error) { +// Parse parses the configuration set by the user so it can be +// used by the middleware +func parse(c *caddy.Controller, root string) (*Config, error) { conf := &Config{ Public: strings.Replace(root, "./", "", -1), BaseURL: "/admin", Root: "./", - Git: false, - Hugo: installer.GetPath(), } - stlsbytes, err := Asset("public/css/styles.css") - - if err != nil { - return conf, err - } - - conf.Styles = string(stlsbytes) - + conf.Hugo = installer.GetPath() for c.Next() { args := c.RemainingArgs() @@ -121,19 +116,17 @@ func ParseHugo(c *caddy.Controller, root string) (*Config, error) { if !c.NextArg() { return conf, c.ArgErr() } - stylesheet, err := ioutil.ReadFile(c.Val()) + tplBytes, err := ioutil.ReadFile(c.Val()) if err != nil { return conf, err } - conf.Styles += string(stylesheet) + conf.Styles = string(tplBytes) case "admin": if !c.NextArg() { - return nil, c.ArgErr() + return conf, c.ArgErr() } conf.BaseURL = c.Val() - // Remove the beginning slash if it exists or not conf.BaseURL = strings.TrimPrefix(conf.BaseURL, "/") - // Add a beginning slash to make a conf.BaseURL = "/" + conf.BaseURL default: key := "--" + c.Val() @@ -148,9 +141,5 @@ func ParseHugo(c *caddy.Controller, root string) (*Config, error) { } } - if _, err := os.Stat(filepath.Join(conf.Root, ".git")); err == nil { - conf.Git = true - } - return conf, nil } diff --git a/utils/commands/commands.go b/utils/commands/commands.go new file mode 100644 index 00000000..2e8626d4 --- /dev/null +++ b/utils/commands/commands.go @@ -0,0 +1,15 @@ +package commands + +import ( + "os" + "os/exec" +) + +// Run executes an external command +func Run(command string, args []string, path string) error { + cmd := exec.Command(command, args...) + cmd.Dir = path + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/utils/files/files.go b/utils/files/files.go new file mode 100644 index 00000000..084259ca --- /dev/null +++ b/utils/files/files.go @@ -0,0 +1,30 @@ +package files + +import ( + "io" + "os" +) + +// CopyFile is used to copy a file +func CopyFile(old, new string) error { + // Open the file and create a new one + r, err := os.Open(old) + if err != nil { + return err + } + defer r.Close() + + w, err := os.Create(new) + if err != nil { + return err + } + defer w.Close() + + // Copy the content + _, err = io.Copy(w, r) + if err != nil { + return err + } + + return nil +} diff --git a/variables.go b/variables.go deleted file mode 100644 index e361b612..00000000 --- a/variables.go +++ /dev/null @@ -1,95 +0,0 @@ -package hugo - -import ( - "errors" - "log" - "reflect" - "strings" - "unicode" -) - -// Defined checks if variable is defined in a struct -func Defined(data interface{}, field string) bool { - t := reflect.Indirect(reflect.ValueOf(data)).Type() - - if t.Kind() != reflect.Struct { - log.Print("Non-struct type not allowed.") - return false - } - - _, b := t.FieldByName(field) - return b -} - -// Dict allows to send more than one variable into a template -func Dict(values ...interface{}) (map[string]interface{}, error) { - if len(values)%2 != 0 { - return nil, errors.New("invalid dict call") - } - dict := make(map[string]interface{}, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].(string) - if !ok { - return nil, errors.New("dict keys must be strings") - } - dict[key] = values[i+1] - } - - return dict, nil -} - -// IsMap checks if some variable is a map -func IsMap(sth interface{}) bool { - return reflect.ValueOf(sth).Kind() == reflect.Map -} - -// IsSlice checks if some variable is a slice -func IsSlice(sth interface{}) bool { - return reflect.ValueOf(sth).Kind() == reflect.Slice -} - -// StringInSlice checks if a slice contains a string -func StringInSlice(a string, list []string) (bool, int) { - for i, b := range list { - if b == a { - return true, i - } - } - return false, 0 -} - -var splitCapitalizeExceptions = map[string]string{ - "youtube": "YouTube", - "github": "GitHub", - "googleplus": "Google Plus", - "linkedin": "LinkedIn", -} - -// SplitCapitalize splits a string by its uppercase letters and capitalize the -// first letter of the string -func SplitCapitalize(name string) string { - if val, ok := splitCapitalizeExceptions[strings.ToLower(name)]; ok { - return val - } - - var words []string - l := 0 - for s := name; s != ""; s = s[l:] { - l = strings.IndexFunc(s[1:], unicode.IsUpper) + 1 - if l <= 0 { - l = len(s) - } - words = append(words, s[:l]) - } - - name = "" - - for _, element := range words { - name += element + " " - } - - name = strings.ToLower(name[:len(name)-1]) - name = strings.ToUpper(string(name[0])) + name[1:] - - return name -}