clean
This commit is contained in:
201
_stuff/LICENSE.md
Normal file
201
_stuff/LICENSE.md
Normal file
@@ -0,0 +1,201 @@
|
||||
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.
|
||||
37
_stuff/README.md
Normal file
37
_stuff/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# hugo - a caddy plugin
|
||||
|
||||
[](https://travis-ci.org/hacdias/caddy-hugo)
|
||||
[](https://forum.caddyserver.com)
|
||||
[](https://caddyserver.com/docs/hugo)
|
||||
[](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
|
||||
161
_stuff/editor.go
Normal file
161
_stuff/editor.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package hugo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/hacdias/caddy-hugo/tools/frontmatter"
|
||||
"github.com/hacdias/caddy-hugo/tools/templates"
|
||||
"github.com/hacdias/caddy-hugo/tools/variables"
|
||||
"github.com/spf13/hugo/parser"
|
||||
)
|
||||
|
||||
type editor struct {
|
||||
Name string
|
||||
Class string
|
||||
IsPost bool
|
||||
Mode string
|
||||
Content string
|
||||
FrontMatter interface{}
|
||||
Config *Config
|
||||
}
|
||||
|
||||
// GET handles the GET method on editor page
|
||||
func 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.
|
||||
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
|
||||
page := new(editor)
|
||||
page.Mode = strings.TrimPrefix(filepath.Ext(filename), ".")
|
||||
page.Name = strings.Replace(filename, c.Path, "", 1)
|
||||
page.Config = c
|
||||
page.IsPost = false
|
||||
|
||||
// Sanitize the extension
|
||||
page.Mode = sanitizeMode(page.Mode)
|
||||
|
||||
var ppage parser.Page
|
||||
|
||||
// Handle the content depending on the file extension
|
||||
switch page.Mode {
|
||||
case "markdown", "asciidoc", "rst":
|
||||
if hasFrontMatterRune(file) {
|
||||
// Starts a new buffer and parses the file using Hugo's functions
|
||||
buffer := bytes.NewBuffer(file)
|
||||
ppage, err = parser.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if strings.Contains(string(ppage.FrontMatter()), "date") {
|
||||
page.IsPost = true
|
||||
}
|
||||
|
||||
// Parses the page content and the frontmatter
|
||||
page.Content = strings.TrimSpace(string(ppage.Content()))
|
||||
page.FrontMatter, page.Name, err = frontmatter.Pretty(ppage.FrontMatter())
|
||||
page.Class = "complete"
|
||||
} else {
|
||||
// The editor will handle only content
|
||||
page.Class = "content-only"
|
||||
page.Content = string(file)
|
||||
}
|
||||
case "json", "toml", "yaml":
|
||||
// Defines the class and declares an error
|
||||
page.Class = "frontmatter-only"
|
||||
|
||||
// Checks if the file already has the frontmatter rune and parses it
|
||||
if hasFrontMatterRune(file) {
|
||||
page.FrontMatter, _, err = frontmatter.Pretty(file)
|
||||
} else {
|
||||
page.FrontMatter, _, err = frontmatter.Pretty(appendFrontMatterRune(file, page.Mode))
|
||||
}
|
||||
|
||||
// Check if there were any errors
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
default:
|
||||
// The editor will handle only content
|
||||
page.Class = "content-only"
|
||||
page.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": variables.SplitCapitalize,
|
||||
"Defined": variables.Defined,
|
||||
}
|
||||
|
||||
tpl, err := templates.Get(r, functions, "editor", "frontmatter")
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, tpl.Execute(w, page)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
66
_stuff/git.go
Normal file
66
_stuff/git.go
Normal file
@@ -0,0 +1,66 @@
|
||||
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)
|
||||
}
|
||||
98
_stuff/hugo.go
Normal file
98
_stuff/hugo.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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
|
||||
}
|
||||
119
_stuff/page.go
Normal file
119
_stuff/page.go
Normal file
@@ -0,0 +1,119 @@
|
||||
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)
|
||||
}
|
||||
28
_stuff/response.go
Normal file
28
_stuff/response.go
Normal file
@@ -0,0 +1,28 @@
|
||||
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
|
||||
}
|
||||
29
_stuff/run.go
Normal file
29
_stuff/run.go
Normal file
@@ -0,0 +1,29 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
145
_stuff/setup.go
Normal file
145
_stuff/setup.go
Normal file
@@ -0,0 +1,145 @@
|
||||
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
|
||||
}
|
||||
0
_stuff/templates.go
Normal file
0
_stuff/templates.go
Normal file
15
_stuff/tools/commands/commands.go
Normal file
15
_stuff/tools/commands/commands.go
Normal file
@@ -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()
|
||||
}
|
||||
173
_stuff/tools/frontmatter/frontmatter.go
Normal file
173
_stuff/tools/frontmatter/frontmatter.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package frontmatter
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-hugo/tools/variables"
|
||||
"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 variables.IsMap(element) {
|
||||
objects = append(objects, handleObjects(element, parent, name))
|
||||
} else if variables.IsSlice(element) {
|
||||
arrays = append(arrays, handleArrays(element, parent, name))
|
||||
} else {
|
||||
if name == "title" && parent.Name == mainName {
|
||||
mainTitle = element.(string)
|
||||
}
|
||||
|
||||
fields = append(fields, handleFlatValues(element, parent, name))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sortByTitle(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
|
||||
}
|
||||
25
_stuff/tools/installer/hashes.go
Normal file
25
_stuff/tools/installer/hashes.go
Normal file
@@ -0,0 +1,25 @@
|
||||
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",
|
||||
}
|
||||
)
|
||||
216
_stuff/tools/installer/installer.go
Normal file
216
_stuff/tools/installer/installer.go
Normal file
@@ -0,0 +1,216 @@
|
||||
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!")
|
||||
}
|
||||
30
_stuff/tools/server/response.go
Normal file
30
_stuff/tools/server/response.go
Normal file
@@ -0,0 +1,30 @@
|
||||
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
|
||||
}
|
||||
26
_stuff/tools/server/url.go
Normal file
26
_stuff/tools/server/url.go
Normal file
@@ -0,0 +1,26 @@
|
||||
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
|
||||
}
|
||||
73
_stuff/tools/templates/templates.go
Normal file
73
_stuff/tools/templates/templates.go
Normal file
@@ -0,0 +1,73 @@
|
||||
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
|
||||
}
|
||||
39
_stuff/tools/templates/templates_test.go
Normal file
39
_stuff/tools/templates/templates_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
10
_stuff/tools/variables/strings.go
Normal file
10
_stuff/tools/variables/strings.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package variables
|
||||
|
||||
func StringInSlice(a string, list []string) (bool, int) {
|
||||
for i, b := range list {
|
||||
if b == a {
|
||||
return true, i
|
||||
}
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
42
_stuff/tools/variables/transform.go
Normal file
42
_stuff/tools/variables/transform.go
Normal file
@@ -0,0 +1,42 @@
|
||||
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
|
||||
}
|
||||
31
_stuff/tools/variables/transform_test.go
Normal file
31
_stuff/tools/variables/transform_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
13
_stuff/tools/variables/types.go
Normal file
13
_stuff/tools/variables/types.go
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
||||
37
_stuff/tools/variables/variables.go
Normal file
37
_stuff/tools/variables/variables.go
Normal file
@@ -0,0 +1,37 @@
|
||||
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
|
||||
}
|
||||
41
_stuff/tools/variables/variables_test.go
Normal file
41
_stuff/tools/variables/variables_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
24
_stuff/utilities.go
Normal file
24
_stuff/utilities.go
Normal file
@@ -0,0 +1,24 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user