updates and restructure

This commit is contained in:
Henrique Dias 2016-06-21 16:01:46 +01:00
parent b43d8e2465
commit 72c7abe469
29 changed files with 416 additions and 1390 deletions

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,37 +0,0 @@
# hugo - a caddy plugin
[![Build](https://img.shields.io/travis/hacdias/caddy-hugo.svg?style=flat-square)](https://travis-ci.org/hacdias/caddy-hugo)
[![community](https://img.shields.io/badge/community-forum-ff69b4.svg?style=flat-square)](https://forum.caddyserver.com)
[![Documentation](https://img.shields.io/badge/caddy-doc-F06292.svg?style=flat-square)](https://caddyserver.com/docs/hugo)
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/hacdias/caddy-hugo)
[Hugo](http://gohugo.io/) is an easy to use and fast command line static website generator, while [Caddy](http://caddyserver.com) is a lightweight, fast, general-purpose, cross-platform HTTP/2 web server with automatic HTTPS. This extension is able to bring a web interface to Caddy to manage Hugo generated websites. This plugin provides you an web interface to manage your websites made with Hugo.
**If you're not developer go to the [documentation](https://caddyserver.com/docs/hugo)**.
## Build from source
Requirements:
+ [Go 1.6 or higher][1]
+ [caddydev][2]
+ [go-bindata][3]
+ [Node.js w/ npm][4] (optional)
Instructions:
1. ```go get github.com/hacdias/caddy-hugo```
2. ```cd $GOPATH/github.com/hacdias/caddy-hugo```
1. If you want to modify the CSS/JS:
2. Change the third comment to ```//go:generate go-bindata -debug -pkg assets -o assets/assets.go templates/ assets/css/ assets/js/ assets/fonts/```
3. ```npm install```
4. ```grunt watch```
3. ```go generate```
4. ```cd $YOUR_WEBSITE_PATH```
5. ```caddydev --source $GOPATH/github.com/hacdias/caddy-hugo hugo```
6. Go to ```http://domain:port```
[1]: https://golang.org/dl/
[2]: https://github.com/caddyserver/caddydev
[3]: https://github.com/jteeuwen/go-bindata
[4]: https://nodejs.org

View File

@ -1,66 +0,0 @@
package hugo
import (
"bytes"
"encoding/json"
"net/http"
"os/exec"
"strings"
)
// HandleGit handles the POST method on GIT page which is only an API.
func HandleGit(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
response := &Response{
Code: http.StatusOK,
Err: nil,
Content: "OK",
}
// Check if git is installed on the computer
if _, err := exec.LookPath("git"); err != nil {
response.Code = http.StatusNotImplemented
response.Content = "Git is not installed on your computer."
return response.Send(w)
}
// Get the JSON information sent using a buffer
buff := new(bytes.Buffer)
buff.ReadFrom(r.Body)
// Creates the raw file "map" using the JSON
var info map[string]interface{}
json.Unmarshal(buff.Bytes(), &info)
// Check if command was sent
if _, ok := info["command"]; !ok {
response.Code = http.StatusBadRequest
response.Content = "Command not specified."
return response.Send(w)
}
command := info["command"].(string)
args := strings.Split(command, " ")
if len(args) > 0 && args[0] == "git" {
args = append(args[:0], args[1:]...)
}
if len(args) == 0 {
response.Code = http.StatusBadRequest
response.Content = "Command not specified."
return response.Send(w)
}
cmd := exec.Command("git", args...)
cmd.Dir = c.Path
output, err := cmd.CombinedOutput()
if err != nil {
response.Code = http.StatusInternalServerError
response.Content = err.Error()
return response.Send(w)
}
response.Content = string(output)
return response.Send(w)
}

View File

@ -1,98 +0,0 @@
// Package hugo makes the bridge between the static website generator Hugo
// and the webserver Caddy, also providing an administrative user interface.
package hugo
import (
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/hacdias/caddy-filemanager"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
// Hugo contais the next middleware to be run and the configuration
// of the current one.
type Hugo struct {
FileManager *filemanager.FileManager
Next httpserver.Handler
Config *Config
}
// 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.Admin) {
// 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 h.FileManager.ServeHTTP(w, r)
}
if val, ok := query["download"]; ok && val[0] == "true" {
return h.FileManager.ServeHTTP(w, r)
}
// If the url matches exactly with /{admin}/settings/, redirect
// to the page of the configuration file
if r.URL.Path == h.Config.Admin+"/settings/" {
var frontmatter string
if _, err := os.Stat(h.Config.Path + "config.yaml"); err == nil {
frontmatter = "yaml"
}
if _, err := os.Stat(h.Config.Path + "config.json"); err == nil {
frontmatter = "json"
}
if _, err := os.Stat(h.Config.Path + "config.toml"); err == nil {
frontmatter = "toml"
}
http.Redirect(w, r, h.Config.Admin+"/config."+frontmatter, http.StatusTemporaryRedirect)
return 0, nil
}
filename := strings.Replace(r.URL.Path, c.Admin+"/edit/", "", 1)
filename = c.Path + filename
if strings.HasPrefix(r.URL.Path, h.Config.Admin+"/api/git/") && r.Method == http.MethodPost {
return HandleGit(w, r, h.Config)
}
if h.ShouldHandle(r.URL) {
// return editor
return 0, nil
}
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",
}
// ShouldHandle checks if this extension should be handled by this plugin
func (h Hugo) ShouldHandle(url *url.URL) bool {
extension := strings.TrimPrefix(filepath.Ext(url.Path), ".")
for _, ext := range extensions {
if ext == extension {
return true
}
}
return false
}

View File

@ -1,119 +0,0 @@
package hugo
import (
"encoding/json"
"html/template"
"log"
"net/http"
"strings"
)
// Page contains the informations and functions needed to show the page
type Page struct {
Info *PageInfo
}
// PageInfo contains the information of a page
type PageInfo struct {
Name string
Path string
IsDir bool
Config *Config
Data interface{}
}
// BreadcrumbMap returns p.Path where every element is a map
// of URLs and path segment names.
func (p PageInfo) BreadcrumbMap() map[string]string {
result := map[string]string{}
if len(p.Path) == 0 {
return result
}
// skip trailing slash
lpath := p.Path
if lpath[len(lpath)-1] == '/' {
lpath = lpath[:len(lpath)-1]
}
parts := strings.Split(lpath, "/")
for i, part := range parts {
if i == 0 && part == "" {
// Leading slash (root)
result["/"] = "/"
continue
}
result[strings.Join(parts[:i+1], "/")] = part
}
return result
}
// PreviousLink returns the path of the previous folder
func (p PageInfo) PreviousLink() string {
parts := strings.Split(strings.TrimSuffix(p.Path, "/"), "/")
if len(parts) <= 1 {
return ""
}
if parts[len(parts)-2] == "" {
if p.Config.BaseURL == "" {
return "/"
}
return p.Config.BaseURL
}
return parts[len(parts)-2]
}
// PrintAsHTML formats the page in HTML and executes the template
func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) {
templates = append(templates, "actions", "base")
var tpl *template.Template
// For each template, add it to the the tpl variable
for i, t := range templates {
// Get the template from the assets
page, err := Asset("templates/" + t + ".tmpl")
// Check if there is some error. If so, the template doesn't exist
if err != nil {
log.Print(err)
return http.StatusInternalServerError, err
}
// If it's the first iteration, creates a new template and add the
// functions map
if i == 0 {
tpl, err = template.New(t).Parse(string(page))
} else {
tpl, err = tpl.Parse(string(page))
}
if err != nil {
log.Print(err)
return http.StatusInternalServerError, err
}
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
err := tpl.Execute(w, p.Info)
if err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
// PrintAsJSON prints the current page infromation in JSON
func (p Page) PrintAsJSON(w http.ResponseWriter) (int, error) {
marsh, err := json.Marshal(p.Info.Data)
if err != nil {
return http.StatusInternalServerError, err
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return w.Write(marsh)
}

View File

@ -1,28 +0,0 @@
package hugo
import (
"encoding/json"
"net/http"
)
// Response conta
type Response struct {
Code int
Err error
Content string
}
// Send used to send JSON responses to the web server
func (r *Response) Send(w http.ResponseWriter) (int, error) {
content := map[string]string{"message": r.Content}
msg, msgErr := json.Marshal(content)
if msgErr != nil {
return 500, msgErr
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(r.Code)
w.Write(msg)
return 0, r.Err
}

View File

@ -1,29 +0,0 @@
package hugo
import (
"log"
"os"
"github.com/hacdias/caddy-hugo/tools/commands"
"github.com/hacdias/caddy-hugo/tools/variables"
)
// Run is used to run the static website generator
func Run(c *Config, force bool) {
os.RemoveAll(c.Path + "public")
// 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
}
if len(c.Args) == pos+1 {
return
}
}
if err := commands.Run(c.Hugo, c.Args, c.Path); err != nil {
log.Panic(err)
}
}

View File

@ -1,145 +0,0 @@
package hugo
import (
"log"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/hacdias/caddy-filemanager"
"github.com/hacdias/caddy-hugo/tools/commands"
"github.com/hacdias/caddy-hugo/tools/installer"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func init() {
caddy.RegisterPlugin("hugo", caddy.Plugin{
ServerType: "http",
Action: setup,
})
}
// Setup is the init function of Caddy plugins and it configures the whole
// middleware thing.
func setup(c *caddy.Controller) error {
cnf := httpserver.GetConfig(c.Key)
conf, _ := ParseHugo(c, cnf.Root)
// Checks if there is an Hugo website in the path that is provided.
// If not, a new website will be created.
create := true
if _, err := os.Stat(conf.Path + "config.yaml"); err == nil {
create = false
}
if _, err := os.Stat(conf.Path + "config.json"); err == nil {
create = false
}
if _, err := os.Stat(conf.Path + "config.toml"); err == nil {
create = false
}
if create {
err := commands.Run(conf.Hugo, []string{"new", "site", conf.Path, "--force"}, ".")
if err != nil {
log.Panic(err)
}
}
// Generates the Hugo website for the first time the plugin is activated.
go Run(conf, true)
mid := func(next httpserver.Handler) httpserver.Handler {
return &Hugo{
Next: next,
FileManager: &filemanager.FileManager{
Next: next,
Configs: []filemanager.Config{
filemanager.Config{
PathScope: conf.Path,
Root: http.Dir(conf.Path),
BaseURL: conf.Admin,
},
},
},
Config: conf,
}
}
cnf.AddMiddleware(mid)
return nil
}
// Config is the add-on configuration set on Caddyfile
type Config struct {
Public string // Public content path
Path string // Hugo files path
Styles string // Admin styles path
Args []string // Hugo arguments
Hugo string // Hugo executable path
Admin string // Hugo admin URL
Git bool // Is this site a git repository
}
// ParseHugo parses the configuration file
func ParseHugo(c *caddy.Controller, root string) (*Config, error) {
conf := &Config{
Public: strings.Replace(root, "./", "", -1),
Admin: "/admin",
Path: "./",
Git: false,
}
conf.Hugo = installer.GetPath()
for c.Next() {
args := c.RemainingArgs()
switch len(args) {
case 1:
conf.Path = args[0]
conf.Path = strings.TrimSuffix(conf.Path, "/")
conf.Path += "/"
}
for c.NextBlock() {
switch c.Val() {
case "styles":
if !c.NextArg() {
return nil, c.ArgErr()
}
conf.Styles = c.Val()
// Remove the beginning slash if it exists or not
conf.Styles = strings.TrimPrefix(conf.Styles, "/")
// Add a beginning slash to make a
conf.Styles = "/" + conf.Styles
case "admin":
if !c.NextArg() {
return nil, c.ArgErr()
}
conf.Admin = c.Val()
conf.Admin = strings.TrimPrefix(conf.Admin, "/")
conf.Admin = "/" + conf.Admin
default:
key := "--" + c.Val()
value := "true"
if c.NextArg() {
value = c.Val()
}
conf.Args = append(conf.Args, key+"="+value)
}
}
}
if _, err := os.Stat(filepath.Join(conf.Path, ".git")); err == nil {
conf.Git = true
}
return conf, nil
}

View File

View File

@ -1,15 +0,0 @@
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()
}

View File

@ -1,25 +0,0 @@
package installer
var (
sha256Hash = map[string]string{
"hugo_0.16_darwin-arm32.tgz": "683d5d4b4e0ac03a183ca5eb9019981ba696569445c7d6d1efc7e6706bd273a5",
"hugo_0.16_dragonfly-64bit.tgz": "63a3ee9a36d4d2166c77b96bb8bf39b2239affe118e44a83b3d0a44374a8921d",
"hugo_0.16_freebsd-32bit.tgz": "ea3f84900feeeb9d89573dea49a4349753116e70de561eeec4858f7ffc74f8f9",
"hugo_0.16_freebsd-64bit.tgz": "8d9320bb660090a77a4f922ca30b1582593bc6d87c3fd8bd6f5ecbe49cf1d2f2",
"hugo_0.16_freebsd-arm32.tgz": "b4c21296e01ea68709ac50d7eb1d314b738f1c8408ff2be223d06ae76604dbea",
"hugo_0.16_linux-32bit.tgz": "aed82d156f01a4562c39bd1af41aa81699009140da965e0369c370ba874725c9",
"hugo_0.16_linux-64bit.tgz": "13e299dc45bea4fad5bdf8c2640305a5926e2acd02c3aa03b7864403e513920e",
"hugo_0.16_linux-arm32.tgz": "bc836def127d93e2457da9994f9c09b0100523e46d61074cd724ef092b11714f",
"hugo_0.16_linux-arm64.tgz": "d04486918f43f89f1e0359eebedd8a05d96f7ca40f93e7fd8d7c3f2dac115a8d",
"hugo_0.16_netbsd-32bit.tgz": "cb578eebec5b6364b0afd5bb208d94317acab0a3e033b81f04b1511af0669b63",
"hugo_0.16_netbsd-64bit.tgz": "d3c766d9800d7fdd268ffd2f28b7af451f13a4de63901bfdae2ee5c96528b8cc",
"hugo_0.16_netbsd-arm32.tgz": "51162b2637e71b786582af715a44b778f62bdc62a9a354ccc4a7c8384afe194c",
"hugo_0.16_openbsd-32bit.tgz": "2d1e112a7346850897ea77da868c0d987ef90efb7f49c917659437a5a67f89f8",
"hugo_0.16_openbsd-64bit.tgz": "7b33ff2565df5a6253c3e4308813d947e34af04c633fb4e01cac83751066e16e",
"hugo_0.16_osx-32bit.tgz": "6155dda548bbd1e26c26a4a00472e4c0e55fad9fcd46991ce90987385bd5fd0a",
"hugo_0.16_osx-64bit.tgz": "b0cba8f6996946ef34a664184d6461567d79fc2a3e793145d34379902eda0ad9",
"hugo_0.16_solaris-64bit.tgz": "af9557403af5e16eb7faf965c04540417a70699efbbbc4e0a7ae4c4703ad1ae8",
"hugo_0.16_windows-32bit.zip": "1c72d06843fe02cb62348660d87a523c885ed684a683271fc8762e7234c4210b",
"hugo_0.16_windows-64bit.zip": "a3fda0bd30592e4eb3bdde85c8a8ce23a7433073536466d16fd0e97bf7794067",
}
)

View File

@ -1,216 +0,0 @@
package installer
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"github.com/hacdias/caddy-hugo/tools/files"
"github.com/mitchellh/go-homedir"
"github.com/pivotal-golang/archiver/extractor"
)
const (
version = "0.16"
baseurl = "https://github.com/spf13/hugo/releases/download/v" + version + "/"
)
var caddy, bin, temp, hugo, tempfile, zipname, exename string
// GetPath retrives the Hugo path for the user or install it if it's not found
func GetPath() string {
initializeVariables()
var err error
// Check if Hugo is already on $PATH
if hugo, err = exec.LookPath("hugo"); err == nil {
if checkVersion() {
return hugo
}
}
// Check if Hugo is on $HOME/.caddy/bin
if _, err = os.Stat(hugo); err == nil {
if checkVersion() {
return hugo
}
}
fmt.Println("Unable to find Hugo on your computer.")
// Create the neccessary folders
os.MkdirAll(caddy, 0774)
os.Mkdir(bin, 0774)
if temp, err = ioutil.TempDir("", "caddy-hugo"); err != nil {
fmt.Println(err)
os.Exit(-1)
}
downloadHugo()
checkSHA256()
fmt.Print("Unzipping... ")
// Unzip or Ungzip the file
switch runtime.GOOS {
case "darwin", "windows":
zp := extractor.NewZip()
err = zp.Extract(tempfile, temp)
default:
gz := extractor.NewTgz()
err = gz.Extract(tempfile, temp)
}
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
fmt.Println("done.")
var exetorename string
err = filepath.Walk(temp, func(path string, f os.FileInfo, err error) error {
if f.Name() == exename {
exetorename = path
}
return nil
})
// Copy the file
fmt.Print("Moving Hugo executable... ")
err = files.CopyFile(exetorename, hugo)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
err = os.Chmod(hugo, 0755)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
fmt.Println("done.")
fmt.Println("Hugo installed at " + hugo)
defer os.RemoveAll(temp)
return hugo
}
func initializeVariables() {
var arch string
switch runtime.GOARCH {
case "amd64":
arch = "64bit"
case "386":
arch = "32bit"
case "arm":
arch = "arm32"
default:
arch = runtime.GOARCH
}
var ops = runtime.GOOS
if runtime.GOOS == "darwin" && runtime.GOARCH != "arm" {
ops = "osx"
}
exename = "hugo"
zipname = "hugo_" + version + "_" + ops + "-" + arch
homedir, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
caddy = filepath.Join(homedir, ".caddy")
bin = filepath.Join(caddy, "bin")
hugo = filepath.Join(bin, "hugo")
switch runtime.GOOS {
case "windows":
zipname += ".zip"
exename += ".exe"
hugo += ".exe"
default:
zipname += ".tgz"
}
}
func checkVersion() bool {
out, _ := exec.Command("hugo", "version").Output()
r := regexp.MustCompile(`v\d\.\d{2}`)
v := r.FindStringSubmatch(string(out))[0]
v = v[1:len(v)]
return (v == version)
}
func downloadHugo() {
tempfile = filepath.Join(temp, zipname)
fmt.Print("Downloading Hugo from GitHub releases... ")
// Create the file
out, err := os.Create(tempfile)
out.Chmod(0774)
if err != nil {
defer os.RemoveAll(temp)
fmt.Println(err)
os.Exit(-1)
}
defer out.Close()
// Get the data
resp, err := http.Get(baseurl + zipname)
if err != nil {
fmt.Println("An error ocurred while downloading. If this error persists, try downloading Hugo from \"https://github.com/spf13/hugo/releases/\" and put the executable in " + bin + " and rename it to 'hugo' or 'hugo.exe' if you're on Windows.")
fmt.Println(err)
os.Exit(-1)
}
defer resp.Body.Close()
// Writer the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
fmt.Println("downloaded.")
}
func checkSHA256() {
fmt.Print("Checking SHA256...")
hasher := sha256.New()
f, err := os.Open(tempfile)
if err != nil {
log.Fatal(err)
}
defer f.Close()
if _, err := io.Copy(hasher, f); err != nil {
log.Fatal(err)
}
if hex.EncodeToString(hasher.Sum(nil)) != sha256Hash[zipname] {
fmt.Println("can't verify SHA256.")
os.Exit(-1)
}
fmt.Println("checked!")
}

View File

@ -1,30 +0,0 @@
package server
import (
"encoding/json"
"net/http"
)
type Response struct {
Code int
Err error
Content interface{}
}
// RespondJSON used to send JSON responses to the web server
func RespondJSON(w http.ResponseWriter, r *Response) (int, error) {
if r.Content == nil {
r.Content = map[string]string{}
}
msg, msgErr := json.Marshal(r.Content)
if msgErr != nil {
return 500, msgErr
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(r.Code)
w.Write(msg)
return 0, r.Err
}

View File

@ -1,26 +0,0 @@
package server
import (
"net/http"
"strings"
)
// ParseURLComponents parses the components of an URL creating an array
func ParseURLComponents(r *http.Request) []string {
//The URL that the user queried.
path := r.URL.Path
path = strings.TrimSpace(path)
//Cut off the leading and trailing forward slashes, if they exist.
//This cuts off the leading forward slash.
if strings.HasPrefix(path, "/") {
path = path[1:]
}
//This cuts off the trailing forward slash.
if strings.HasSuffix(path, "/") {
cutOffLastCharLen := len(path) - 1
path = path[:cutOffLastCharLen]
}
//We need to isolate the individual components of the path.
components := strings.Split(path, "/")
return components
}

View File

@ -1,73 +0,0 @@
package templates
import (
"log"
"net/http"
"strings"
"text/template"
"github.com/hacdias/caddy-hugo/routes/assets"
)
// CanBeEdited checks if the extension of a file is supported by the editor
func CanBeEdited(filename string) bool {
extensions := [...]string{
"md", "markdown", "mdown", "mmark",
"asciidoc", "adoc", "ad",
"rst",
".json", ".toml", ".yaml",
".css", ".sass", ".scss",
".js",
".html",
".txt",
}
for _, extension := range extensions {
if strings.HasSuffix(filename, extension) {
return true
}
}
return false
}
// Get is used to get a ready to use template based on the url and on
// other sent templates
func Get(r *http.Request, functions template.FuncMap, templates ...string) (*template.Template, error) {
// If this is a pjax request, use the minimal template to send only
// the main content
if r.Header.Get("X-PJAX") == "true" {
templates = append(templates, "base_minimal")
} else {
templates = append(templates, "base_full")
}
var tpl *template.Template
// For each template, add it to the the tpl variable
for i, t := range templates {
// Get the template from the assets
page, err := assets.Asset("templates/" + t + ".tmpl")
// Check if there is some error. If so, the template doesn't exist
if err != nil {
log.Print(err)
return new(template.Template), err
}
// If it's the first iteration, creates a new template and add the
// functions map
if i == 0 {
tpl, err = template.New(t).Funcs(functions).Parse(string(page))
} else {
tpl, err = tpl.Parse(string(page))
}
if err != nil {
log.Print(err)
return new(template.Template), err
}
}
return tpl, nil
}

View File

@ -1,39 +0,0 @@
package templates
import "testing"
type canBeEdited struct {
file string
result bool
}
var canBeEditedPairs = []canBeEdited{
{"file.markdown", true},
{"file.md", true},
{"file.json", true},
{"file.toml", true},
{"file.yaml", true},
{"file.css", true},
{"file.sass", true},
{"file.scss", true},
{"file.js", true},
{"file.html", true},
{"file.git", false},
{"file.log", false},
{"file.sh", false},
{"file.png", false},
{"file.jpg", false},
}
func TestCanBeEdited(t *testing.T) {
for _, pair := range canBeEditedPairs {
v := CanBeEdited(pair.file)
if v != pair.result {
t.Error(
"For", pair.file,
"expected", pair.result,
"got", v,
)
}
}
}

View File

@ -1,10 +0,0 @@
package variables
func StringInSlice(a string, list []string) (bool, int) {
for i, b := range list {
if b == a {
return true, i
}
}
return false, 0
}

View File

@ -1,42 +0,0 @@
package variables
import (
"strings"
"unicode"
)
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
}

View File

@ -1,31 +0,0 @@
package variables
import "testing"
type testSplitCapitalize struct {
name string
result string
}
var testSplitCapitalizeCases = []testSplitCapitalize{
{"loremIpsum", "Lorem ipsum"},
{"LoremIpsum", "Lorem ipsum"},
{"loremipsum", "Loremipsum"},
{"YouTube", "YouTube"},
{"GitHub", "GitHub"},
{"GooglePlus", "Google Plus"},
{"Facebook", "Facebook"},
}
func TestSplitCapitalize(t *testing.T) {
for _, pair := range testSplitCapitalizeCases {
v := SplitCapitalize(pair.name)
if v != pair.result {
t.Error(
"For", pair.name,
"expected", pair.result,
"got", v,
)
}
}
}

View File

@ -1,13 +0,0 @@
package variables
import "reflect"
// 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
}

View File

@ -1,37 +0,0 @@
package variables
import (
"errors"
"log"
"reflect"
)
// 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
}

View File

@ -1,41 +0,0 @@
package variables
import "testing"
type testDefinedData struct {
f1 string
f2 bool
f3 int
f4 func()
}
type testDefined struct {
data interface{}
field string
result bool
}
var testDefinedCases = []testDefined{
{testDefinedData{}, "f1", true},
{testDefinedData{}, "f2", true},
{testDefinedData{}, "f3", true},
{testDefinedData{}, "f4", true},
{testDefinedData{}, "f5", false},
{[]string{}, "", false},
{map[string]int{"oi": 4}, "", false},
{"asa", "", false},
{"int", "", false},
}
func TestDefined(t *testing.T) {
for _, pair := range testDefinedCases {
v := Defined(pair.data, pair.field)
if v != pair.result {
t.Error(
"For", pair.data,
"expected", pair.result,
"got", v,
)
}
}
}

View File

@ -1,24 +0,0 @@
package hugo
import (
"encoding/json"
"net/http"
)
// RespondJSON used to send JSON responses to the web server
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
}

View File

@ -0,0 +1,58 @@
{{ define "content" }}
<div class="editor container {{ .Class }}">
{{ if eq .Class "complete" }}
<h1><textarea id="site-title">{{ .Name }}</textarea></h1>
{{ end }}
<form method="POST" action="">
<main>
{{ if not (eq .Class "complete") }}
<h1 id="site-title">{{ .Name }}</h1>
{{ end }}
{{ if eq .Class "frontmatter-only" }}
<div class="frontmatter blocks">
{{ template "frontmatter" .FrontMatter }}
<p class="actions">
<button class="add">Add field</button>
</p>
</div>
{{ else if eq .Class "content-only" }}
<div class="content">
<div id="editor-source" data-mode="{{ .Mode }}"></div>
<textarea name="content">{{ .Content }}</textarea>
</div>
{{ else }}
<div class="frontmatter">
<div class="blocks">
{{ template "frontmatter" .FrontMatter }}
</div>
<p class="actions">
<button class="add">Add field</button>
</p>
</div>
<div class="content">
{{ if eq .Mode "markdown" }}
<nav>
<a id="see-source" class="active"><i class="fa fa-code"></i> Source</a>
<a id="see-preview"><i class="fa fa-eye"></i> Preview</a>
</nav>
{{ end}}
<div id="editor-source" data-mode="{{ .Mode }}"></div>
<textarea name="content">{{ .Content }}</textarea>
<div id="editor-preview"></div>
</div>
{{ end }}
<p class="toolbar">
<input type="submit" data-type="{{ .Class }}" data-regenerate="false" data-schedule="false" data-message="{{ if eq .Class "frontmatter-only" }}The fields were put on their way.{{ else if eq .Class "content-only" }}Every byte was saved.{{ else }}Post saved with pomp and circumstance.{{ end }}" value="Save">
<span class="right">
{{ if and (eq .Class "complete") ( .IsPost ) }}<input type="submit" data-type="{{ .Class }}" data-schedule="true" data-regenerate="false" data-message="Post scheduled." value="Schedule"> {{ end }}
<input type="submit" data-type="{{ .Class }}" data-regenerate="true" data-schedule="false" data-message="{{ if eq .Class "frontmatter-only" }}Saved and regenerated.{{ else if eq .Class "content-only" }}Done. What do you want more?{{ else }}Post published. Go and share it!{{ end }}" class="default" value="Publish">
</span>
</p>
</main>
</form>
</div>
{{ end }}

View File

@ -0,0 +1,44 @@
{{ define "frontmatter" }}
{{ range $key, $value := . }}
{{ if or (eq $value.Type "object") (eq $value.Type "array") }}
<fieldset id="{{ $value.Name }}" data-type="{{ $value.Type }}">
<h3>{{ SplitCapitalize $value.Title }}</h3>
<span class="actions">
<button class="add">&#43;</button>
<button class="delete">&#8722;</button>
</span>
{{ template "frontmatter" $value.Content }}
</fieldset>
{{ else }}
{{ if not (eq $value.Parent.Type "array") }}
<div class="block" id="block-{{ $value.Name }}" data-content="{{ $value.Name }}">
<label for="{{ $value.Name }}">{{ SplitCapitalize $value.Title }}</label>
<span class="actions">
<button class="delete">&#8722;</button>
</span>
{{ end }}
{{ if eq $value.Parent.Type "array" }}
<div id="{{ $value.Name }}-{{ $key }}" data-type="array-item">
{{ end }}
{{ if eq $value.HTMLType "textarea" }}
<textarea class="scroll" name="{{ $value.Name }}:{{ $value.Type }}" id="{{ $value.Name }}" data-parent-type="{{ $value.Parent.Type }}">{{ $value.Content }}</textarea>
{{ else if eq $value.HTMLType "datetime" }}
<input name="{{ $value.Name }}:{{ $value.Type }}" id="{{ $value.Name }}" value="{{ $value.Content.Format "2006-01-02T15:04" }}" type="datetime-local" data-parent-type="{{ $value.Parent.Type }}"></input>
{{ else }}
<input name="{{ $value.Name }}:{{ $value.Type }}" id="{{ $value.Name }}" value="{{ $value.Content }}" type="{{ $value.HTMLType }}" data-parent-type="{{ $value.Parent.Type }}"></input>
{{ end }}
{{ if not (eq $value.Parent.Type "array") }}</div>{{ end }}
{{ if eq $value.Parent.Type "array" }}
<span class="actions"><button class="delete">&#8722;</button></span></div>
{{ end }}
{{ end }}
{{ end }}
{{ end }}

260
binary.go Normal file
View File

@ -0,0 +1,260 @@
// Code generated by go-bindata.
// sources:
// assets/templates/editor.tmpl
// assets/templates/frontmatter.tmpl
// DO NOT EDIT!
package hugo
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _templatesEditorTmpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xc4\x56\x4d\x6f\xdc\x38\x0c\xbd\x07\xc8\x7f\xe0\xea\x94\x3d\x8c\x8d\xbd\x7b\xbc\x58\xec\x6e\x8b\x1e\xd2\x0e\x30\x01\x7a\xd6\x58\x9c\x58\x88\x6d\xb9\x12\x3d\xd3\x41\x90\xff\x5e\xca\xf2\x97\x32\xcd\x07\x82\xa0\xf5\xc9\x96\xc8\xc7\x47\xf2\x51\xf2\xfd\x3d\x28\xdc\xeb\x06\x41\x14\xa6\x21\x6c\x48\xc0\xc3\xc3\xe5\x45\xa6\xf4\x01\x8a\x4a\x3a\xb7\x16\xa8\x34\x19\x0b\x7e\x5f\xb2\xa5\x05\x76\x4a\xfe\xf5\x7b\x6c\x2a\xf2\xcb\x0b\xf0\x2b\x7a\x0f\xf8\x6d\x5c\x67\xb0\xba\xad\x90\x30\xa0\x01\x64\xe5\x5f\x79\x46\xf8\x9d\xa4\x45\x09\x5a\xad\x85\xd3\x84\x2b\xd2\x54\xa1\xc8\x3d\xe0\x67\x59\x23\x1b\x67\xe9\x68\x95\x67\x29\x3b\x0d\xe8\xd8\xa8\x11\x69\x6f\x6c\x0d\x35\x52\x69\x18\x65\xf3\x65\x7b\x23\x40\x16\xa4\x4d\xb3\x16\x81\x0c\xdb\xd4\x4c\x74\x78\x1f\xc9\x35\x86\xe0\xea\x67\x0c\xff\x1c\x80\xc3\xc3\x44\x9f\xa7\x37\x70\x1a\x80\x47\x5e\x71\xac\x45\x98\xbd\xe5\xb2\xd5\x92\x08\xed\xca\x34\xd5\x49\x2c\xa2\x2d\x8b\xbc\xb0\x83\x5d\x65\x8a\x3b\x27\xf2\x99\x15\xc3\x12\x32\x5f\x49\x18\x41\x0a\x48\x3e\xf8\xaf\xeb\xe0\x18\x25\xd2\x8e\xd0\xa1\x3a\x11\x1e\x6f\xef\x3a\x22\xd3\x4c\x36\x4a\x89\xfc\x1f\xa5\x60\xaf\xb1\x52\x59\x1a\x76\x17\x1e\x59\xda\x4e\x5f\x59\xca\xc4\xa3\x2a\x54\x0e\xcf\x04\xd0\xab\xe9\xd9\x9c\x47\xc5\x2d\xc3\xf8\x6d\x5f\xff\x20\xba\x95\x33\x9d\x2d\x58\x45\x4a\x92\x5c\xd5\x46\xe1\x5a\xf8\x6e\x5c\xf3\x5b\x2f\xbe\x98\x0a\xfb\x4f\x1a\x6b\xb8\x61\x8b\x10\xbd\x66\xc3\x47\xac\xb2\xe7\x93\x7a\xb1\x59\x67\xe4\x07\x93\xf7\xe9\xe1\xc0\xe9\x97\x76\xf5\x95\x9d\x9a\x95\xde\x37\x43\xd4\xd2\xde\x29\x73\x6c\x44\x9c\x41\x23\x0f\x31\xc3\x61\xfa\x11\xa7\xde\x2e\x12\x3a\xf0\xb4\x65\x7a\x2a\xb3\x84\xbd\x5c\x15\x0c\xef\x1b\xad\x73\xd8\xf6\x1e\x59\x2a\x9f\x82\x6c\x2d\x1e\x34\x1e\xcf\x41\xf0\x34\x62\x6c\x82\x49\x0c\x92\xa5\x31\xcf\x30\xd9\x51\x22\xbf\x4d\x98\xe7\xb1\xe7\x2c\xa3\x20\xe7\x0a\x7e\x74\x38\xcd\xe2\x21\x63\xaa\x9d\x8c\xc5\xab\x9b\xb6\x23\xa0\x53\xcb\xec\x5c\xb7\xab\x35\x0d\xd9\x85\xa5\xe8\xd0\x0f\x1b\x16\x6f\x91\xef\x03\x16\xb4\x2f\x33\x8f\xcb\xb0\xee\x8a\x12\x55\x57\xcd\xab\x43\x95\xd0\x39\x79\x1b\xa0\x5e\x3e\x24\x6f\x4a\x0c\xb2\x75\x70\x44\x8b\xe0\xc9\xb1\xb2\xa9\x44\x6d\xe1\x28\x4f\xc9\xeb\xce\x9d\xff\x0f\x68\x4f\xb0\x3b\xf1\xd0\x1d\xa5\x03\x27\x0f\xa8\x92\x79\xba\x37\xc6\x51\x58\x84\xa3\xa6\x12\x5a\xbe\x15\x40\x72\xdd\x0a\x6d\x8b\xae\x76\x24\x9b\x02\x93\xa9\x96\x02\x0e\xb2\xea\x38\x85\xad\xf4\x52\x5d\x54\xcf\xb5\x72\x1a\x3b\xab\x6f\x4b\x8a\x07\x33\xa4\xec\x81\x9f\xb8\x83\xae\x20\xf9\xe4\x7a\x36\xfe\x3e\x7a\x43\x33\xe6\xa2\x93\xed\xf0\x85\x0e\x4d\xad\x08\xf9\x0f\xae\x2a\xe1\x56\x8d\x09\x0e\x6b\x22\x8f\xaf\xdf\x77\x92\xcb\x82\xe3\x99\x5a\xde\x20\x96\x6d\xdf\x41\x5f\xde\x39\x88\x7a\xa5\x42\xfe\x33\x0d\x26\xf0\xb5\x94\x04\xca\xc0\xc9\x74\x2c\x14\x9e\xc5\xda\x58\xfc\xfb\x91\x50\xda\x6e\x57\x69\xc7\x74\x13\xf8\x68\xfa\x70\xae\xe4\x59\x05\x4d\x7f\x2c\x24\x32\xa8\x80\xff\xab\x64\x57\xd1\x24\x99\x4d\x70\x8e\x54\x93\x7a\xd9\x2c\x06\x78\x3c\x8d\xb3\x74\xfa\x83\xc9\x52\xff\xc3\xc3\xaf\xe3\x78\x2f\xda\xf1\x23\x00\x00\xff\xff\x32\x91\x30\x45\xbe\x09\x00\x00")
func templatesEditorTmplBytes() ([]byte, error) {
return bindataRead(
_templatesEditorTmpl,
"templates/editor.tmpl",
)
}
func templatesEditorTmpl() (*asset, error) {
bytes, err := templatesEditorTmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/editor.tmpl", size: 2494, mode: os.FileMode(438), modTime: time.Unix(1466520293, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesFrontmatterTmpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x54\xdd\x6e\xd3\x4c\x10\xbd\xaf\xd4\x77\x18\xed\x57\x7d\x02\x09\xc7\x69\x5a\x7e\x14\x1c\xdf\x54\x42\x5c\x00\x42\x22\x2f\xb0\xb1\xc7\xb0\x74\xb3\x6b\xd6\x93\x8a\x50\xf5\xdd\x19\xaf\xbd\xa9\xb1\x9d\xca\xad\x10\x77\x59\x9f\xd9\xd9\x73\xce\xcc\xc9\xed\x2d\xe4\x58\x28\x83\x20\x0a\x67\x0d\x6d\x25\x11\x3a\x01\x77\x77\xa7\x27\x8c\x39\x69\xbe\x22\x9c\x5d\xe3\xfe\x05\x9c\xdd\x48\xbd\x43\x58\xae\x60\xe6\xe1\xd3\x13\x00\x2e\x51\x05\x58\x07\xcf\xf0\x47\x5b\x30\x5b\xef\x4b\xee\x66\x37\xdf\x31\x23\xf1\x7c\x88\x48\xe7\xe4\x9e\x81\xba\x07\x40\x52\x28\xd4\x79\x85\x04\x2a\x5f\x09\xee\xd7\xd6\x7e\x92\x5b\xe4\x12\x01\xb9\x24\x19\x11\xdf\xec\xa2\xbe\x13\xa3\x69\xdd\x82\x9b\x7c\xbb\x48\x19\xfc\x52\x6a\x45\x57\xb2\x54\x24\xb5\xfa\x85\x87\x62\x45\xba\xae\x4e\x62\x2e\x6b\x2f\x54\xa5\x34\x90\x69\x59\x55\x2b\x21\x33\x52\xd6\x54\xa1\x19\xa3\x9b\x1d\x91\xbd\xc7\xf3\x5c\xa4\xff\xff\x77\x79\xf1\x36\x89\x1b\xe4\x58\x65\x8e\x1a\x09\xeb\xe2\x37\xaf\x17\x8b\x7e\x79\x12\xd7\xaf\xb6\x07\xa6\x4b\xb8\x2d\xb5\xa4\xbe\xf5\x2d\xeb\x2b\xfe\x84\x86\x82\x4d\x71\xf0\x29\x6d\x7d\x47\x5d\x61\x6f\x0e\xc6\x52\xd7\xee\xcf\xd2\x71\x83\x71\xd7\x73\x75\x13\x48\x6f\xb4\xcd\xae\x85\xb7\xdf\xff\x8c\x8e\x0d\x21\x6b\x18\x8d\x4d\x29\x28\xd4\x72\x83\x1a\x0a\xeb\x46\x8b\xa6\x8c\xc8\x77\x78\xca\x94\x26\x7a\x1f\xdc\x33\x79\xcf\xbc\x07\x7d\xeb\xda\x36\xba\xa7\xde\x34\x8e\x49\x7f\x65\xfd\xf5\x48\xf1\xa8\x45\x3a\xe1\xe9\xf7\xeb\x8f\x1f\x9a\x77\x09\x7f\x12\xf3\x90\x87\xa7\xc3\x87\xa0\xb7\xca\x9c\xd5\x5a\x80\x61\x02\x23\x84\x96\xc3\xb0\x3c\x98\xb0\xd2\x8b\x1e\x04\xad\xeb\x45\x3b\xc2\xc1\x7e\x26\x71\xe0\xf6\xc7\x6e\x1e\x11\xc6\xaf\x21\xa9\x2d\x1e\x84\x29\x53\xee\xe8\x2f\xc8\xf0\xc7\x2e\xd2\x32\x9c\xbd\xb3\x8e\xb3\x05\x62\x31\x9f\xbf\x8a\xe6\xe7\xd1\x7c\xb1\x3e\x7f\xb9\x9c\x5f\x0a\x7f\xad\x51\x1c\x58\x45\x9c\x00\xa9\x1f\xe7\x49\x12\x7b\x09\x83\x60\xfe\x03\x6d\x1d\x05\xf7\xd8\xc1\xeb\xc7\x0e\xb7\x2f\x64\xb0\xa8\xd3\xfe\x60\x92\x98\x63\x92\x3e\x3d\x64\xa3\xa9\x9f\x98\xf6\x36\xe7\x0d\x85\x81\x8e\xce\x61\xfc\xe7\xef\x00\x00\x00\xff\xff\x30\xc2\x3e\x0c\x0f\x07\x00\x00")
func templatesFrontmatterTmplBytes() ([]byte, error) {
return bindataRead(
_templatesFrontmatterTmpl,
"templates/frontmatter.tmpl",
)
}
func templatesFrontmatterTmpl() (*asset, error) {
bytes, err := templatesFrontmatterTmplBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/frontmatter.tmpl", size: 1807, mode: os.FileMode(438), modTime: time.Unix(1466520317, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// 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.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"templates/editor.tmpl": templatesEditorTmpl,
"templates/frontmatter.tmpl": templatesFrontmatterTmpl,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"templates": &bintree{nil, map[string]*bintree{
"editor.tmpl": &bintree{templatesEditorTmpl, map[string]*bintree{}},
"frontmatter.tmpl": &bintree{templatesFrontmatterTmpl, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -2,17 +2,14 @@ package hugo
import ( import (
"bytes" "bytes"
"errors" "html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template"
"github.com/hacdias/caddy-hugo/tools/frontmatter" "github.com/hacdias/caddy-filemanager"
"github.com/hacdias/caddy-hugo/tools/templates"
"github.com/hacdias/caddy-hugo/tools/variables"
"github.com/spf13/hugo/parser" "github.com/spf13/hugo/parser"
) )
@ -23,17 +20,10 @@ type editor struct {
Mode string Mode string
Content string Content string
FrontMatter interface{} FrontMatter interface{}
Config *Config
} }
// GET handles the GET method on editor page // GET handles the GET method on editor page
func GET(w http.ResponseWriter, r *http.Request, c *Config, filename string) (int, error) { func (h Hugo) GET(w http.ResponseWriter, r *http.Request, c *Config, filename string) (int, error) {
// Check if the file format is supported. If not, send a "Not Acceptable"
// header and an error
if !templates.CanBeEdited(filename) {
return http.StatusNotAcceptable, errors.New("File format not supported.")
}
// Check if the file exists. // Check if the file exists.
if _, err := os.Stat(filename); os.IsNotExist(err) { if _, err := os.Stat(filename); os.IsNotExist(err) {
return http.StatusNotFound, err return http.StatusNotFound, err
@ -51,51 +41,56 @@ func GET(w http.ResponseWriter, r *http.Request, c *Config, filename string) (in
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
page := &filemanager.Page{
Info: &filemanager.PageInfo{
IsDir: false,
Config: &h.FileManager.Configs[0],
Name: strings.Replace(filename, c.Root, "", 1),
},
}
// Create a new editor variable and set the extension // Create a new editor variable and set the extension
page := new(editor) data := new(editor)
page.Mode = strings.TrimPrefix(filepath.Ext(filename), ".") data.Mode = strings.TrimPrefix(filepath.Ext(filename), ".")
page.Name = strings.Replace(filename, c.Path, "", 1) data.Name = strings.Replace(filename, c.Root, "", 1)
page.Config = c data.IsPost = false
page.IsPost = false data.Mode = sanitizeMode(data.Mode)
// Sanitize the extension var parserPage parser.Page
page.Mode = sanitizeMode(page.Mode)
var ppage parser.Page
// Handle the content depending on the file extension // Handle the content depending on the file extension
switch page.Mode { switch data.Mode {
case "markdown", "asciidoc", "rst": case "markdown", "asciidoc", "rst":
if hasFrontMatterRune(file) { if hasFrontMatterRune(file) {
// Starts a new buffer and parses the file using Hugo's functions // Starts a new buffer and parses the file using Hugo's functions
buffer := bytes.NewBuffer(file) buffer := bytes.NewBuffer(file)
ppage, err = parser.ReadFrom(buffer) parserPage, err = parser.ReadFrom(buffer)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
if strings.Contains(string(ppage.FrontMatter()), "date") { if strings.Contains(string(parserPage.FrontMatter()), "date") {
page.IsPost = true data.IsPost = true
} }
// Parses the page content and the frontmatter // Parses the page content and the frontmatter
page.Content = strings.TrimSpace(string(ppage.Content())) data.Content = strings.TrimSpace(string(parserPage.Content()))
page.FrontMatter, page.Name, err = frontmatter.Pretty(ppage.FrontMatter()) data.FrontMatter, data.Name, err = Pretty(parserPage.FrontMatter())
page.Class = "complete" data.Class = "complete"
} else { } else {
// The editor will handle only content // The editor will handle only content
page.Class = "content-only" data.Class = "content-only"
page.Content = string(file) data.Content = string(file)
} }
case "json", "toml", "yaml": case "json", "toml", "yaml":
// Defines the class and declares an error // Defines the class and declares an error
page.Class = "frontmatter-only" data.Class = "frontmatter-only"
// Checks if the file already has the frontmatter rune and parses it // Checks if the file already has the frontmatter rune and parses it
if hasFrontMatterRune(file) { if hasFrontMatterRune(file) {
page.FrontMatter, _, err = frontmatter.Pretty(file) data.FrontMatter, _, err = Pretty(file)
} else { } else {
page.FrontMatter, _, err = frontmatter.Pretty(appendFrontMatterRune(file, page.Mode)) data.FrontMatter, _, err = Pretty(appendFrontMatterRune(file, data.Mode))
} }
// Check if there were any errors // Check if there were any errors
@ -104,24 +99,35 @@ func GET(w http.ResponseWriter, r *http.Request, c *Config, filename string) (in
} }
default: default:
// The editor will handle only content // The editor will handle only content
page.Class = "content-only" data.Class = "content-only"
page.Content = string(file) data.Content = string(file)
} }
// Create the functions map, then the template, check for erros and // Create the functions map, then the template, check for erros and
// execute the template if there aren't errors // execute the template if there aren't errors
functions := template.FuncMap{ functions := template.FuncMap{
"SplitCapitalize": variables.SplitCapitalize, "SplitCapitalize": SplitCapitalize,
"Defined": variables.Defined, "Defined": Defined,
} }
tpl, err := templates.Get(r, functions, "editor", "frontmatter") var code int
page.Info.Data = data
code, err = page.AddTemplate("base", filemanager.Asset, functions)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return code, err
} }
return http.StatusOK, tpl.Execute(w, page) templates := []string{"listing", "actions"}
for _, t := range templates {
code, err = page.AddTemplate(t, Asset, nil)
if err != nil {
return code, err
}
}
return page.PrintAsHTML(w)
} }
func hasFrontMatterRune(file []byte) bool { func hasFrontMatterRune(file []byte) bool {

View File

@ -1,4 +1,4 @@
package frontmatter package hugo
import ( import (
"log" "log"
@ -6,7 +6,6 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/hacdias/caddy-hugo/tools/variables"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/spf13/hugo/parser" "github.com/spf13/hugo/parser"
) )
@ -64,9 +63,9 @@ func rawToPretty(config interface{}, parent *frontmatter) interface{} {
} }
for name, element := range cnf { for name, element := range cnf {
if variables.IsMap(element) { if IsMap(element) {
objects = append(objects, handleObjects(element, parent, name)) objects = append(objects, handleObjects(element, parent, name))
} else if variables.IsSlice(element) { } else if IsSlice(element) {
arrays = append(arrays, handleArrays(element, parent, name)) arrays = append(arrays, handleArrays(element, parent, name))
} else { } else {
if name == "title" && parent.Name == mainName { if name == "title" && parent.Name == mainName {

View File

@ -1,3 +1,7 @@
//go:generate go get github.com/jteeuwen/go-bindata
//go:generate go install github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -pkg hugo -prefix "assets" -o binary.go assets/...
// Package hugo makes the bridge between the static website generator Hugo // Package hugo makes the bridge between the static website generator Hugo
// and the webserver Caddy, also providing an administrative user interface. // and the webserver Caddy, also providing an administrative user interface.
package hugo package hugo