make updates

pull/144/head
Henrique Dias 2016-06-25 21:57:10 +01:00
parent 4274a181a5
commit 669cb2c4ae
32 changed files with 997 additions and 211 deletions

View File

@ -325,6 +325,12 @@ header p i {
font-size: 1em !important; font-size: 1em !important;
color: rgba(255, 255, 255, .31); color: rgba(255, 255, 255, .31);
} }
header #logout {
background-color: rgba(0,0,0,0.1);
border-radius: 0;
margin: -0.5em;
padding: .5em;
}
header p i { header p i {
vertical-align: middle; vertical-align: middle;
} }
@ -414,6 +420,7 @@ header form input {
} }
.action.disabled { .action.disabled {
opacity: 0.2; opacity: 0.2;
cursor: not-allowed;
} }
.action i { .action i {
padding: 0.5em; padding: 0.5em;
@ -486,13 +493,30 @@ i.spin {
/* EDITOR */ /* EDITOR */
#editor .block {
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);
display: block;
border-radius: .2em;
padding: .5em;
margin-bottom: 1em;
break-inside: avoid;
background-color: #fff;
}
#editor .frontmatter { #editor .frontmatter {
border: 1px solid #ddd; /* border: 1px solid #ddd; */
background: #fff; /* background: #fff; */
column-count: 3;
column-gap: 1em;
margin-bottom: 1em;
display: flex;
flex-direction: column;
} }
#editor label { #editor label {
display: inline-block; display: block;
width: 19%; width: 19%;
font-size: 2em;
} }
#editor fieldset { #editor fieldset {
margin: 0; margin: 0;
@ -503,3 +527,10 @@ i.spin {
#editor button { #editor button {
display: none; display: none;
} }
#editor textarea[name="content"] {
display: none;
}
#editor h3 {
font-size: .8rem;
}

View File

@ -111,6 +111,9 @@ var handleViewType = function (viewList) {
// Handles the open file button event // Handles the open file button event
var openEvent = function (event) { var openEvent = function (event) {
if (this.classList.contains('disabled')) {
return false;
}
if (selectedItems.length) { if (selectedItems.length) {
window.open(selectedItems[0] + '?raw=true'); window.open(selectedItems[0] + '?raw=true');
return false; return false;
@ -181,6 +184,9 @@ var preventDefault = function (event) {
// Rename file event // Rename file event
var renameEvent = function (event) { var renameEvent = function (event) {
if (this.classList.contains('disabled')) {
return false;
}
if (selectedItems.length) { if (selectedItems.length) {
Array.from(selectedItems).forEach(link => { Array.from(selectedItems).forEach(link => {
let item = document.getElementById(link); let item = document.getElementById(link);
@ -248,6 +254,9 @@ var renameEvent = function (event) {
// Download file event // Download file event
var downloadEvent = function (event) { var downloadEvent = function (event) {
if (this.classList.contains('disabled')) {
return false;
}
if (selectedItems.length) { if (selectedItems.length) {
Array.from(selectedItems).forEach(item => { Array.from(selectedItems).forEach(item => {
window.open(item + "?download=true"); window.open(item + "?download=true");
@ -416,7 +425,6 @@ document.addEventListener("DOMContentLoaded", function (event) {
}, false); }, false);
} }
if (document.getElementById('editor')) { if (document.getElementById('editor')) {
handleEditorPage(); handleEditorPage();
} }
@ -425,6 +433,31 @@ document.addEventListener("DOMContentLoaded", function (event) {
}); });
var handleEditorPage = function () { var handleEditorPage = function () {
let container = document.getElementById('editor');
let kind = container.dataset.kind;
if (kind != 'frontmatter-only') {
let editor = document.getElementById('editor-source');
let mode = editor.dataset.mode;
let textarea = document.querySelector('textarea[name="content"]');
let aceEditor = ace.edit('editor-source');
aceEditor.getSession().setMode("ace/mode/" + mode);
aceEditor.getSession().setValue(textarea.value);
aceEditor.getSession().on('change', function() {
textarea.value = aceEditor.getSession().getValue();
});
aceEditor.setOptions({
wrap: true,
maxLines: Infinity,
theme: "ace/theme/github",
showPrintMargin: false,
fontSize: "1em",
minLines: 20
});
}
return false; return false;
} }

View File

@ -18,6 +18,7 @@
{{ end }} {{ end }}
</head> </head>
<body> <body>
<header> <header>
<div> <div>
{{ $lnk := .PreviousLink }} {{ $lnk := .PreviousLink }}

View File

@ -1,5 +1,5 @@
{{ define "content" }} {{ define "content" }}
<div id="editor" class="container {{ .Class }}"> <div id="editor" class="container" data-kind="{{ .Class }}">
<form method="POST" action="./"> <form method="POST" action="./">
{{ if or (eq .Class "frontmatter-only") (eq .Class "complete") }} {{ if or (eq .Class "frontmatter-only") (eq .Class "complete") }}
<div class="frontmatter"> <div class="frontmatter">
@ -10,7 +10,7 @@
{{ if or (eq .Class "content-only") (eq .Class "complete") }} {{ if or (eq .Class "content-only") (eq .Class "complete") }}
<div class="content"> <div class="content">
<div id="editor" data-mode="{{ .Mode }}"></div> <div id="editor-source" data-mode="{{ .Mode }}"></div>
<textarea name="content">{{ .Content }}</textarea> <textarea name="content">{{ .Content }}</textarea>
</div> </div>
{{ end }} {{ end }}

View File

@ -1,6 +1,6 @@
//go:generate go get github.com/jteeuwen/go-bindata //go:generate go get github.com/jteeuwen/go-bindata
//go:generate go install github.com/jteeuwen/go-bindata/go-bindata //go:generate go install github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -debug -pkg filemanager -prefix "assets" -o binary.go assets/... //go:generate go-bindata -debug -pkg assets -prefix "assets" -o internal/assets/binary.go assets/...
// Package filemanager provides middleware for managing files in a directory // Package filemanager provides middleware for managing files in a directory
// when directory path is requested instead of a specific file. Based on browse // when directory path is requested instead of a specific file. Based on browse
@ -10,31 +10,30 @@ package filemanager
import ( import (
"io" "io"
"log" "log"
"mime"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
a "github.com/hacdias/caddy-filemanager/internal/assets"
"github.com/hacdias/caddy-filemanager/internal/config"
"github.com/hacdias/caddy-filemanager/internal/file"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
) )
// AssetsURL is the url of the assets
const AssetsURL = "/_filemanagerinternal"
// FileManager is an http.Handler that can show a file listing when // FileManager is an http.Handler that can show a file listing when
// directories in the given paths are specified. // directories in the given paths are specified.
type FileManager struct { type FileManager struct {
Next httpserver.Handler Next httpserver.Handler
Configs []Config Configs []config.Config
} }
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met. // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
var ( var (
c *Config c *config.Config
fi *FileInfo fi *file.Info
code int code int
err error err error
assets bool assets bool
@ -43,7 +42,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
for i := range f.Configs { for i := range f.Configs {
if httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) { if httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
c = &f.Configs[i] c = &f.Configs[i]
assets = httpserver.Path(r.URL.Path).Matches(c.BaseURL + AssetsURL) assets = httpserver.Path(r.URL.Path).Matches(c.BaseURL + a.BaseURL)
if r.Method != http.MethodPost && !assets { if r.Method != http.MethodPost && !assets {
fi, code, err = GetFileInfo(r.URL, c) fi, code, err = GetFileInfo(r.URL, c)
@ -62,7 +61,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
case http.MethodGet: case http.MethodGet:
// Read and show directory or file // Read and show directory or file
if assets { if assets {
return ServeAssets(w, r, c) return a.ServeAssets(w, r, c)
} }
if !fi.IsDir { if !fi.IsDir {
@ -113,28 +112,8 @@ func ErrorToHTTPCode(err error) int {
} }
} }
// ServeAssets provides the needed assets for the front-end
func ServeAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
// gets the filename to be used with Assets function
filename := strings.Replace(r.URL.Path, c.BaseURL+AssetsURL, "public", 1)
file, err := Asset(filename)
if err != nil {
return http.StatusNotFound, nil
}
// Get the file extension and its mimetype
extension := filepath.Ext(filename)
mediatype := mime.TypeByExtension(extension)
// Write the header with the Content-Type and write the file
// content to the buffer
w.Header().Set("Content-Type", mediatype)
w.Write(file)
return 200, nil
}
// Upload is used to handle the upload requests to the server // Upload is used to handle the upload requests to the server
func Upload(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { func Upload(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
// Parse the multipart form in the request // Parse the multipart form in the request
err := r.ParseMultipartForm(100000) err := r.ParseMultipartForm(100000)
if err != nil { if err != nil {
@ -178,7 +157,7 @@ func Upload(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
} }
// NewFolder makes a new directory // NewFolder makes a new directory
func NewFolder(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { func NewFolder(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
path := strings.Replace(r.URL.Path, c.BaseURL, c.PathScope, 1) path := strings.Replace(r.URL.Path, c.BaseURL, c.PathScope, 1)
path = filepath.Clean(path) path = filepath.Clean(path)
err := os.MkdirAll(path, 0755) err := os.MkdirAll(path, 0755)

33
internal/assets/assets.go Normal file
View File

@ -0,0 +1,33 @@
package assets
import (
"mime"
"net/http"
"path/filepath"
"strings"
"github.com/hacdias/caddy-filemanager/internal/config"
)
// BaseURL is the url of the assets
const BaseURL = "/_filemanagerinternal"
// ServeAssets provides the needed assets for the front-end
func ServeAssets(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
// gets the filename to be used with Assets function
filename := strings.Replace(r.URL.Path, c.BaseURL+BaseURL, "public", 1)
file, err := Asset(filename)
if err != nil {
return http.StatusNotFound, nil
}
// Get the file extension and its mimetype
extension := filepath.Ext(filename)
mediatype := mime.TypeByExtension(extension)
// Write the header with the Content-Type and write the file
// content to the buffer
w.Header().Set("Content-Type", mediatype)
w.Write(file)
return 200, nil
}

View File

@ -10,7 +10,7 @@
// assets/templates/single.tmpl // assets/templates/single.tmpl
// DO NOT EDIT! // DO NOT EDIT!
package filemanager package assets
import ( import (
"fmt" "fmt"

73
internal/config/config.go Normal file
View File

@ -0,0 +1,73 @@
package config
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/mholt/caddy"
)
// Config is a configuration for browsing in a particualr path.
type Config struct {
PathScope string
Root http.FileSystem
BaseURL string
StyleSheet string // Costum stylesheet
HugoEnabled bool // This must be only used by Hugo plugin
}
// Parse parses the configuration set by the user so it can
// be used by the middleware
func Parse(c *caddy.Controller) ([]Config, error) {
var configs []Config
appendConfig := func(cfg Config) error {
for _, c := range configs {
if c.PathScope == cfg.PathScope {
return fmt.Errorf("duplicate file managing config for %s", c.PathScope)
}
}
configs = append(configs, cfg)
return nil
}
for c.Next() {
var cfg = Config{PathScope: ".", BaseURL: "", HugoEnabled: false}
for c.NextBlock() {
switch c.Val() {
case "show":
if !c.NextArg() {
return configs, c.ArgErr()
}
cfg.PathScope = c.Val()
cfg.PathScope = strings.TrimSuffix(cfg.PathScope, "/")
case "on":
if !c.NextArg() {
return configs, c.ArgErr()
}
cfg.BaseURL = c.Val()
cfg.BaseURL = strings.TrimPrefix(cfg.BaseURL, "/")
cfg.BaseURL = strings.TrimSuffix(cfg.BaseURL, "/")
cfg.BaseURL = "/" + cfg.BaseURL
case "styles":
if !c.NextArg() {
return configs, c.ArgErr()
}
tplBytes, err := ioutil.ReadFile(c.Val())
if err != nil {
return configs, err
}
cfg.StyleSheet = string(tplBytes)
}
}
cfg.Root = http.Dir(cfg.PathScope)
if err := appendConfig(cfg); err != nil {
return configs, err
}
}
return configs, nil
}

View File

@ -1,10 +1,11 @@
package filemanager package editor
import ( import (
"bytes" "bytes"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/hacdias/caddy-filemanager/internal/file"
"github.com/spf13/hugo/parser" "github.com/spf13/hugo/parser"
) )
@ -17,10 +18,10 @@ type Editor struct {
} }
// GetEditor gets the editor based on a FileInfo struct // GetEditor gets the editor based on a FileInfo struct
func (fi FileInfo) GetEditor() (*Editor, error) { func (i *file.Info) GetEditor() (*Editor, error) {
// Create a new editor variable and set the mode // Create a new editor variable and set the mode
editor := new(Editor) editor := new(Editor)
editor.Mode = strings.TrimPrefix(filepath.Ext(fi.Name), ".") editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name), ".")
switch editor.Mode { switch editor.Mode {
case "md", "markdown", "mdown", "mmark": case "md", "markdown", "mdown", "mmark":
@ -41,9 +42,9 @@ func (fi FileInfo) GetEditor() (*Editor, error) {
// Handle the content depending on the file extension // Handle the content depending on the file extension
switch editor.Mode { switch editor.Mode {
case "markdown", "asciidoc", "rst": case "markdown", "asciidoc", "rst":
if editor.hasFrontMatterRune(fi.Raw) { if editor.hasFrontMatterRune(i.Raw) {
// 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(fi.Raw) buffer := bytes.NewBuffer(i.Raw)
page, err = parser.ReadFrom(buffer) page, err = parser.ReadFrom(buffer)
if err != nil { if err != nil {
return editor, err return editor, err
@ -63,10 +64,10 @@ func (fi FileInfo) GetEditor() (*Editor, error) {
editor.Class = "frontmatter-only" editor.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 editor.hasFrontMatterRune(fi.Raw) { if editor.hasFrontMatterRune(i.Raw) {
editor.FrontMatter, _, err = Pretty(fi.Raw) editor.FrontMatter, _, err = Pretty(i.Raw)
} else { } else {
editor.FrontMatter, _, err = Pretty(editor.appendFrontMatterRune(fi.Raw, editor.Mode)) editor.FrontMatter, _, err = Pretty(editor.appendFrontMatterRune(i.Raw, editor.Mode))
} }
// Check if there were any errors // Check if there were any errors
@ -76,7 +77,7 @@ func (fi FileInfo) GetEditor() (*Editor, error) {
default: default:
// The editor will handle only content // The editor will handle only content
editor.Class = "content-only" editor.Class = "content-only"
editor.Content = fi.Content editor.Content = i.Content
} }
return editor, nil return editor, nil

View File

@ -1,4 +1,4 @@
package filemanager package file
import ( import (
"fmt" "fmt"
@ -12,11 +12,15 @@ import (
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/hacdias/caddy-filemanager/internal/config"
"github.com/hacdias/caddy-filemanager/internal/editor"
p "github.com/hacdias/caddy-filemanager/internal/page"
"github.com/hacdias/caddy-filemanager/utils/errors"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
) )
// FileInfo is the information about a particular file or directory // Info is the information about a particular file or directory
type FileInfo struct { type Info struct {
IsDir bool IsDir bool
Name string Name string
Size int64 Size int64
@ -31,33 +35,33 @@ type FileInfo struct {
Type string Type string
} }
// GetFileInfo gets the file information and, in case of error, returns the // GetInfo gets the file information and, in case of error, returns the
// respective HTTP error code // respective HTTP error code
func GetFileInfo(url *url.URL, c *Config) (*FileInfo, int, error) { func GetInfo(url *url.URL, c *config.Config) (*Info, int, error) {
var err error var err error
rootPath := strings.Replace(url.Path, c.BaseURL, "", 1) rootPath := strings.Replace(url.Path, c.BaseURL, "", 1)
rootPath = strings.TrimPrefix(rootPath, "/") rootPath = strings.TrimPrefix(rootPath, "/")
rootPath = "/" + rootPath rootPath = "/" + rootPath
path := c.PathScope + rootPath relpath := c.PathScope + rootPath
path = strings.Replace(path, "\\", "/", -1) relpath = strings.Replace(relpath, "\\", "/", -1)
path = filepath.Clean(path) relpath = filepath.Clean(relpath)
file := &FileInfo{ file := &Info{
URL: url.Path, URL: url.Path,
RootPath: rootPath, RootPath: rootPath,
Path: path, Path: relpath,
} }
f, err := c.Root.Open(rootPath) f, err := c.Root.Open(rootPath)
if err != nil { if err != nil {
return file, ErrorToHTTPCode(err), err return file, errors.ToHTTPCode(err), err
} }
defer f.Close() defer f.Close()
info, err := f.Stat() info, err := f.Stat()
if err != nil { if err != nil {
return file, ErrorToHTTPCode(err), err return file, errors.ToHTTPCode(err), err
} }
file.IsDir = info.IsDir() file.IsDir = info.IsDir()
@ -67,107 +71,107 @@ func GetFileInfo(url *url.URL, c *Config) (*FileInfo, int, error) {
return file, 0, nil return file, 0, nil
} }
// GetExtendedFileInfo is used to get extra parameters for FileInfo struct // GetExtendedInfo is used to get extra parameters for FileInfo struct
func (fi *FileInfo) GetExtendedFileInfo() error { func (i *Info) GetExtendedInfo() error {
err := fi.Read() err := i.Read()
if err != nil { if err != nil {
return err return err
} }
fi.Type = SimplifyMimeType(fi.Mimetype) i.Type = SimplifyMimeType(i.Mimetype)
return nil return nil
} }
// Read is used to read a file and store its content // Read is used to read a file and store its content
func (fi *FileInfo) Read() error { func (i *Info) Read() error {
raw, err := ioutil.ReadFile(fi.Path) raw, err := ioutil.ReadFile(i.Path)
if err != nil { if err != nil {
return err return err
} }
fi.Mimetype = http.DetectContentType(raw) i.Mimetype = http.DetectContentType(raw)
fi.Content = string(raw) i.Content = string(raw)
fi.Raw = raw i.Raw = raw
return nil return nil
} }
// HumanSize returns the size of the file as a human-readable string // HumanSize returns the size of the file as a human-readable string
// in IEC format (i.e. power of 2 or base 1024). // in IEC format (i.e. power of 2 or base 1024).
func (fi FileInfo) HumanSize() string { func (i Info) HumanSize() string {
return humanize.IBytes(uint64(fi.Size)) return humanize.IBytes(uint64(i.Size))
} }
// HumanModTime returns the modified time of the file as a human-readable string. // HumanModTime returns the modified time of the file as a human-readable string.
func (fi FileInfo) HumanModTime(format string) string { func (i Info) HumanModTime(format string) string {
return fi.ModTime.Format(format) return i.ModTime.Format(format)
} }
// Delete handles the delete requests // Delete handles the delete requests
func (fi *FileInfo) Delete() (int, error) { func (i *Info) Delete() (int, error) {
var err error var err error
// If it's a directory remove all the contents inside // If it's a directory remove all the contents inside
if fi.IsDir { if i.IsDir {
err = os.RemoveAll(fi.Path) err = os.RemoveAll(i.Path)
} else { } else {
err = os.Remove(fi.Path) err = os.Remove(i.Path)
} }
if err != nil { if err != nil {
return ErrorToHTTPCode(err), err return errors.ToHTTPCode(err), err
} }
return http.StatusOK, nil return http.StatusOK, nil
} }
// Rename function is used tor rename a file or a directory // Rename function is used tor rename a file or a directory
func (fi *FileInfo) Rename(w http.ResponseWriter, r *http.Request) (int, error) { func (i *Info) Rename(w http.ResponseWriter, r *http.Request) (int, error) {
newname := r.Header.Get("Rename-To") newname := r.Header.Get("Rename-To")
if newname == "" { if newname == "" {
return http.StatusBadRequest, nil return http.StatusBadRequest, nil
} }
newpath := filepath.Clean(newname) newpath := filepath.Clean(newname)
newpath = strings.Replace(fi.Path, fi.Name, newname, 1) newpath = strings.Replace(i.Path, i.Name, newname, 1)
if err := os.Rename(fi.Path, newpath); err != nil { if err := os.Rename(i.Path, newpath); err != nil {
return ErrorToHTTPCode(err), err return errors.ToHTTPCode(err), err
} }
fi.Path = newpath i.Path = newpath
return http.StatusOK, nil return http.StatusOK, nil
} }
// ServeAsHTML is used to serve single file pages // ServeAsHTML is used to serve single file pages
func (fi *FileInfo) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { func (i *Info) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
if fi.IsDir { if i.IsDir {
return fi.serveListing(w, r, c) return i.serveListing(w, r, c)
} }
return fi.serveSingleFile(w, r, c) return i.serveSingleFile(w, r, c)
} }
func (fi *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
err := fi.GetExtendedFileInfo() err := i.GetExtendedInfo()
if err != nil { if err != nil {
return ErrorToHTTPCode(err), err return errors.ToHTTPCode(err), err
} }
if fi.Type == "blob" { if i.Type == "blob" {
return fi.ServeRawFile(w, r, c) return i.ServeRawFile(w, r, c)
} }
page := &Page{ page := &p.Page{
Info: &PageInfo{ Info: &p.Info{
Name: fi.Name, Name: i.Name,
Path: fi.RootPath, Path: i.RootPath,
IsDir: false, IsDir: false,
Data: fi, Data: i,
Config: c, Config: c,
}, },
} }
if CanBeEdited(fi.Name) { if editor.CanBeEdited(i.Name) {
editor, err := fi.GetEditor() editor, err := i.GetEditor()
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
@ -180,16 +184,16 @@ func (fi *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *C
return page.PrintAsHTML(w, "single") return page.PrintAsHTML(w, "single")
} }
func (fi *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
var err error var err error
file, err := c.Root.Open(fi.RootPath) file, err := c.Root.Open(i.RootPath)
if err != nil { if err != nil {
return ErrorToHTTPCode(err), err return errors.ToHTTPCode(err), err
} }
defer file.Close() defer file.Close()
listing, err := fi.loadDirectoryContents(file, c) listing, err := i.loadDirectoryContents(file, c)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
switch { switch {
@ -222,10 +226,10 @@ func (fi *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Conf
listing.ItemsLimitedTo = limit listing.ItemsLimitedTo = limit
} }
page := &Page{ page := &p.Page{
Info: &PageInfo{ Info: &p.Info{
Name: listing.Name, Name: listing.Name,
Path: fi.RootPath, Path: i.RootPath,
IsDir: true, IsDir: true,
Config: c, Config: c,
Data: listing, Data: listing,
@ -235,19 +239,19 @@ func (fi *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Conf
return page.PrintAsHTML(w, "listing") return page.PrintAsHTML(w, "listing")
} }
func (fi FileInfo) loadDirectoryContents(file http.File, c *Config) (*Listing, error) { func (i Info) loadDirectoryContents(file http.File, c *config.Config) (*Listing, error) {
files, err := file.Readdir(-1) files, err := file.Readdir(-1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
listing := directoryListing(files, fi.RootPath) listing := directoryListing(files, i.RootPath)
return &listing, nil return &listing, nil
} }
func directoryListing(files []os.FileInfo, urlPath string) Listing { func directoryListing(files []os.FileInfo, urlPath string) Listing {
var ( var (
fileinfos []FileInfo fileinfos []Info
dirCount, fileCount int dirCount, fileCount int
) )
@ -263,7 +267,7 @@ func directoryListing(files []os.FileInfo, urlPath string) Listing {
url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
fileinfos = append(fileinfos, FileInfo{ fileinfos = append(fileinfos, Info{
IsDir: f.IsDir(), IsDir: f.IsDir(),
Name: f.Name(), Name: f.Name(),
Size: f.Size(), Size: f.Size(),
@ -283,18 +287,18 @@ func directoryListing(files []os.FileInfo, urlPath string) Listing {
} }
// ServeRawFile serves raw files // ServeRawFile serves raw files
func (fi *FileInfo) ServeRawFile(w http.ResponseWriter, r *http.Request, c *Config) (int, error) { func (i *Info) ServeRawFile(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
err := fi.GetExtendedFileInfo() err := i.GetExtendedInfo()
if err != nil { if err != nil {
return ErrorToHTTPCode(err), err return errors.ToHTTPCode(err), err
} }
if fi.Type != "text" { if i.Type != "text" {
fi.Read() i.Read()
} }
w.Header().Set("Content-Type", fi.Mimetype) w.Header().Set("Content-Type", i.Mimetype)
w.Write([]byte(fi.Content)) w.Write([]byte(i.Content))
return 200, nil return 200, nil
} }

View File

@ -1,4 +1,4 @@
package filemanager package file
import ( import (
"net/http" "net/http"
@ -16,7 +16,7 @@ type Listing struct {
// The full path of the request // The full path of the request
Path string Path string
// The items (files and folders) in the path // The items (files and folders) in the path
Items []FileInfo Items []Info
// The number of directories in the listing // The number of directories in the listing
NumDirs int NumDirs int
// The number of files (items that aren't directories) in the listing // The number of files (items that aren't directories) in the listing

View File

@ -0,0 +1,173 @@
package frontmatter
import (
"log"
"reflect"
"sort"
"strings"
"github.com/hacdias/caddy-filemanager/utils/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
}

View File

@ -1,4 +1,4 @@
package filemanager package page
import ( import (
"bytes" "bytes"
@ -8,37 +8,36 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/hacdias/caddy-filemanager/variables" "github.com/hacdias/caddy-filemanager/internal/assets"
"github.com/hacdias/caddy-filemanager/internal/config"
"github.com/hacdias/caddy-filemanager/utils/variables"
) )
// Page contains the informations and functions needed to show the page // Page contains the informations and functions needed to show the page
type Page struct { type Page struct {
Info *PageInfo *Info
} }
// AssetFunc is an Assets function // Info contains the information of a page
type AssetFunc func(name string) ([]byte, error) type Info struct {
// PageInfo contains the information of a page
type PageInfo struct {
Name string Name string
Path string Path string
IsDir bool IsDir bool
Config *Config Config *config.Config
Data interface{} Data interface{}
} }
// BreadcrumbMap returns p.Path where every element is a map // BreadcrumbMap returns p.Path where every element is a map
// of URLs and path segment names. // of URLs and path segment names.
func (p PageInfo) BreadcrumbMap() map[string]string { func (i Info) BreadcrumbMap() map[string]string {
result := map[string]string{} result := map[string]string{}
if len(p.Path) == 0 { if len(i.Path) == 0 {
return result return result
} }
// skip trailing slash // skip trailing slash
lpath := p.Path lpath := i.Path
if lpath[len(lpath)-1] == '/' { if lpath[len(lpath)-1] == '/' {
lpath = lpath[:len(lpath)-1] lpath = lpath[:len(lpath)-1]
} }
@ -57,17 +56,17 @@ func (p PageInfo) BreadcrumbMap() map[string]string {
} }
// PreviousLink returns the path of the previous folder // PreviousLink returns the path of the previous folder
func (p PageInfo) PreviousLink() string { func (i Info) PreviousLink() string {
parts := strings.Split(strings.TrimSuffix(p.Path, "/"), "/") parts := strings.Split(strings.TrimSuffix(i.Path, "/"), "/")
if len(parts) <= 1 { if len(parts) <= 1 {
return "" return ""
} }
if parts[len(parts)-2] == "" { if parts[len(parts)-2] == "" {
if p.Config.BaseURL == "" { if i.Config.BaseURL == "" {
return "/" return "/"
} }
return p.Config.BaseURL return i.Config.BaseURL
} }
return parts[len(parts)-2] return parts[len(parts)-2]
@ -88,7 +87,7 @@ func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
// For each template, add it to the the tpl variable // For each template, add it to the the tpl variable
for i, t := range templates { for i, t := range templates {
// Get the template from the assets // Get the template from the assets
page, err := Asset("templates/" + t + ".tmpl") page, err := assets.Asset("templates/" + t + ".tmpl")
// Check if there is some error. If so, the template doesn't exist // Check if there is some error. If so, the template doesn't exist
if err != nil { if err != nil {

View File

@ -1,11 +1,7 @@
package filemanager package filemanager
import ( import (
"fmt" "github.com/hacdias/caddy-filemanager/internal/config"
"io/ioutil"
"net/http"
"strings"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
) )
@ -19,7 +15,7 @@ func init() {
// setup configures a new FileManager middleware instance. // setup configures a new FileManager middleware instance.
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
configs, err := parseConfiguration(c) configs, err := config.Parse(c)
if err != nil { if err != nil {
return err return err
} }
@ -30,66 +26,3 @@ func setup(c *caddy.Controller) error {
return nil return nil
} }
// Config is a configuration for browsing in a particualr path.
type Config struct {
PathScope string
Root http.FileSystem
BaseURL string
StyleSheet string // Costum stylesheet
HugoEnabled bool // This must be only used by Hugo plugin
}
// parseConfiguration parses the configuration set by the user so it can
// be used by the middleware
func parseConfiguration(c *caddy.Controller) ([]Config, error) {
var configs []Config
appendConfig := func(cfg Config) error {
for _, c := range configs {
if c.PathScope == cfg.PathScope {
return fmt.Errorf("duplicate file managing config for %s", c.PathScope)
}
}
configs = append(configs, cfg)
return nil
}
for c.Next() {
var cfg = Config{PathScope: ".", BaseURL: "", HugoEnabled: false}
for c.NextBlock() {
switch c.Val() {
case "show":
if !c.NextArg() {
return configs, c.ArgErr()
}
cfg.PathScope = c.Val()
cfg.PathScope = strings.TrimSuffix(cfg.PathScope, "/")
case "on":
if !c.NextArg() {
return configs, c.ArgErr()
}
cfg.BaseURL = c.Val()
cfg.BaseURL = strings.TrimPrefix(cfg.BaseURL, "/")
cfg.BaseURL = strings.TrimSuffix(cfg.BaseURL, "/")
cfg.BaseURL = "/" + cfg.BaseURL
case "styles":
if !c.NextArg() {
return configs, c.ArgErr()
}
tplBytes, err := ioutil.ReadFile(c.Val())
if err != nil {
return configs, err
}
cfg.StyleSheet = string(tplBytes)
}
}
cfg.Root = http.Dir(cfg.PathScope)
if err := appendConfig(cfg); err != nil {
return configs, err
}
}
return configs, nil
}

View 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()
}

View File

@ -0,0 +1,30 @@
package files
import (
"io"
"os"
)
// CopyFile is used to copy a file
func CopyFile(old, new string) error {
// Open the file and create a new one
r, err := os.Open(old)
if err != nil {
return err
}
defer r.Close()
w, err := os.Create(new)
if err != nil {
return err
}
defer w.Close()
// Copy the content
_, err = io.Copy(w, r)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,11 @@
package files
import "testing"
func TestCopyFile(t *testing.T) {
err := CopyFile("test_data/file_to_copy.txt", "test_data/copied_file.txt")
if err != nil {
t.Error("Can't copy the file.")
}
}

View File

@ -0,0 +1,9 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin faucibus augue vel ex auctor molestie. Sed id lorem quis velit eleifend vestibulum. Etiam tincidunt nulla et metus dictum imperdiet. Suspendisse ac auctor risus. Donec tempus eros quis erat interdum fringilla. Duis ac tristique lorem, quis vulputate enim. In ut est elementum, dignissim tellus quis, ultrices diam. Aenean in efficitur velit, ut imperdiet felis. Suspendisse aliquet accumsan ligula, a iaculis sem luctus vel. Integer sagittis, orci viverra lacinia efficitur, eros magna sodales sapien, in tristique ante arcu ut nunc. Maecenas odio dolor, semper quis vehicula ut, malesuada sollicitudin massa. Integer dolor ligula, hendrerit convallis quam at, luctus dignissim nibh. Fusce interdum congue justo, at imperdiet lorem maximus in.
Curabitur dignissim id turpis ut eleifend. Pellentesque vehicula et purus et consectetur. Nulla facilisis vehicula nunc in imperdiet. Nam eget porta libero, vel dapibus leo. Maecenas iaculis, arcu quis vehicula sollicitudin, massa tortor mattis tortor, vitae venenatis erat neque et dui. Mauris eleifend vel urna ac interdum. Sed convallis, arcu et scelerisque suscipit, nulla urna laoreet mauris, quis aliquam dolor elit id mauris. Pellentesque ac pulvinar nibh. Duis vestibulum ligula orci, et mattis turpis facilisis mattis. Vivamus feugiat neque sed aliquet aliquet. Sed consequat augue sagittis eros imperdiet, ac lacinia erat tempor. Cras gravida mi a orci euismod feugiat. Mauris condimentum turpis id quam vulputate, et maximus dolor hendrerit.
Ut nisi urna, sollicitudin ac facilisis eu, rhoncus sed urna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla eu maximus mi, sed commodo diam. Nunc scelerisque tincidunt ipsum eget mattis. Fusce id finibus nisi, sit amet tempus tortor. Fusce accumsan lectus at dignissim volutpat. Nullam nisl nulla, rhoncus id arcu vitae, eleifend posuere libero. Aliquam dictum bibendum bibendum. Integer non mattis neque. Phasellus fermentum et ex vel vehicula.
Proin eget ligula lorem. Ut vehicula quis augue sed aliquet. Nulla viverra turpis nulla. Morbi et ipsum odio. Nulla in nisi suscipit justo blandit tristique ac vitae ipsum. Donec viverra dictum arcu. Duis est elit, ultrices a massa non, lobortis maximus eros. Nulla porttitor rhoncus lectus in finibus. Duis convallis sapien porta, fringilla orci nec, ultricies nisl. Morbi pulvinar quam in nulla rutrum euismod.
Donec et accumsan lectus, consectetur posuere nunc. Quisque et quam hendrerit, vestibulum quam eget, luctus enim. In hac habitasse platea dictumst. Nunc et est scelerisque, bibendum neque quis, ornare elit. Aliquam erat volutpat. Nulla maximus nibh vitae ex interdum, a efficitur lacus eleifend. Nam vestibulum blandit aliquet. In hac habitasse platea dictumst. Maecenas interdum quam et lorem posuere interdum. Mauris id feugiat augue. Etiam vestibulum pharetra cursus.

View File

@ -1,4 +1,4 @@
package filemanager package frontmatter
import ( import (
"log" "log"
@ -6,7 +6,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/hacdias/caddy-filemanager/variables" "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"
) )

View File

@ -0,0 +1,38 @@
package hugo
import (
"log"
"os"
"github.com/hacdias/caddy-hugo/config"
"github.com/hacdias/caddy-hugo/tools/commands"
)
// Run is used to run the static website generator
func Run(c *config.Config.Config, force bool) {
os.RemoveAll(c.Path + "public")
// Prevent running if watching is enabled
if b, pos := stringInSlice("--watch", c.Args); b && !force {
if len(c.Args) > pos && c.Args[pos+1] != "false" {
return
}
if len(c.Args) == pos+1 {
return
}
}
if err := commands.Run(c.Hugo, c.Args, c.Path); err != nil {
log.Panic(err)
}
}
func stringInSlice(a string, list []string) (bool, int) {
for i, b := range list {
if b == a {
return true, i
}
}
return false, 0
}

View 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",
}
)

View 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!")
}

View File

@ -0,0 +1,24 @@
package server
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,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
}

View 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
}

View 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,
)
}
}
}

20
utils/errors/http.go Normal file
View File

@ -0,0 +1,20 @@
package errors
import (
"net/http"
"os"
)
// ToHTTPCode gets the respective HTTP code for an error
func ToHTTPCode(err error) int {
switch {
case os.IsPermission(err):
return http.StatusForbidden
case os.IsNotExist(err):
return http.StatusNotFound
case os.IsExist(err):
return http.StatusGone
default:
return http.StatusInternalServerError
}
}