major changes

This commit is contained in:
ston1th 2018-02-07 21:41:54 +01:00
commit 2b56832843
83 changed files with 7760 additions and 1244 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
gowiki
tmp
run.sh

24
Godeps/Godeps.json generated
View file

@ -1,12 +1,12 @@
{
"ImportPath": "git.giftfish.de/ston1th/gowiki",
"GoVersion": "go1.7",
"GodepVersion": "v74",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Deps": [
{
"ImportPath": "git.giftfish.de/ston1th/godrop",
"Comment": "v1.0",
"Rev": "8666e4461c9612e1432f05af6e0abfd716adca36"
"Comment": "v1.1",
"Rev": "108583814ef55f34ac8e939cc47699dc7e9befb8"
},
{
"ImportPath": "github.com/blevesearch/bleve",
@ -183,8 +183,8 @@
},
{
"ImportPath": "github.com/gorilla/mux",
"Comment": "v1.1",
"Rev": "0eeaf8392f5b04950925b8a69fe70f110fa7cbfc"
"Comment": "v1.3.0",
"Rev": "392c28fe23e1c45ddba891b0320b3b5df220beea"
},
{
"ImportPath": "github.com/gorilla/securecookie",
@ -193,12 +193,13 @@
},
{
"ImportPath": "github.com/gorilla/sessions",
"Rev": "f7261893ca3ea922c30eabe742c036d2c1de6e0a"
"Comment": "v1.1",
"Rev": "ca9ada44574153444b00d3fd9c8559e4cc95f896"
},
{
"ImportPath": "github.com/russross/blackfriday",
"Comment": "v1.4-31-g2004188",
"Rev": "2004188462c3946efefbdcd91aa83e49b4175bfb"
"Comment": "v1.4",
"Rev": "0b647d0506a698cca42caca173e55559b12a69f2"
},
{
"ImportPath": "github.com/shurcooL/sanitized_anchor_name",
@ -208,6 +209,11 @@
"ImportPath": "github.com/steveyen/gtreap",
"Rev": "0abe01ef9be25c4aedc174758ec2d917314d6d70"
},
{
"ImportPath": "github.com/urfave/cli",
"Comment": "v1.19.1",
"Rev": "0bdeddeeb0f650497d603c4ad7b20cfe685682f6"
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd"

View file

@ -1,6 +1,8 @@
CC=go
BUILD=build -v
LDFLAGS=--ldflags "-s -w"
VERSION=$(shell cat VERSION)
GCFLAGS=-gcflags '-e'
LDFLAGS=-ldflags '-X main.version=$(VERSION) -s -w'
PROGRAM=gowiki
all: $(PROGRAM)

1
VERSION Normal file
View file

@ -0,0 +1 @@
v2.0

49
cache.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"sync"
"time"
)
type CachedArticle struct {
Article *Article
CacheTime time.Time
}
type IndexCache struct {
sync.RWMutex
Cache map[string]*CachedArticle
}
func NewIndexCache() *IndexCache {
return &IndexCache{Cache: make(map[string]*CachedArticle)}
}
func (ic IndexCache) Add(title string, a *Article) {
now := time.Now()
ic.Lock()
defer ic.Unlock()
c, ok := ic.Cache[title]
if ok && now.After(c.CacheTime) {
c.Article = a
c.CacheTime = now
ic.Cache[title] = c
return
}
ic.Cache[title] = &CachedArticle{a, now}
}
func (ic IndexCache) Get(title string) *Article {
ic.RLock()
defer ic.RUnlock()
if a, ok := ic.Cache[title]; ok {
return a.Article
}
return nil
}
func (ic *IndexCache) Delete(title string) {
ic.Lock()
delete(ic.Cache, title)
ic.Unlock()
}

193
cmd.go Normal file
View file

@ -0,0 +1,193 @@
// Copyright (C) 2017 Marius Schellenberger
package main
import (
"errors"
"git.giftfish.de/ston1th/godrop"
"github.com/urfave/cli"
logger "log"
"net"
"os"
"os/signal"
"syscall"
"time"
)
const (
blevePath = "bleve"
defDataDir = "/var/lib/gowiki"
defListen = "127.0.0.1:8090"
defLogFile = "gowiki.log"
defRunUser = "wiki"
defRunGroup = "wiki"
)
func server(conf serverConfig) (err error) {
dropCfg := godrop.Config{
User: conf.User,
Group: conf.Group,
Foreground: conf.Foreground,
}
err = godrop.Drop(dropCfg, func() (net.Listener, error) {
return net.Listen("tcp", conf.ListenAddr)
})
if err != nil {
return
}
if err = os.Chdir(conf.DataDir); err != nil {
return
}
newLogger(conf.LogFile)
l, err := godrop.GetListener()
if err != nil {
return
}
srv := NewHTTPServer(l, conf.Version, conf.SecureCookie)
err = srv.Start()
if err != nil {
return
}
log.Println("gowiki started")
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Println("caught " + sig.String())
go func() {
time.Sleep(time.Second * 20)
log.Fatal(errors.New("stop timed out: killing"))
}()
err = srv.Stop()
if err != nil {
log.Println(err)
}
log.Println("gowiki stopped")
return
}
var (
verbose bool
conf serverConfig
)
func run(app *cli.App) {
app.Commands = []cli.Command{
{
Name: "server",
Usage: "start gowiki server",
Flags: defaultFlags(serverFlags()),
Action: func(c *cli.Context) error {
conf.Version = app.Version
if err := server(conf); err != nil {
logger.Fatal("server: ", err)
}
return nil
},
},
{
Name: "dump",
Usage: "create a json dump of all articles in the data directory (dump is written to stdout) gowiki needs to be stopped",
Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error {
if err := os.Chdir(conf.DataDir); err != nil {
logger.Fatal("dump: ", err)
}
if err := dump(blevePath, verbose); err != nil {
logger.Fatal("dump: ", err)
}
return nil
},
},
{
Name: "restore",
Usage: "restore a json dump to the data directory (dump is read from stdin) gowiki needs to be stopped",
Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error {
if err := os.Chdir(conf.DataDir); err != nil {
logger.Fatal("restore: ", err)
}
if err := restore(blevePath, verbose); err != nil {
logger.Fatal("restore: ", err)
}
return nil
},
},
}
app.Run(os.Args)
}
type serverConfig struct {
DataDir string
User string
Group string
ListenAddr string
LogFile string
Version string
Foreground bool
SecureCookie bool
}
func serverFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "user, u",
Value: defRunUser,
Usage: "drop privileges to user",
Destination: &conf.User,
},
cli.StringFlag{
Name: "group, g",
Value: defRunGroup,
Usage: "drop privileges to group",
Destination: &conf.Group,
},
cli.StringFlag{
Name: "listen, l",
Value: defListen,
Usage: "listening <address>:<port>",
Destination: &conf.ListenAddr,
},
cli.StringFlag{
Name: "log",
Value: defLogFile,
Usage: "log file (leave empty for stdout, this only works in combination with -f)",
Destination: &conf.LogFile,
},
cli.BoolFlag{
Name: "foreground, f",
Usage: "do not fork into the background",
Destination: &conf.Foreground,
},
cli.BoolFlag{
Name: "secure, s",
Usage: "enable secure cookie",
Destination: &conf.SecureCookie,
},
}
}
func dumpRestoreFlags() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: "verbose, v",
Usage: "enable verbose mode",
Destination: &verbose,
},
}
}
func defaultFlags(f []cli.Flag) []cli.Flag {
return append([]cli.Flag{
cli.StringFlag{
Name: "data, d",
Value: defDataDir,
Usage: "data directory",
Destination: &conf.DataDir,
},
}, f...)
}

View file

@ -3,63 +3,16 @@
package main
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"html/template"
"io"
"net/http"
"time"
)
const (
keyLen = 32
authLen = keyLen * 2
cookieName = "gosession"
)
var store *sessions.CookieStore
func cookieStore() {
options := &sessions.Options{
Path: "/",
MaxAge: 3600 * 12,
Secure: secureCookie,
HttpOnly: true,
}
authKey := genKey(authLen)
encKey := genKey(keyLen)
store = sessions.NewCookieStore(authKey, encKey)
store.Options = options
go func() {
for {
time.Sleep(time.Hour * 12)
newAuthKey := genKey(authLen)
newEncKey := genKey(keyLen)
newStore := sessions.NewCookieStore(newAuthKey, newEncKey, authKey, encKey)
newStore.Options = options
store = newStore
authKey = newAuthKey
encKey = newEncKey
}
}()
}
func genKey(length int) (bytes []byte) {
bytes = make([]byte, length)
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
log.Println("genKey:", err)
}
return
}
func newContext(w http.ResponseWriter, r *http.Request) *Context {
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL.Path)
func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) *Context {
h := w.Header()
h.Set("X-Frame-Options", "DENY")
h.Set("X-Content-Type-Options", "nosniff")
@ -68,18 +21,27 @@ func newContext(w http.ResponseWriter, r *http.Request) *Context {
return &Context{
Request: r,
Response: w,
Srv: s,
Time: time.Now(),
}
}
type Context struct {
Request *http.Request
Response http.ResponseWriter
Srv *HTTPServer
T *template.Template
Status int
Err error
Time time.Time
Data webData
}
type webData struct {
Version string
Time int64
Title string
BodyTitle string
@ -91,25 +53,53 @@ type webData struct {
Msg string
Search string
Editor *Editor
Data interface{}
}
func (c *Context) Exec() {
defer c.log()
if c.T == nil {
log.Println("Exec:", errors.New("template is nil"))
c.Status = http.StatusInternalServerError
c.Err = errors.New("template is nil")
return
}
c.newToken()
c.Data.Version = c.Srv.Version
if c.Data.BodyTitle == "" {
c.Data.BodyTitle = c.Data.Title
}
c.Data.Admin = c.GetAdmin()
c.Data.User = c.GetUser()
c.Data.Login = c.LoggedOn()
err := c.T.Execute(c.Response, c.Data)
if err != nil {
log.Println("Exec:", err)
c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e6
c.Err = c.T.Execute(c.Response, c.Data)
if c.Err != nil {
c.Status = http.StatusInternalServerError
}
if c.Status == 0 {
c.Status = http.StatusOK
}
}
func (c *Context) log() {
if c.Err != nil {
log.Printf("%s %s %s %d error: %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status, c.Err)
return
}
log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status)
}
func (c *Context) NotFound() {
c.Template("notFoundHandler")
c.Data = webData{Title: "404"}
c.Status = http.StatusNotFound
c.Response.WriteHeader(http.StatusNotFound)
c.Exec()
}
func (c *Context) Template(name string) {
c.T = templ[name]
c.T = c.Srv.templ[name]
}
func (c *Context) Write(buf []byte) (err error) {
@ -122,6 +112,7 @@ func (c *Context) SetHeader(name, value string) {
}
func (c *Context) Redirect(uri string, code int) {
log.Printf("%s %s %s %d %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, code, uri)
http.Redirect(c.Response, c.Request, uri, code)
}
@ -149,12 +140,12 @@ func (c *Context) Var(name string) (ret string) {
return
}
func (c *Context) InitToken() {
func (c *Context) initToken() {
token := c.getToken()
if token == nil || len(token) != keyLen {
s, _ := store.Get(c.Request, cookieName)
s, _ := c.Srv.Store.Get(c.Request, cookieName)
s.Values["token"] = genKey(keyLen)
s.Options = store.Options
s.Options = c.Srv.Store.Options
err := s.Save(c.Request, c.Response)
if err != nil {
log.Println("InitToken: ", err)
@ -162,14 +153,14 @@ func (c *Context) InitToken() {
}
}
// NewToken sets and returns the xsrf token
func (c *Context) NewToken() {
func (c *Context) newToken() {
c.initToken()
rnd := genKey(keyLen)
c.Data.Token = base64.StdEncoding.EncodeToString(append(rnd, xor(rnd, c.getToken())...))
}
// CheckToken validates the xsrf token
func (c *Context) CheckToken() bool {
func (c *Context) CheckToken(f func()) bool {
token, err := base64.StdEncoding.DecodeString(c.Form("token"))
if err != nil {
log.Println("CheckToken: ", err)
@ -178,36 +169,45 @@ func (c *Context) CheckToken() bool {
if len(token) != authLen {
return false
}
return subtle.ConstantTimeCompare(c.getToken(), xor(token[keyLen:], token[:keyLen])) == 1
ok := subtle.ConstantTimeCompare(c.getToken(), xor(token[keyLen:], token[:keyLen])) == 1
if !ok {
if f != nil {
f()
return ok
}
c.Data.Msg = "wrong csrf token"
c.Exec()
}
return ok
}
func (c *Context) LoggedOn() (ok bool) {
s, _ := store.Get(c.Request, cookieName)
s, _ := c.Srv.Store.Get(c.Request, cookieName)
_, ok = s.Values["user"]
return
}
func (c *Context) SetUdata(user string, admin bool) {
s, _ := store.Get(c.Request, cookieName)
s, _ := c.Srv.Store.Get(c.Request, cookieName)
s.Values["user"] = user
s.Values["admin"] = admin
s.Options = store.Options
s.Options = c.Srv.Store.Options
if err := s.Save(c.Request, c.Response); err != nil {
log.Println(err)
}
}
func (c *Context) UnsetUdata() {
s, _ := store.Get(c.Request, cookieName)
s, _ := c.Srv.Store.Get(c.Request, cookieName)
s.Values = make(map[interface{}]interface{})
s.Options = store.Options
s.Options = c.Srv.Store.Options
if err := s.Save(c.Request, c.Response); err != nil {
log.Println(err)
}
}
func (c *Context) GetUser() string {
s, _ := store.Get(c.Request, cookieName)
s, _ := c.Srv.Store.Get(c.Request, cookieName)
if ret, ok := s.Values["user"].(string); ok {
return ret
}
@ -215,7 +215,7 @@ func (c *Context) GetUser() string {
}
func (c *Context) GetAdmin() bool {
s, _ := store.Get(c.Request, cookieName)
s, _ := c.Srv.Store.Get(c.Request, cookieName)
if ret, ok := s.Values["admin"].(bool); ok {
return ret
}
@ -223,7 +223,7 @@ func (c *Context) GetAdmin() bool {
}
func (c *Context) getToken() []byte {
s, _ := store.Get(c.Request, cookieName)
s, _ := c.Srv.Store.Get(c.Request, cookieName)
t, ok := s.Values["token"]
if !ok {
return nil

91
dump.go
View file

@ -6,10 +6,14 @@ import (
"encoding/json"
"fmt"
"os"
"github.com/blevesearch/bleve"
)
type Dump struct {
Articles []DumpArticle `json:"articles"`
Snippets []Snippet `json:"snippets"`
Users []User `json:"users"`
}
type DumpArticle struct {
Title string `json:"title"`
LinkTitle string `json:"link_title"`
@ -19,35 +23,38 @@ type DumpArticle struct {
Public bool `json:"public"`
}
func verboseLog(prefix, title string) {
if verbose {
func verboseLog(prefix, title string, v bool) {
if v {
fmt.Fprintln(os.Stderr, prefix, title)
}
}
func dumpWiki() error {
index, err := bleve.OpenUsing(bleveStore, map[string]interface{}{"read_only": true})
func dump(path string, v bool) (err error) {
i, err := NewDumpIndex(path)
if err != nil {
return err
return
}
engine = NewEngine(index)
art, err := engine.Get(false, indexName)
defer i.Close()
bs, err := NewBoltStore()
if err != nil {
return err
return
}
defer engine.Close()
var arts []DumpArticle
titles, err := engine.Dump()
art, err := i.Get(false, indexName)
if err != nil {
return err
return
}
var dump Dump
titles, err := i.Dump()
if err != nil {
return
}
for _, title := range titles {
art, err = engine.Get(false, title)
art, err = i.Get(false, title)
if err != nil {
return err
return
}
verboseLog("dump:", art.Title)
arts = append(arts, DumpArticle{
verboseLog("dump:", art.Title, v)
dump.Articles = append(dump.Articles, DumpArticle{
Title: art.Title,
LinkTitle: art.LinkTitle,
MD: art.MD,
@ -56,31 +63,41 @@ func dumpWiki() error {
Public: art.Public,
})
}
return json.NewEncoder(os.Stdout).Encode(arts)
}
func restoreWiki() (err error) {
index, err := bleve.Open(bleveStore)
if err != nil {
mapping := bleve.NewIndexMapping()
index, err = bleve.New(bleveStore, mapping)
if err != nil {
return
}
}
engine = NewEngine(index)
defer engine.Close()
var arts []DumpArticle
err = json.NewDecoder(os.Stdin).Decode(&arts)
//TODO snippets
//verboseLog("dump:", "snippets", v)
verboseLog("dump:", "users", v)
dump.Users, err = bs.DumpUsers()
if err != nil {
return
}
for _, art := range arts {
verboseLog("restore:", art.Title)
err = engine.Restore(art)
return json.NewEncoder(os.Stdout).Encode(dump)
}
func restore(path string, v bool) (err error) {
i, err := NewIndex(path)
if err != nil {
return
}
defer i.Close()
bs, err := NewBoltStore()
if err != nil {
return
}
var dump Dump
err = json.NewDecoder(os.Stdin).Decode(&dump)
if err != nil {
return
}
for _, art := range dump.Articles {
verboseLog("restore:", art.Title, v)
err = i.Restore(&art)
if err != nil {
return
}
}
//TODO snippets
//verboseLog("restore:", "snippets", v)
verboseLog("restore:", "users", v)
err = bs.RestoreUsers(dump.Users)
return
}

1
dump.test Normal file

File diff suppressed because one or more lines are too long

43
editor.go Normal file
View file

@ -0,0 +1,43 @@
package main
import "sync"
type Editor struct {
Username string
Time string
}
type EditorStore struct {
sync.RWMutex
m map[string]*Editor
}
func NewEditorStore() *EditorStore {
return &EditorStore{m: make(map[string]*Editor)}
}
func (e *EditorStore) Set(page, username string) *Editor {
e.Lock()
defer e.Unlock()
editor := e.m[page]
if editor != nil {
return editor
}
e.m[page] = &Editor{username, now()}
return nil
}
func (e *EditorStore) RemoveEditor(page, username string) {
e.Lock()
defer e.Unlock()
if editor, ok := e.m[page]; ok {
if editor.Username == username {
delete(e.m, page)
}
}
}
func (e *EditorStore) Remove(page string) {
e.Lock()
defer e.Unlock()
delete(e.m, page)
}

270
engine.go
View file

@ -1,270 +0,0 @@
// Copyright (C) 2017 Marius Schellenberger
package main
import (
"errors"
"html/template"
"regexp"
"time"
"github.com/blevesearch/bleve"
"github.com/blevesearch/bleve/document"
)
const (
timeFmt = "2006-01-02 15:04:05"
maxAllResult = 1000000
maxSearchResult = 101
)
var titleRe = regexp.MustCompile("[^a-zA-Z0-9+ ]+")
func now() string {
return time.Now().Format(timeFmt)
}
type Article struct {
Title string `json:"title"`
LinkTitle string
Search string `json:"search"`
Index template.HTML `json:"index"`
Text template.HTML `json:"text"`
MD string `json:"md"`
Created string `json:"created"`
Updated string `json:"updated"`
Public bool `json:"public"`
}
type Engine struct {
index bleve.Index
}
func NewEngine(index bleve.Index) *Engine {
return &Engine{index}
}
func (e *Engine) Close() error {
return e.index.Close()
}
func (e *Engine) Restore(dumpArt DumpArticle) error {
art := render(dumpArt.MD)
art.Title = dumpArt.Title
art.Created = dumpArt.Created
art.Updated = dumpArt.Updated
art.Public = dumpArt.Public
return e.index.Index(dumpArt.LinkTitle, art)
}
func (e *Engine) Index(title, text, user string, public bool) (string, error) {
if user == "" {
return "", errors.New("engine: user cannot be empty")
}
title = titleRe.ReplaceAllString(title, "")
newTitle := makeLinkTitle(title)
if err := e.Exists(false, newTitle); err == nil {
return "", errors.New("engine: article '" + title + "' already exists")
}
art := render(text)
art.Title = title
art.Created = user + " " + now()
art.Public = public
err := e.index.Index(newTitle, art)
return newTitle, err
}
func (e *Engine) Exists(public bool, title string) error {
_, err := e.Get(public, title)
if err != nil {
return err
}
return nil
}
func (e *Engine) Delete(title string) error {
if title == indexName {
return errors.New("engine: page '" + indexName + "' cannot be deleted")
}
if err := e.Exists(false, title); err != nil {
return err
}
return e.index.Delete(title)
}
func (e *Engine) Update(title, text, user string, public bool) error {
if user == "" {
return errors.New("engine: user cannot be empty")
}
if title == indexName && !public {
return errors.New("engine: page '" + indexName + "' must stay public")
}
if err := e.Exists(false, title); err != nil {
return err
}
art, err := e.Get(false, title)
if err != nil {
return err
}
rend := render(text)
art.Updated = user + " " + now()
art.Search = rend.Search
art.Index = rend.Index
art.Text = rend.Text
art.MD = rend.MD
art.Public = public
return e.index.Index(title, art)
}
func (e *Engine) Get(public bool, title string) (Article, error) {
var art Article
doc, err := e.index.Document(title)
if err != nil {
return art, err
}
if doc == nil || doc.Fields == nil {
return art, errors.New("engine: article '" + title + "' does not esist")
}
for _, field := range doc.Fields {
switch f := field.(type) {
case *document.TextField:
switch field.Name() {
case "title":
art.Title = string(f.Value())
case "index":
art.Index = template.HTML(string(f.Value()))
case "text":
art.Text = template.HTML(string(f.Value()))
case "md":
art.MD = string(f.Value())
case "created":
art.Created = string(f.Value())
case "updated":
art.Updated = string(f.Value())
}
case *document.BooleanField:
if field.Name() == "public" {
p, err := f.Boolean()
if err != nil {
return art, err
}
art.Public = p
}
}
}
art.LinkTitle = title
if public && !art.Public {
err = errors.New("engine: article '" + title + "' does not esist")
}
return art, err
}
func (e *Engine) Search(public bool, search string) (string, error) {
var (
res *bleve.SearchResult
req *bleve.SearchRequest
err error
)
if public {
req = bleve.NewSearchRequest(bleve.NewConjunctionQuery([]bleve.Query{
bleve.NewBoolFieldQuery(true).SetField("public"),
bleve.NewQueryStringQuery("title:" + search + " search:" + search),
}))
} else {
req = bleve.NewSearchRequest(bleve.NewQueryStringQuery("title:" + search + " search:" + search))
}
req.Highlight = bleve.NewHighlightWithStyle("html")
req.Size = maxSearchResult
res, err = e.index.Search(req)
if err != nil {
return "", err
}
if res.Total == 0 {
return "<b>No search results</b>", nil
} else if res.Total == maxSearchResult {
return "", errors.New("engine: too many search results")
}
html := ""
for _, hit := range res.Hits {
if hit == nil {
continue
}
art, err := e.Get(false, hit.ID)
if err != nil {
continue
}
html += `<a href="/wiki/` + hit.ID + `"><b>` + art.Title + "</b></a>"
for fragField, frags := range hit.Fragments {
if fragField == "title" || fragField == "search" {
html += `<pre style="white-space: pre-wrap">`
for _, f := range frags {
html += f
}
html += "</pre>"
break
}
}
html += "<br>"
}
return html, nil
}
type Title struct {
Title string
LinkTitle string
}
func (e *Engine) GetAll(public bool) (t []Title, err error) {
var (
res *bleve.SearchResult
req *bleve.SearchRequest
)
if public {
req = bleve.NewSearchRequest(bleve.NewBoolFieldQuery(true).SetField("public"))
} else {
req = bleve.NewSearchRequest(bleve.NewMatchAllQuery())
}
req.Size = maxAllResult
res, err = e.index.Search(req)
if err != nil {
return
}
for _, v := range res.Hits {
if v == nil {
continue
}
if v.ID == indexName {
continue
}
doc, err := e.index.Document(v.ID)
if err != nil || doc == nil || doc.Fields == nil {
continue
}
for _, field := range doc.Fields {
if field.Name() == "title" {
f, ok := field.(*document.TextField)
if ok {
t = append(t, Title{string(f.Value()), v.ID})
break
}
}
}
}
return
}
func (e *Engine) Dump() (titles []string, err error) {
req := bleve.NewSearchRequest(bleve.NewMatchAllQuery())
req.Size = maxAllResult
res, err := e.index.Search(req)
if err != nil {
return
}
for _, v := range res.Hits {
if v == nil {
continue
}
titles = append(titles, v.ID)
}
return
}

View file

@ -4,39 +4,16 @@ package main
import (
"html/template"
"net"
"net/http"
"regexp"
"time"
)
func webserver(l net.Listener) {
cookieStore()
loadTemplates()
srv := &http.Server{
Handler: buildRoutes(),
}
log.Fatal("webserver: ", srv.Serve(l))
type notFoundHandler struct {
s *HTTPServer
}
type notFoundHandler struct{}
func (notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
f := func(ctx *Context) {
ctx.Template("notFoundHandler")
ctx.Data = webData{
Title: "404",
}
ctx.Exec()
}
w.WriteHeader(http.StatusNotFound)
f(newContext(w, r))
}
func contextWrapper(h ctxHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h(newContext(w, r))
}
func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
newContext(w, r, nf.s).NotFound()
}
func adminAuthHandler(h ctxHandler) ctxHandler {
@ -45,7 +22,7 @@ func adminAuthHandler(h ctxHandler) ctxHandler {
h(ctx)
return
}
ctx.Redirect("/404", 302)
ctx.NotFound()
}
}
@ -59,7 +36,17 @@ func userAuthHandler(h ctxHandler) ctxHandler {
h(ctx)
return
}
ctx.Redirect("/404", 302)
ctx.NotFound()
}
}
func authHandler(h ctxHandler) ctxHandler {
return func(ctx *Context) {
if ctx.LoggedOn() {
h(ctx)
return
}
ctx.NotFound()
}
}
@ -69,9 +56,9 @@ func staticHandler(ctx *Context) {
ctx.SetHeader("Expires", time.Now().Add(time.Hour*12).Format(http.TimeFormat))
switch ctx.Path() {
case "/bootstrap.css":
err = ctx.Write(res["bootstrap.css"])
err = ctx.Write(ctx.Srv.res["bootstrap.css"])
case "/custom.css":
err = ctx.Write(res["custom.css"])
err = ctx.Write(ctx.Srv.res["custom.css"])
}
if err != nil {
log.Println("static:", err)
@ -79,7 +66,6 @@ func staticHandler(ctx *Context) {
}
func indexHandler(ctx *Context) {
ctx.InitToken()
if !ctx.LoggedOn() {
ctx.UnsetUdata()
}
@ -87,7 +73,6 @@ func indexHandler(ctx *Context) {
}
func loginHandler(ctx *Context) {
ctx.InitToken()
if ctx.LoggedOn() {
ctx.Redirect("/wiki/Index", 302)
return
@ -99,38 +84,31 @@ func loginHandler(ctx *Context) {
}
switch ctx.Method() {
case "GET":
ctx.NewToken()
ctx.Exec()
case "POST":
user := ctx.Form("user")
password := ctx.Form("password")
submit := ctx.Form("submit")
if !ctx.CheckToken() {
ctx.NewToken()
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
if !ctx.CheckToken(nil) {
return
}
if user == "" || password == "" || submit != "Login" {
ctx.NewToken()
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
err := db.Login(user, password)
err := ctx.Srv.BS.Login(user, password)
if err != nil {
ctx.NewToken()
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.SetUdata(user, db.UserIsAdmin(user))
ctx.SetUdata(user, ctx.Srv.BS.UserIsAdmin(user))
ctx.Redirect("/wiki/Index", 302)
}
}
func searchHandler(ctx *Context) {
ctx.InitToken()
ctx.Template("searchHandler")
ctx.Data = webData{
Title: "Search",
@ -138,34 +116,27 @@ func searchHandler(ctx *Context) {
}
switch ctx.Method() {
case "GET":
ctx.NewToken()
ctx.Exec()
case "POST":
search := ctx.Form("search")
submit := ctx.Form("submit")
if !ctx.CheckToken() {
ctx.NewToken()
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
if !ctx.CheckToken(nil) {
return
}
if search == "" || submit != "Search" {
ctx.NewToken()
if submit != "Search" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
res, err := engine.Search(!ctx.LoggedOn(), search)
res, err := ctx.Srv.Index.Search(!ctx.LoggedOn(), search)
if err != nil {
ctx.NewToken()
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Data.Search = search
ctx.Data.Data = template.HTML(res)
ctx.NewToken()
ctx.Exec()
}
}
@ -177,7 +148,7 @@ func allHandler(ctx *Context) {
BodyTitle: "All Articles",
Admin: ctx.GetAdmin(),
}
all, err := engine.GetAll(!ctx.LoggedOn())
all, err := ctx.Srv.Index.GetAll(!ctx.LoggedOn())
if err != nil {
ctx.Data.Msg = err.Error()
}
@ -187,14 +158,9 @@ func allHandler(ctx *Context) {
func wikiNewHandler(ctx *Context) {
ctx.Template("wikiNewHandler")
ctx.Data = webData{
Title: "New Article",
BodyTitle: "New Article",
Admin: ctx.GetAdmin(),
}
ctx.Data = webData{Title: "New Article"}
switch ctx.Method() {
case "GET":
ctx.NewToken()
ctx.Exec()
case "POST":
title := ctx.Form("title")
@ -210,28 +176,7 @@ func wikiNewHandler(ctx *Context) {
MD: text,
}
ctx.Data.Data = art
if !ctx.CheckToken() {
ctx.NewToken()
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
return
}
ctx.NewToken()
m, err := regexp.MatchString("[^a-zA-Z0-9 _-]", title)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
if m {
ctx.Data.Msg = "title contains forbidden characters, allowed are [a-zA-Z0-9 _-]"
ctx.Exec()
return
}
err = engine.Exists(!ctx.LoggedOn(), title)
if err == nil {
ctx.Data.Msg = "article alerady exsist"
ctx.Exec()
if !ctx.CheckToken(nil) {
return
}
if submit != "Submit" {
@ -239,7 +184,7 @@ func wikiNewHandler(ctx *Context) {
ctx.Exec()
return
}
newTitle, err := engine.Index(title, text, ctx.GetUser(), pub)
newTitle, err := ctx.Srv.Index.Index(title, text, ctx.GetUser(), pub)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
@ -256,11 +201,10 @@ func wikiHandler(ctx *Context) {
ctx.Data = webData{
Admin: ctx.GetAdmin(),
}
ctx.NewToken()
title := ctx.Var("title")
art, err := engine.Get(!ctx.LoggedOn(), title)
art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title)
if err != nil {
ctx.Redirect("/404", 302)
ctx.NotFound()
return
}
art.LinkTitle = title
@ -283,16 +227,16 @@ func wikiEditHandler(ctx *Context) {
title := ctx.Var("title")
switch ctx.Method() {
case "GET":
ctx.NewToken()
art, err := engine.Get(!ctx.LoggedOn(), title)
art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title)
if err != nil {
ctx.Redirect("/404", 302)
ctx.NotFound()
return
}
art.LinkTitle = title
ctx.Data.Data = art
ctx.Data.Title = art.Title
ctx.Data.BodyTitle = art.Title
ctx.Data.Editor = ctx.Srv.Editors.Set(title, ctx.GetUser())
ctx.Exec()
case "POST":
ftitle := ctx.Form("title")
@ -311,24 +255,26 @@ func wikiEditHandler(ctx *Context) {
}
ctx.Data.Title = ftitle
ctx.Data.BodyTitle = ftitle
if !ctx.CheckToken() {
ctx.NewToken()
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
if !ctx.CheckToken(nil) {
return
}
if submit == "Back" {
ctx.Srv.Editors.RemoveEditor(title, ctx.GetUser())
ctx.Redirect("/wiki/"+title, 302)
return
}
ctx.NewToken()
if submit != "Submit" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
err := engine.Update(title, text, ctx.GetUser(), pub)
err := ctx.Srv.Index.Update(title, text, ctx.GetUser(), pub)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Srv.Editors.Remove(title)
ctx.Redirect("/wiki/"+title, 302)
}
}
@ -336,24 +282,26 @@ func wikiEditHandler(ctx *Context) {
func wikiDelHandler(ctx *Context) {
title := ctx.Var("title")
submit := ctx.Form("submit")
if !ctx.CheckToken() {
f := func() {
ctx.Redirect("/wiki/"+title, 302)
}
if !ctx.CheckToken(f) {
return
}
if submit != "Delete" {
ctx.Redirect("/wiki/"+title, 302)
f()
return
}
if err := engine.Delete(title); err != nil {
if err := ctx.Srv.Index.Delete(title); err != nil {
log.Println("delete:", err)
ctx.Redirect("/wiki/"+title, 302)
f()
}
ctx.Redirect("/wiki/Index", 302)
}
func usersHandler(ctx *Context) {
if !ctx.LoggedOn() {
ctx.Redirect("/404", 302)
ctx.NotFound()
return
}
ctx.Template("usersHandler")
@ -362,8 +310,7 @@ func usersHandler(ctx *Context) {
BodyTitle: "Users",
User: ctx.GetUser(),
}
ctx.NewToken()
us, err := db.GetUsers()
us, err := ctx.Srv.BS.GetUsers()
if err != nil {
ctx.Data.Msg = err.Error()
}
@ -379,7 +326,6 @@ func userNewHandler(ctx *Context) {
}
switch ctx.Method() {
case "GET":
ctx.NewToken()
ctx.Exec()
case "POST":
user := ctx.Form("user")
@ -391,21 +337,15 @@ func userNewHandler(ctx *Context) {
adm = true
}
submit := ctx.Form("submit")
if !ctx.CheckToken() {
ctx.NewToken()
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
if !ctx.CheckToken(nil) {
return
}
if user == "" || password == "" || password != repeat || submit != "Submit" {
ctx.NewToken()
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
log.Println(user, password, adm)
if err := db.CreateUser(user, password, adm); err != nil {
ctx.NewToken()
if err := ctx.Srv.BS.CreateUser(user, password, adm); err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
@ -422,14 +362,13 @@ func userEditHandler(ctx *Context) {
}
switch ctx.Method() {
case "GET":
u, err := db.GetUserWithoutPassword(ctx.Var("user"))
u, err := ctx.Srv.BS.GetUserWithoutPassword(ctx.Var("user"))
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Data.Data = u
ctx.NewToken()
ctx.Exec()
case "POST":
user := ctx.Var("user")
@ -441,29 +380,23 @@ func userEditHandler(ctx *Context) {
adm = true
}
submit := ctx.Form("submit")
if !ctx.CheckToken() {
ctx.NewToken()
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
if !ctx.CheckToken(nil) {
return
}
if user == "" || password != repeat || submit != "Submit" {
ctx.NewToken()
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
if ctx.GetAdmin() {
if err := db.AdminUpdateUser(user, password, adm); err != nil {
ctx.NewToken()
if err := ctx.Srv.BS.AdminUpdateUser(user, password, adm); err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
} else {
if user == ctx.GetUser() {
if err := db.UpdateUserPassword(user, password); err != nil {
ctx.NewToken()
if err := ctx.Srv.BS.UpdateUserPassword(user, password); err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
@ -478,7 +411,7 @@ func userEditHandler(ctx *Context) {
func userUnlockHandler(ctx *Context) {
user := ctx.Var("user")
if err := db.UnlockUser(user); err != nil {
if err := ctx.Srv.BS.UnlockUser(user); err != nil {
log.Println("unlock:", err)
}
ctx.Redirect("/users", 302)
@ -492,17 +425,16 @@ func userDelHandler(ctx *Context) {
}
switch ctx.Method() {
case "GET":
user, err := db.GetUserWithoutPassword(ctx.Var("user"))
user, err := ctx.Srv.BS.GetUserWithoutPassword(ctx.Var("user"))
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Data.Data = user
ctx.NewToken()
ctx.Exec()
case "POST":
if err := db.DeleteUser(ctx.Var("user")); err != nil {
if err := ctx.Srv.BS.DeleteUser(ctx.Var("user")); err != nil {
log.Println("delete:", err)
}
ctx.Redirect("/users", 302)

36
helper.go Normal file
View file

@ -0,0 +1,36 @@
package main
import (
"fmt"
"strconv"
"time"
)
const timeFmt = "2006-01-02 15:04:05"
func now() string {
return time.Now().Format(timeFmt)
}
func since(t time.Time) string {
s := time.Since(t)
u := uint64(s)
if u <= 0 {
return "0s"
}
str := s.String()
switch {
case u >= uint64(time.Second):
f, _ := strconv.ParseFloat(str[:len(str)-1], 64)
return fmt.Sprintf("%.fs", f)
case u < uint64(time.Microsecond):
f, _ := strconv.ParseFloat(str[:len(str)-2], 64)
return fmt.Sprintf("%.fns", f)
case u < uint64(time.Millisecond):
f, _ := strconv.ParseFloat(str[:len(str)-3], 64)
return fmt.Sprintf("%.fµs", f)
default:
f, _ := strconv.ParseFloat(str[:len(str)-2], 64)
return fmt.Sprintf("%.fms", f)
}
}

328
index.go Normal file
View file

@ -0,0 +1,328 @@
// Copyright (C) 2017 Marius Schellenberger
package main
import (
"fmt"
"html/template"
"os"
"github.com/blevesearch/bleve"
"github.com/blevesearch/bleve/document"
)
const (
maxResult = 1e6
maxSearchResult = 101
noResults = "<b>No search results</b>"
indexName = "Index"
welcome = `# Welcome to GoWiki
This is the [Index](/wiki/Index) page.
You can customize it how you like.`
)
type Article struct {
Title string `json:"title"`
LinkTitle string `json:"-"`
Search string `json:"search"`
Index template.HTML `json:"index"`
Text template.HTML `json:"text"`
MD string `json:"md"`
Created string `json:"created"`
Updated string `json:"updated"`
Public bool `json:"public"`
}
func notExists(title string) error {
return indexErr("article '" + title + "' does not esist")
}
func indexErr(i interface{}) error {
return fmt.Errorf("index: %s", i)
}
type Index struct {
index bleve.Index
cache *IndexCache
}
func NewIndex(path string) (i *Index, err error) {
var bi bleve.Index
if _, err = os.Stat(path); os.IsNotExist(err) {
mapping := bleve.NewIndexMapping()
bi, err = bleve.New(path, mapping)
if err != nil {
err = indexErr(err)
return
}
i = &Index{index: bi}
_, err = i.Index(indexName, welcome, defUser, true)
if err != nil {
err = indexErr(err)
return
}
} else {
bi, err = bleve.Open(path)
if err != nil {
err = indexErr(err)
return
}
i = &Index{index: bi}
}
i.cache = NewIndexCache()
return
}
func NewDumpIndex(path string) (i *Index, err error) {
bi, err := bleve.OpenUsing(path, map[string]interface{}{"read_only": true})
if err != nil {
err = indexErr(err)
return
}
i = &Index{index: bi}
return
}
func (i *Index) Close() error {
return i.index.Close()
}
func (i *Index) Restore(dumpArt *DumpArticle) error {
art := render(dumpArt.MD).Article()
art.Title = dumpArt.Title
art.Created = dumpArt.Created
art.Updated = dumpArt.Updated
art.Public = dumpArt.Public
return i.index.Index(dumpArt.LinkTitle, art)
}
func (i *Index) Index(title, text, user string, public bool) (string, error) {
if user == "" {
return "", indexErr("user cannot be empty")
}
newTitle, title := makeLinkTitle(title)
if i.exists(false, newTitle) {
return "", indexErr("article '" + title + "' already exists")
}
art := render(text).Article()
art.Title = title
art.Created = user + " " + now()
art.Public = public
err := i.index.Index(newTitle, art)
i.cache.Add(newTitle, art)
return newTitle, err
}
func (i *Index) exists(public bool, title string) bool {
_, err := i.Get(public, title)
return err == nil
}
func (i *Index) Delete(title string) error {
if title == indexName {
return indexErr("page '" + indexName + "' cannot be deleted")
}
if !i.exists(false, title) {
return notExists(title)
}
err := i.index.Delete(title)
if err != nil {
return err
}
i.cache.Delete(title)
return err
}
func (i *Index) Update(title, text, user string, public bool) error {
if user == "" {
return indexErr("user cannot be empty")
}
if title == indexName && !public {
return indexErr("page '" + indexName + "' must stay public")
}
if !i.exists(false, title) {
return notExists(title)
}
art, err := i.Get(false, title)
if err != nil {
return err
}
rend := render(text).Article()
art.Updated = user + " " + now()
art.Search = rend.Search
art.Index = rend.Index
art.Text = rend.Text
art.MD = rend.MD
art.Public = public
err = i.index.Index(title, art)
if err != nil {
return err
}
i.cache.Add(title, &art)
return err
}
func (i *Index) Get(public bool, title string) (art Article, err error) {
a := i.cache.Get(title)
switch {
case a != nil:
art = *a
default:
var doc *document.Document
doc, err = i.index.Document(title)
if err != nil {
return
}
if doc == nil || doc.Fields == nil {
err = notExists(title)
return
}
for _, field := range doc.Fields {
switch f := field.(type) {
case *document.TextField:
switch field.Name() {
case "title":
art.Title = string(f.Value())
case "index":
art.Index = template.HTML(string(f.Value()))
case "text":
art.Text = template.HTML(string(f.Value()))
case "md":
art.MD = string(f.Value())
case "created":
art.Created = string(f.Value())
case "updated":
art.Updated = string(f.Value())
}
case *document.BooleanField:
if field.Name() == "public" {
p, e := f.Boolean()
if e != nil {
err = e
return
}
art.Public = p
}
}
}
}
art.LinkTitle = title
if public && !art.Public {
err = notExists(title)
}
return
}
func (i *Index) Search(public bool, search string) (html string, err error) {
if search == "" {
html = noResults
return
}
var (
res *bleve.SearchResult
req *bleve.SearchRequest
)
if public {
req = bleve.NewSearchRequest(bleve.NewConjunctionQuery([]bleve.Query{
bleve.NewBoolFieldQuery(true).SetField("public"),
bleve.NewQueryStringQuery("title:" + search + " search:" + search),
}))
} else {
req = bleve.NewSearchRequest(bleve.NewQueryStringQuery("title:" + search + " search:" + search))
}
req.Highlight = bleve.NewHighlightWithStyle("html")
req.Size = maxSearchResult
res, err = i.index.Search(req)
if err != nil {
return
}
if res.Total == 0 {
html = noResults
return
} else if res.Total == maxSearchResult {
err = indexErr("too many search results")
return
}
for _, hit := range res.Hits {
if hit == nil {
continue
}
art, err := i.Get(false, hit.ID)
if err != nil {
continue
}
html += `<a href="/wiki/` + hit.ID + `"><b>` + art.Title + "</b></a>"
for fragField, frags := range hit.Fragments {
if fragField == "title" || fragField == "search" {
html += `<pre style="white-space: pre-wrap">`
for _, f := range frags {
html += f
}
html += "</pre>"
break
}
}
html += "<br>"
}
return
}
type Title struct {
Title string
LinkTitle string
}
func (i *Index) GetAll(public bool) (t []Title, err error) {
var (
res *bleve.SearchResult
req *bleve.SearchRequest
)
if public {
req = bleve.NewSearchRequest(bleve.NewBoolFieldQuery(true).SetField("public"))
} else {
req = bleve.NewSearchRequest(bleve.NewMatchAllQuery())
}
req.Size = maxResult
res, err = i.index.Search(req)
if err != nil {
return
}
for _, v := range res.Hits {
if v == nil {
continue
}
if v.ID == indexName {
continue
}
doc, err := i.index.Document(v.ID)
if err != nil || doc == nil || doc.Fields == nil {
continue
}
for _, field := range doc.Fields {
if field.Name() == "title" {
f, ok := field.(*document.TextField)
if ok {
t = append(t, Title{string(f.Value()), v.ID})
break
}
}
}
}
return
}
func (i *Index) Dump() (titles []string, err error) {
req := bleve.NewSearchRequest(bleve.NewMatchAllQuery())
req.Size = maxResult
res, err := i.index.Search(req)
if err != nil {
return
}
for _, v := range res.Hits {
if v == nil {
continue
}
titles = append(titles, v.ID)
}
return
}

154
main.go
View file

@ -4,154 +4,14 @@
package main
import (
"errors"
"flag"
logger "log"
"net"
"os"
"os/signal"
"syscall"
"time"
import "github.com/urfave/cli"
"git.giftfish.de/ston1th/godrop"
"github.com/blevesearch/bleve"
)
const (
bleveStore = "."
defDataDir = "/var/lib/gowiki"
defListen = "127.0.0.1:8090"
defLog = "gowiki.log"
defUser = "admin"
defPassword = "gowiki"
defRunUser = "wiki"
defRunGroup = "wiki"
welcome = `# Welcome to GoWiki
This is the [Index](/wiki/Index) page.
You can customize it how you like.`
indexName = "Index"
)
var (
datadir string
listen string
secureCookie bool
user string
group string
logfile string
engine *Engine
db *BoltStore
dump bool
restore bool
verbose bool
)
var version string
func main() {
flag.StringVar(&datadir, "d", defDataDir, "data directory")
flag.StringVar(&user, "u", defRunUser, "drop privileges to user")
flag.StringVar(&group, "g", defRunGroup, "drop privileges to group")
flag.StringVar(&listen, "l", defListen, "listening <address>:<port>")
flag.StringVar(&logfile, "log", defLog, "log file")
flag.BoolVar(&secureCookie, "s", false, "enable secure cookie")
flag.BoolVar(&dump, "dump", false, "create a json dump of all articles in the data directory (dump is written to stdout)")
flag.BoolVar(&restore, "restore", false, "restore a json dump to the data directory (dump is read from stdin)")
flag.BoolVar(&verbose, "v", false, "enable verbose dump/restore mode")
flag.Parse()
err := os.Chdir(datadir)
if err != nil {
logger.Fatal("main: ", err)
}
if dump {
err = dumpWiki()
if err != nil {
logger.Fatal("dump: ", err)
}
return
}
if restore {
err = restoreWiki()
if err != nil {
logger.Fatal("restore: ", err)
}
return
}
cfg := godrop.Config{
User: user,
Group: group,
}
err = godrop.Drop(cfg, func() (net.Listener, error) {
return net.Listen("tcp", listen)
})
if err != nil {
logger.Fatal("main: ", err)
}
newLogger(logfile)
l, err := godrop.GetListener()
if err != nil {
log.Fatal("main: ", err)
}
db, err = NewBoltStore("users")
if err != nil {
log.Fatal("main: ", err)
}
if !db.UserExists(defUser) {
err = db.CreateUser(defUser, defPassword, true)
if err != nil {
log.Fatal("main: ", err)
}
}
if _, err := os.Stat(bleveStore); os.IsNotExist(err) {
mapping := bleve.NewIndexMapping()
index, err := bleve.New(bleveStore, mapping)
if err != nil {
log.Fatal("main: ", err)
}
engine = NewEngine(index)
_, err = engine.Index(indexName, welcome, defUser, true)
if err != nil {
log.Fatal("main: ", err)
}
} else {
index, err := bleve.Open(bleveStore)
if err != nil {
log.Fatal("main: ", err)
}
engine = NewEngine(index)
}
go webserver(l)
log.Println("gowiki started")
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Println("caught " + sig.String())
go func() {
time.Sleep(time.Second * 20)
log.Fatal(errors.New("stop timed out: killing"))
}()
err = l.Close()
if err != nil {
log.Println(err)
}
err = db.Close()
if err != nil {
log.Println(err)
}
err = engine.Close()
if err != nil {
log.Println(err)
}
app := cli.NewApp()
app.Name = "gowiki"
app.Usage = "a simple wiki engine"
app.Version = version
run(app)
}

View file

@ -25,38 +25,74 @@ var (
reLink = regexp.MustCompile(`id="(.+)">`)
whiteSpace = regexp.MustCompile(`\s+`)
reIndex = regexp.MustCompile("<h([1-6])>(.+)</h[1-6]>")
titleRe = regexp.MustCompile("[^a-zA-Z0-9 -]+")
)
func render(text string) Article {
type rendered struct {
Index template.HTML
Text template.HTML
MD string
Raw []byte
}
func (r *rendered) Article() *Article {
return &Article{
Index: r.Index,
Text: r.Text,
MD: r.MD,
Search: whiteSpace.ReplaceAllString(string(filter.Filter(r.Raw)), " "),
}
}
func (r *rendered) Snippet() *Snippet {
return &Snippet{
Index: r.Index,
Text: r.Text,
MD: r.MD,
}
}
func render(text string) *rendered {
rend := blackfriday.MarkdownCommon([]byte(text))
html := string(rend)
index := buildIndex(html)
index, html := buildIndex(string(rend))
html = strings.Replace(html, "<table>", `<table class="table">`, -1)
html = reHeader.ReplaceAllString(html, `${1} id="${2}">${2}${3}`)
html = reLink.ReplaceAllStringFunc(html, makeLinkTitle)
html = strings.Replace(html, "</h1>", `</h1><hr>`, -1)
html = strings.Replace(html, "<a", `<a target="_blank"`, -1)
return Article{
Search: whiteSpace.ReplaceAllString(string(filter.Filter(rend)), " "),
Index: template.HTML(index),
Text: template.HTML(html),
MD: text,
return &rendered{
Index: template.HTML(index),
Text: template.HTML(html),
MD: text,
Raw: rend,
}
}
func makeLinkTitle(title string) string {
return strings.Replace(title, " ", "+", -1)
func spaceReplace(s string) string {
return strings.Replace(s, " ", "+", -1)
}
func buildIndex(html string) (ret string) {
func makeLinkTitle(t string) (newTitle string, title string) {
title = titleRe.ReplaceAllString(t, "")
newTitle = spaceReplace(title)
return
}
func buildIndex(html string) (string, string) {
var ret string
m := make(map[string]int)
hn := reIndex.FindAllStringSubmatch(html, -1)
html = reHeader.ReplaceAllString(html, `${1} id="${2}">${2}${3}`)
html = reLink.ReplaceAllStringFunc(html, spaceReplace)
for k, v := range hn {
link := makeLinkTitle(v[2])
link, _ := makeLinkTitle(v[2])
i := m[link]
m[link] = i + 1
newLink := link + strconv.Itoa(i)
html = strings.Replace(html, link+`"`, newLink+`"`, 1)
ret += before(hn, k)
ret += `<li><a href="#` + link + `">` + v[2] + "</a>"
ret += `<li><a href="#` + newLink + `">` + v[2] + "</a>"
ret += after(hn, k)
}
return
return ret, html
}
func before(arr [][]string, i int) string {

124
routes.go
View file

@ -2,134 +2,126 @@
package main
import (
"net/http"
"github.com/gorilla/mux"
)
type route struct {
Path string
Handler http.HandlerFunc
Handler ctxHandler
Methods []string
}
func buildRoutes() http.Handler {
m := mux.NewRouter()
m.NotFoundHandler = notFoundHandler{}
for _, v := range routes {
m.HandleFunc(v.Path, v.Handler).Methods(v.Methods...)
}
return m
}
var routes = []route{
{
"/",
contextWrapper(
indexHandler),
indexHandler,
[]string{"GET"},
},
{
"/bootstrap.css",
contextWrapper(
staticHandler),
staticHandler,
[]string{"GET"},
},
{
"/custom.css",
contextWrapper(
staticHandler),
staticHandler,
[]string{"GET"},
},
{
"/login",
contextWrapper(
loginHandler),
loginHandler,
[]string{"GET", "POST"},
},
{
"/search",
contextWrapper(
searchHandler),
searchHandler,
[]string{"GET", "POST"},
},
{
"/new",
contextWrapper(
authHandler(
wikiNewHandler),
[]string{"GET", "POST"},
},
{
"/all",
contextWrapper(
allHandler),
allHandler,
[]string{"GET"},
},
{
"/wiki/{title:[a-zA-Z0-9+]+$}",
contextWrapper(
"/wiki/{title:[a-zA-Z0-9+-]+$}",
wikiHandler,
[]string{"GET"},
},
{
"/wiki/{title:[a-zA-Z0-9+-]+}/md",
wikiMDHandler(
wikiHandler),
[]string{"GET"},
},
{
"/wiki/{title:[a-zA-Z0-9+]+}/md",
contextWrapper(
wikiMDHandler(
wikiHandler)),
[]string{"GET"},
},
{
"/wiki/{title:[a-zA-Z0-9+]+}/edit",
contextWrapper(
"/wiki/{title:[a-zA-Z0-9+-]+}/edit",
authHandler(
wikiEditHandler),
[]string{"GET", "POST"},
},
{
"/wiki/{title:[a-zA-Z0-9+]+}/del",
contextWrapper(
"/wiki/{title:[a-zA-Z0-9+-]+}/del",
authHandler(
wikiDelHandler),
[]string{"POST"},
},
{
"/users",
contextWrapper(
adminAuthHandler(
usersHandler)),
adminAuthHandler(
usersHandler),
[]string{"GET"},
},
{
"/user/new",
contextWrapper(
adminAuthHandler(
userNewHandler)),
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+$}",
wikiHandler,
[]string{"GET"},
},
{
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/md",
wikiMDHandler(
wikiHandler),
[]string{"GET"},
},
{
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/edit",
wikiEditHandler,
[]string{"GET", "POST"},
},
{
"/user/del/{user:[a-z]+$}",
contextWrapper(
adminAuthHandler(
userDelHandler)),
[]string{"GET", "POST"},
},
{
"/user/unlock/{user:[a-z]+$}",
contextWrapper(
adminAuthHandler(
userUnlockHandler)),
"/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/del",
wikiDelHandler,
[]string{"POST"},
},
{
"/user/edit/{user:[a-z]+$}",
contextWrapper(
userAuthHandler(
userEditHandler)),
"/user/new",
adminAuthHandler(
userNewHandler),
[]string{"GET", "POST"},
},
{
"/user/del/{user:[a-zA-Z0-9]+$}",
adminAuthHandler(
userDelHandler),
[]string{"GET", "POST"},
},
{
"/user/unlock/{user:[a-zA-Z0-9]+$}",
adminAuthHandler(
userUnlockHandler),
[]string{"POST"},
},
{
"/user/edit/{user:[a-zA-Z0-9]+$}",
userAuthHandler(
userEditHandler),
[]string{"GET", "POST"},
},
{
"/logout",
contextWrapper(
logoutHandler),
logoutHandler,
[]string{"GET", "POST"},
},
}

0
scripts/gowiki_init Executable file → Normal file
View file

118
server.go Normal file
View file

@ -0,0 +1,118 @@
package main
import (
"context"
"crypto/rand"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"html/template"
"io"
"net"
"net/http"
"time"
)
const (
keyLen = 32
authLen = keyLen * 2
cookieName = "gosession"
)
type HTTPServer struct {
Version string
SecCookie bool
Store *sessions.CookieStore
Editors *EditorStore
BS *BoltStore
Index *Index
listener net.Listener
srv *http.Server
templ map[string]*template.Template
res map[string][]byte
}
func NewHTTPServer(l net.Listener, versin string, secCookie bool) (srv *HTTPServer) {
srv = &HTTPServer{
Version: version,
SecCookie: secCookie,
listener: l,
}
srv.Editors = NewEditorStore()
srv.cookieStore()
srv.loadTemplates()
srv.srv = &http.Server{
Handler: srv.buildRoutes(),
}
return
}
func (s *HTTPServer) Start() (err error) {
s.BS, err = NewBoltStore()
if err != nil {
return
}
s.Index, err = NewIndex(blevePath)
if err != nil {
return
}
go func() {
log.Println(s.srv.Serve(s.listener))
}()
return
}
func (s *HTTPServer) Stop() error {
s.BS.Close()
s.Index.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
return s.srv.Shutdown(ctx)
}
func (s *HTTPServer) buildRoutes() http.Handler {
m := mux.NewRouter()
m.NotFoundHandler = &notFoundHandler{s}
for _, v := range routes {
m.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
}
return m
}
func (s *HTTPServer) contextWrapper(h ctxHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h(newContext(w, r, s))
}
}
func (s *HTTPServer) cookieStore() {
options := &sessions.Options{
Path: "/",
MaxAge: 3600 * 12,
Secure: s.SecCookie,
HttpOnly: true,
}
authKey := genKey(authLen)
encKey := genKey(keyLen)
s.Store = sessions.NewCookieStore(authKey, encKey)
s.Store.Options = options
go func() {
for {
time.Sleep(time.Hour * 12)
newAuthKey := genKey(authLen)
newEncKey := genKey(keyLen)
newStore := sessions.NewCookieStore(newAuthKey, newEncKey, authKey, encKey)
newStore.Options = options
s.Store = newStore
authKey = newAuthKey
encKey = newEncKey
}
}()
}
func genKey(length int) (bytes []byte) {
bytes = make([]byte, length)
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
log.Println("genKey:", err)
}
return
}

148
snippet.go Normal file
View file

@ -0,0 +1,148 @@
package main
import (
"bytes"
"github.com/boltdb/bolt"
"html/template"
"strings"
)
type Snippet struct {
Title string `json:"title"`
LinkTitle string `json:"-"`
Index template.HTML `json:"index"`
Text template.HTML `json:"text"`
MD string `json:"md"`
Created string `json:"created"`
Updated string `json:"updated"`
Permission int `json:"permission"`
}
func copyBuf(buf []byte) (b []byte) {
b = make([]byte, len(buf))
copy(b, buf)
return
}
var (
errSnippetNotExists = dbErr("snippet does not exist")
errSnippetExists = dbErr("snippet already exist")
errInvalidPermission = dbErr("invalid permission")
)
func (bs *BoltStore) GetSnippets(username string) (snip []Snippet, err error) {
user := []byte(username)
var buf [][]byte
err = bs.db.View(func(tx *bolt.Tx) error {
return tx.Bucket([]byte(snippetTable)).ForEach(func(k, v []byte) error {
if !bytes.HasPrefix(k, user) {
return nil
}
buf = append(buf, copyBuf(v))
return nil
})
})
if err != nil {
return
}
for _, b := range buf {
s := new(Snippet)
if err = bs.Unmarshal(b, s); err != nil {
return
}
snip = append(snip, *s)
}
return
}
func (bs *BoltStore) GetSnippet(username, title string, perm int) (*Snippet, error) {
s := new(Snippet)
err := bs.db.View(func(tx *bolt.Tx) error {
v := tx.Bucket([]byte(snippetTable)).Get([]byte(title))
if v == nil {
return errSnippetNotExists
}
return bs.Unmarshal(v, s)
})
if err != nil {
return nil, err
}
switch s.Permission {
case 0:
return s, nil
case 1:
if username != "" {
return s, nil
}
case 2:
if strings.HasPrefix(title, username) {
return s, nil
}
}
return nil, errSnippetNotExists
}
func (bs *BoltStore) snippetExists(username, title string, perm int) (err error) {
_, err = bs.GetSnippet(username, title, perm)
return
}
func makeKey(username, title string) string {
return username + title
}
func checkPerm(p int) error {
switch p {
case 0, 1, 2:
return nil
}
return errInvalidPermission
}
func (bs *BoltStore) CreateSnippet(username, title, text string, perm int) (newTitle string, err error) {
newTitle, title = makeLinkTitle(title)
err = checkPerm(perm)
if err != nil {
return
}
err = bs.snippetExists(username, title, perm)
if err == nil {
err = errSnippetExists
return
}
err = bs.db.Update(func(tx *bolt.Tx) error {
snip := render(text).Snippet()
snip.Title = title
snip.Created = username + " " + now()
snip.Permission = perm
v, err := bs.Marshal(snip)
if err != nil {
return err
}
return tx.Bucket([]byte(snippetTable)).Put([]byte(makeKey(username, title)), v)
})
return
}
func (bs *BoltStore) EditSnippet(username, title, text string, perm int) (err error) {
err = checkPerm(perm)
if err != nil {
return
}
err = bs.snippetExists(username, title, perm)
if err != nil {
return
}
err = bs.db.Update(func(tx *bolt.Tx) error {
snip := render(text).Snippet()
snip.Title = title
snip.Created = username + " " + now()
snip.Permission = perm
v, err := bs.Marshal(snip)
if err != nil {
return err
}
return tx.Bucket([]byte(snippetTable)).Put([]byte(makeKey(username, title)), v)
})
return
}

155
snippet_handler.go Normal file
View file

@ -0,0 +1,155 @@
package main
func allSnippetHandler(ctx *Context) {
ctx.Template("allSnippetHandler")
ctx.Data = webData{Title: "All Snippets"}
all, err := ctx.Srv.BS.GetSnippets(ctx.GetUser())
if err != nil {
ctx.Data.Msg = err.Error()
}
ctx.Data.Data = all
ctx.Exec()
}
func snippetNewHandler(ctx *Context) {
ctx.Template("snippetNewHandler")
ctx.Data = webData{Title: "New Snippet"}
switch ctx.Method() {
case "GET":
ctx.Exec()
case "POST":
user := ctx.GetUser()
title := ctx.Form("title")
text := ctx.Form("text")
perm := ctx.Form("permission")
submit := ctx.Form("submit")
snip := Snippet{
Title: title,
MD: text,
}
ctx.Data.Data = art
if !ctx.CheckToken() {
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
return
}
iperm, err := strconv.Atoi(perm)
if err != nil || submit != "Submit" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
newTitle, err := ctx.Srv.BS.CreateSnippet(user, title, text, iperm)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Redirect("/users/"+user+"/"+newTitle, 302)
}
}
func wikiHandler(ctx *Context) {
if ctx.T == nil {
ctx.Template("wikiHandler")
}
ctx.Data = webData{
Admin: ctx.GetAdmin(),
}
title := ctx.Var("title")
art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title)
if err != nil {
ctx.NotFound()
return
}
art.LinkTitle = title
ctx.Data.Data = art
ctx.Data.Title = art.Title
ctx.Data.BodyTitle = art.Title
ctx.Exec()
}
func wikiMDHandler(f ctxHandler) ctxHandler {
return func(ctx *Context) {
ctx.Template("wikiMDHandler")
f(ctx)
}
}
func wikiEditHandler(ctx *Context) {
ctx.Template("wikiEditHandler")
ctx.Data = webData{}
title := ctx.Var("title")
switch ctx.Method() {
case "GET":
art, err := ctx.Srv.Index.Get(!ctx.LoggedOn(), title)
if err != nil {
ctx.NotFound()
return
}
art.LinkTitle = title
ctx.Data.Data = art
ctx.Data.Title = art.Title
ctx.Data.BodyTitle = art.Title
ctx.Data.Editor = ctx.Srv.Editors.Set(title, ctx.GetUser())
ctx.Exec()
case "POST":
ftitle := ctx.Form("title")
text := ctx.Form("text")
public := ctx.Form("public")
pub := false
if public == "0" {
pub = true
}
submit := ctx.Form("submit")
ctx.Data.Data = Article{
Title: ftitle,
LinkTitle: title,
MD: text,
Public: pub,
}
ctx.Data.Title = ftitle
ctx.Data.BodyTitle = ftitle
if !ctx.CheckToken() {
ctx.Data.Msg = "wrong csrf token"
ctx.Exec()
return
}
if submit == "Back" {
ctx.Srv.Editors.RemoveEditor(title, ctx.GetUser())
ctx.Redirect("/wiki/"+title, 302)
return
}
if submit != "Submit" {
ctx.Data.Msg = "wrong inputs"
ctx.Exec()
return
}
err := ctx.Srv.Index.Update(title, text, ctx.GetUser(), pub)
if err != nil {
ctx.Data.Msg = err.Error()
ctx.Exec()
return
}
ctx.Srv.Editors.Remove(title)
ctx.Redirect("/wiki/"+title, 302)
}
}
func wikiDelHandler(ctx *Context) {
title := ctx.Var("title")
submit := ctx.Form("submit")
if !ctx.CheckToken() {
ctx.Redirect("/wiki/"+title, 302)
return
}
if submit != "Delete" {
ctx.Redirect("/wiki/"+title, 302)
return
}
if err := ctx.Srv.Index.Delete(title); err != nil {
log.Println("delete:", err)
ctx.Redirect("/wiki/"+title, 302)
}
ctx.Redirect("/wiki/Index", 302)
}

View file

@ -31,10 +31,10 @@ const (
<html>
<head>
<title>GoWiki | {{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" mdeia="screen" integrity="sha256-KC5lAzGRWwscU0sTmXtd8ka5mt7Vk8a0L5JqOhwA28s="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" integrity="sha256-uAIARpL4VhLyhfuh5bS4YWjdcAvN+KjjTli9S8Ud8O4="></link>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-KC5lAzGRWwscU0sTmXtd8ka5mt7Vk8a0L5JqOhwA28s="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-gOyQhDAZKjv7dAP8YCFkpN+Hgg7JAfK8pZsZz4+bqXA="></link>
</head>
<body style="padding-top:50px">
<body>
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
@ -46,13 +46,32 @@ const (
</div>
</div>
<div class="container">
{{if .Msg}}
<div class="alert alert-danger" style="margin-top:20px">
<h4>Error!</h4>
<p>{{.Msg}}</p>
</div>
{{end}}
{{template "body" .}}
<div class="content">
<div class="wrapper">
<div id="back-to-top" class="proper-content">
{{if .Msg}}
<div class="alert alert-danger" style="margin-top:30px">
<h4>Error!</h4>
<p>{{.Msg}}</p>
</div>
{{end}}
{{template "body" .}}
</div>
<div class="push"></div>
</div>
<div class="footer-wrapper">
<footer>
<div class="row">
<div class="col-lg-12">
<ul class="list-unstyled">
<li class="pull-right"><a href="#back-to-top">Back to top</a></li>
<li>© 2017 GoWiki {{.Version}} Request: <strong>{{.Time}}ms</strong></li>
</ul>
</div>
</div>
</footer>
</div>
</div>
</div>
</body>
</html>`
@ -94,22 +113,35 @@ const (
</div>
{{end}}`
menu = `{{define "menu"}}
{{if .Login}}
<ul class="nav navbar-nav">
<li><a href="/wiki/Index">
Index
</a></li>
<li><a href="/search">
Search
</a></li>
<li><a href="/all">
All
All Articles
</a></li>
{{if .Login}}
<li><a href="/new">
New
New Article
</a></li>
{{end}}
<li><a href="/snippets">
Snippets
</a></li>
</ul>
<form class="navbar-form navbar-left menu-search-bar" role="search" method="post" action="/search">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control input-sm menu-search-input" placeholder="Search" name="search" value="{{.Search}}">
<span class="input-group-btn">
<button class="btn btn-sm btn-info" type="submit" name="submit" value="Search">Search</button>
</span>
</div>
</div>
</form>
<ul class="nav navbar-nav navbar-right">
{{if .Login}}
{{if .Admin}}
<li><a href="/users">
Users
@ -122,25 +154,12 @@ const (
<li><a href="/logout">
Logout
</a></li>
</ul>
{{else}}
<ul class="nav navbar-nav">
<li><a href="/wiki/Index">
Index
</a></li>
<li><a href="/search">
Search
</a></li>
<li><a href="/all">
All
</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/login">
Login
</a></li>
</ul>
{{end}}
</ul>
{{end}}`
notFound = `{{define "body"}}
<div class="page-header">
@ -170,11 +189,13 @@ const (
<input type="hidden" name="token" value="{{.Token}}">
<fieldset>
<div class="form-group">
<div class="col-lg-8">
<input type="text" class="form-control input-sm" placeholder="Search" name="search" value="{{.Search}}" autofocus>
</div>
<div class="col-lg-4">
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Search">Search</button>
<div class="col-xs-8">
<div class="input-group">
<input type="text" class="form-control input-sm" placeholder="Search" name="search" value="{{.Search}}" autofocus>
<span class="input-group-btn">
<button class="btn btn-sm btn-info" type="submit" name="submit" value="Search">Search</button>
</span>
</div>
</div>
</div>
</fieldset>
@ -366,6 +387,12 @@ const (
</div>
{{end}}`
wikiEdit = `{{define "body"}}
{{if .Editor}}
<div class="alert alert-warning" style="margin-top:30px">
<h4>Warning!</h4>
<p><strong>{{.Editor.Username}}</strong> is editing this page since {{.Editor.Time}}</p>
</div>
{{end}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
@ -400,7 +427,7 @@ const (
<div class="form-group">
<div class="col-xs-11 col-xs-offset-1">
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Submit">Submit</button>
<a href="/wiki/{{.Data.LinkTitle}}" class="btn btn-sm btn-default">Back</a>
<button class="btn btn-sm btn-default" type="submit" name="submit" value="Back">Back</button>
</div>
</div>
</fieldset>
@ -541,7 +568,7 @@ a.para {
h1,h2,h3,h4,h5,h6 {
font-weight: bold;
padding-top: 60px !important;
margin-top: -50px !important;
margin-top: -55px !important;
-webkit-background-clip: content-box !important;
background-clip: content-box !important;
}
@ -590,18 +617,43 @@ code {
textarea,input {
color: #e2e2e2 !important;
background-color: #444444 !important;
}
.menu-search-bar {
padding-top: 4px;
}
.menu-search-input {
background-color: #2f2f2f !important;
}
html, body, .container, .content {
height: 100% !important;
}
.container, .content {
position: relative;
}
.proper-content {
padding-top: 40px;
}
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -60px;
}
.push {
height: 60px;
}
.footer-wrapper {
position: relative;
padding-top: 20px;
height: 50px;
color: #888888;
}`
)
var (
templ map[string]*template.Template
res map[string][]byte
)
func loadTemplates() {
func (s *HTTPServer) loadTemplates() {
var errf bool
templ = make(map[string]*template.Template)
res = make(map[string][]byte)
s.templ = make(map[string]*template.Template)
s.res = make(map[string][]byte)
parse := func(html ...string) (temp *template.Template) {
var err error
temp = template.New("")
@ -617,23 +669,23 @@ func loadTemplates() {
}
// resources
res["bootstrap.css"] = []byte(bootstrapCSS)
res["custom.css"] = []byte(customCSS)
s.res["bootstrap.css"] = []byte(bootstrapCSS)
s.res["custom.css"] = []byte(customCSS)
// pages.go
templ["notFoundHandler"] = parse(index, menu, notFound)
templ["loginHandler"] = parse(index, menu, login)
templ["searchHandler"] = parse(index, menu, search)
templ["allHandler"] = parse(index, menu, all)
templ["wikiNewHandler"] = parse(index, menu, wikiNew)
templ["wikiEditHandler"] = parse(index, menu, wikiEdit)
templ["wikiHandler"] = parse(index, menu, wiki)
templ["wikiMDHandler"] = parse(index, menu, wikiMD)
s.templ["notFoundHandler"] = parse(index, menu, notFound)
s.templ["loginHandler"] = parse(index, menu, login)
s.templ["searchHandler"] = parse(index, menu, search)
s.templ["allHandler"] = parse(index, menu, all)
s.templ["wikiNewHandler"] = parse(index, menu, wikiNew)
s.templ["wikiEditHandler"] = parse(index, menu, wikiEdit)
s.templ["wikiHandler"] = parse(index, menu, wiki)
s.templ["wikiMDHandler"] = parse(index, menu, wikiMD)
// users.go
templ["usersHandler"] = parse(index, menu, users)
templ["userNewHandler"] = parse(index, menu, userNew)
templ["userEditHandler"] = parse(index, menu, userEdit)
templ["userDelHandler"] = parse(index, menu, userDel)
s.templ["usersHandler"] = parse(index, menu, users)
s.templ["userNewHandler"] = parse(index, menu, userNew)
s.templ["userEditHandler"] = parse(index, menu, userEdit)
s.templ["userDelHandler"] = parse(index, menu, userDel)
if errf {
log.Fatal("parse: ", errors.New("parsing templates failed"))

View file

@ -45,15 +45,10 @@ done
cat << EOF >> ${templates_go}
)
var (
templ map[string]*template.Template
res map[string][]byte
)
func loadTemplates() {
func (s *HTTPServer) loadTemplates() {
var errf bool
templ = make(map[string]*template.Template)
res = make(map[string][]byte)
s.templ = make(map[string]*template.Template)
s.res = make(map[string][]byte)
parse := func(html ...string) (temp *template.Template) {
var err error
temp = template.New("")
@ -69,23 +64,23 @@ func loadTemplates() {
}
// resources
res["bootstrap.css"] = []byte(bootstrapCSS)
res["custom.css"] = []byte(customCSS)
s.res["bootstrap.css"] = []byte(bootstrapCSS)
s.res["custom.css"] = []byte(customCSS)
// pages.go
templ["notFoundHandler"] = parse(index, menu, notFound)
templ["loginHandler"] = parse(index, menu, login)
templ["searchHandler"] = parse(index, menu, search)
templ["allHandler"] = parse(index, menu, all)
templ["wikiNewHandler"] = parse(index, menu, wikiNew)
templ["wikiEditHandler"] = parse(index, menu, wikiEdit)
templ["wikiHandler"] = parse(index, menu, wiki)
templ["wikiMDHandler"] = parse(index, menu, wikiMD)
s.templ["notFoundHandler"] = parse(index, menu, notFound)
s.templ["loginHandler"] = parse(index, menu, login)
s.templ["searchHandler"] = parse(index, menu, search)
s.templ["allHandler"] = parse(index, menu, all)
s.templ["wikiNewHandler"] = parse(index, menu, wikiNew)
s.templ["wikiEditHandler"] = parse(index, menu, wikiEdit)
s.templ["wikiHandler"] = parse(index, menu, wiki)
s.templ["wikiMDHandler"] = parse(index, menu, wikiMD)
// users.go
templ["usersHandler"] = parse(index, menu, users)
templ["userNewHandler"] = parse(index, menu, userNew)
templ["userEditHandler"] = parse(index, menu, userEdit)
templ["userDelHandler"] = parse(index, menu, userDel)
s.templ["usersHandler"] = parse(index, menu, users)
s.templ["userNewHandler"] = parse(index, menu, userNew)
s.templ["userEditHandler"] = parse(index, menu, userEdit)
s.templ["userDelHandler"] = parse(index, menu, userDel)
if errf {
log.Fatal("parse: ", errors.New("parsing templates failed"))

View file

@ -38,7 +38,7 @@ a.para {
h1,h2,h3,h4,h5,h6 {
font-weight: bold;
padding-top: 60px !important;
margin-top: -50px !important;
margin-top: -55px !important;
-webkit-background-clip: content-box !important;
background-clip: content-box !important;
}
@ -87,4 +87,34 @@ code {
textarea,input {
color: #e2e2e2 !important;
background-color: #444444 !important;
}
.menu-search-bar {
padding-top: 4px;
}
.menu-search-input {
background-color: #2f2f2f !important;
}
html, body, .container, .content {
height: 100% !important;
}
.container, .content {
position: relative;
}
.proper-content {
padding-top: 40px;
}
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -60px;
}
.push {
height: 60px;
}
.footer-wrapper {
position: relative;
padding-top: 20px;
height: 50px;
color: #888888;
}

View file

@ -2,10 +2,10 @@
<html>
<head>
<title>GoWiki | {{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" mdeia="screen" integrity="sha256-KC5lAzGRWwscU0sTmXtd8ka5mt7Vk8a0L5JqOhwA28s="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" integrity="sha256-uAIARpL4VhLyhfuh5bS4YWjdcAvN+KjjTli9S8Ud8O4="></link>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-KC5lAzGRWwscU0sTmXtd8ka5mt7Vk8a0L5JqOhwA28s="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-gOyQhDAZKjv7dAP8YCFkpN+Hgg7JAfK8pZsZz4+bqXA="></link>
</head>
<body style="padding-top:50px">
<body>
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
@ -17,13 +17,32 @@
</div>
</div>
<div class="container">
{{if .Msg}}
<div class="alert alert-danger" style="margin-top:20px">
<h4>Error!</h4>
<p>{{.Msg}}</p>
</div>
{{end}}
{{template "body" .}}
<div class="content">
<div class="wrapper">
<div id="back-to-top" class="proper-content">
{{if .Msg}}
<div class="alert alert-danger" style="margin-top:30px">
<h4>Error!</h4>
<p>{{.Msg}}</p>
</div>
{{end}}
{{template "body" .}}
</div>
<div class="push"></div>
</div>
<div class="footer-wrapper">
<footer>
<div class="row">
<div class="col-lg-12">
<ul class="list-unstyled">
<li class="pull-right"><a href="#back-to-top">Back to top</a></li>
<li>© 2017 GoWiki {{.Version}} Request: <strong>{{.Time}}ms</strong></li>
</ul>
</div>
</div>
</footer>
</div>
</div>
</div>
</body>
</html>

View file

@ -1,20 +1,33 @@
{{define "menu"}}
{{if .Login}}
<ul class="nav navbar-nav">
<li><a href="/wiki/Index">
Index
</a></li>
<li><a href="/search">
Search
</a></li>
<li><a href="/all">
All
All Articles
</a></li>
{{if .Login}}
<li><a href="/new">
New
New Article
</a></li>
{{end}}
<li><a href="/snippets">
Snippets
</a></li>
</ul>
<form class="navbar-form navbar-left menu-search-bar" role="search" method="post" action="/search">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control input-sm menu-search-input" placeholder="Search" name="search" value="{{.Search}}">
<span class="input-group-btn">
<button class="btn btn-sm btn-info" type="submit" name="submit" value="Search">Search</button>
</span>
</div>
</div>
</form>
<ul class="nav navbar-nav navbar-right">
{{if .Login}}
{{if .Admin}}
<li><a href="/users">
Users
@ -27,23 +40,10 @@
<li><a href="/logout">
Logout
</a></li>
</ul>
{{else}}
<ul class="nav navbar-nav">
<li><a href="/wiki/Index">
Index
</a></li>
<li><a href="/search">
Search
</a></li>
<li><a href="/all">
All
</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/login">
Login
</a></li>
{{end}}
</ul>
{{end}}
{{end}}

View file

@ -9,11 +9,13 @@
<input type="hidden" name="token" value="{{.Token}}">
<fieldset>
<div class="form-group">
<div class="col-lg-8">
<input type="text" class="form-control input-sm" placeholder="Search" name="search" value="{{.Search}}" autofocus>
</div>
<div class="col-lg-4">
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Search">Search</button>
<div class="col-xs-8">
<div class="input-group">
<input type="text" class="form-control input-sm" placeholder="Search" name="search" value="{{.Search}}" autofocus>
<span class="input-group-btn">
<button class="btn btn-sm btn-info" type="submit" name="submit" value="Search">Search</button>
</span>
</div>
</div>
</div>
</fieldset>

View file

@ -1,4 +1,10 @@
{{define "body"}}
{{if .Editor}}
<div class="alert alert-warning" style="margin-top:30px">
<h4>Warning!</h4>
<p><strong>{{.Editor.Username}}</strong> is editing this page since {{.Editor.Time}}</p>
</div>
{{end}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
@ -33,7 +39,7 @@
<div class="form-group">
<div class="col-xs-11 col-xs-offset-1">
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Submit">Submit</button>
<a href="/wiki/{{.Data.LinkTitle}}" class="btn btn-sm btn-default">Back</a>
<button class="btn btn-sm btn-default" type="submit" name="submit" value="Back">Back</button>
</div>
</div>
</fieldset>

156
userdb.go
View file

@ -12,10 +12,10 @@ import (
)
type User struct {
Username string
Password string
Admin bool
Locked int
Username string `json:"username"`
Password string `json:"password"`
Admin bool `json"admin"`
Locked int `json:"locked"`
}
func validatePassword(pw string) (b []byte) {
@ -30,65 +30,69 @@ func validatePassword(pw string) (b []byte) {
}
const (
userTable = "user"
bcryptCost = 13
dbFile = "gowiki.db"
defUser = "admin"
defPassword = "gowiki"
userTable = "user"
snippetTable = "snippet"
bcryptCost = 13
)
var (
ErrNotFound = errors.New("db: key not found")
ErrExists = errors.New("db: key already exists")
errUserNotExists = dbErr("user does not exist")
errUserExists = dbErr("user already exist")
)
func dbErr(i interface{}) error {
return fmt.Errorf("db: %s", i)
}
type BoltStore struct {
Marshaler
db *bolt.DB
}
func NewBoltStore(file string) (*BoltStore, error) {
db, err := bolt.Open(file, 0666, nil)
func NewBoltStore() (bs *BoltStore, err error) {
db, err := bolt.Open(dbFile, 0666, nil)
if err != nil {
return nil, err
return
}
if err = db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucketIfNotExists([]byte([]byte(userTable))); err != nil {
if _, err := tx.CreateBucketIfNotExists([]byte(userTable)); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists([]byte(snippetTable)); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
return
}
bs = &BoltStore{NewGOB(), db}
if !bs.UserExists(defUser) {
err = bs.CreateUser(defUser, defPassword, true)
if err != nil {
return
}
}
bs := &BoltStore{NewGOB(), db}
bs.UnlockUser(defUser)
return bs, nil
return
}
func (bs *BoltStore) Close() error {
return bs.db.Close()
}
func (bs *BoltStore) GetUsers() (us []User, err error) {
err = bs.db.View(func(tx *bolt.Tx) error {
return tx.Bucket([]byte(userTable)).ForEach(func(k, v []byte) error {
u := new(User)
if err := bs.Unmarshal(v, u); err != nil {
return err
}
u.Password = ""
us = append(us, *u)
return nil
})
})
func (bs *BoltStore) GetUsers() (users []User, err error) {
users, err = bs.DumpUsers()
for i := range users {
users[i].Password = ""
}
return
}
func (bs *BoltStore) GetUserWithoutPassword(username string) (u User, err error) {
err = bs.db.View(func(tx *bolt.Tx) error {
v := tx.Bucket([]byte(userTable)).Get([]byte(username))
if v == nil {
return ErrNotFound
}
return bs.Unmarshal(v, &u)
})
u, err = bs.GetUser(username)
u.Password = ""
return
}
@ -97,7 +101,7 @@ func (bs *BoltStore) GetUser(username string) (u User, err error) {
err = bs.db.View(func(tx *bolt.Tx) error {
v := tx.Bucket([]byte(userTable)).Get([]byte(username))
if v == nil {
return ErrNotFound
return errUserNotExists
}
return bs.Unmarshal(v, &u)
})
@ -106,29 +110,71 @@ func (bs *BoltStore) GetUser(username string) (u User, err error) {
func (bs *BoltStore) UserIsAdmin(username string) (admin bool) {
bs.db.View(func(tx *bolt.Tx) error {
u, err := bs.GetUser(username)
if err != nil {
return err
}
u, _ := bs.GetUser(username)
admin = u.Admin
return nil
})
return
}
func (bs *BoltStore) UserExists(username string) (exists bool) {
bs.db.View(func(tx *bolt.Tx) error {
exists = tx.Bucket([]byte(userTable)).Get([]byte(username)) != nil
func (bs *BoltStore) UserExists(username string) error {
return bs.db.View(func(tx *bolt.Tx) error {
if tx.Bucket([]byte(userTable)).Get([]byte(username)) == nil {
return errUserNotExists
}
return nil
})
}
func (bs *BoltStore) DumpUsers() (users []User, err error) {
var buf [][]byte
err = bs.db.View(func(tx *bolt.Tx) error {
return tx.Bucket([]byte(userTable)).ForEach(func(k, v []byte) error {
buf = append(buf, copyBuf(v))
return nil
})
})
if err != nil {
return
}
for _, b := range buf {
u := new(User)
if err = bs.Unmarshal(b, u); err != nil {
return
}
users = append(users, *u)
}
return
}
func (bs *BoltStore) CreateUser(username, password string, admin bool) error {
func (bs *BoltStore) RestoreUsers(users []User) error {
return bs.db.Update(func(tx *bolt.Tx) error {
if bs.UserExists(username) {
return ErrExists
b := tx.Bucket([]byte(userTable))
err := b.ForEach(func(k, v []byte) error {
return b.Delete(k)
})
if err != nil {
return err
}
for _, u := range users {
v, err := bs.Marshal(u)
if err != nil {
return err
}
err = b.Put([]byte(u.Username), v)
if err != nil {
return err
}
}
return nil
})
}
func (bs *BoltStore) CreateUser(username, password string, admin bool) error {
if err := bs.UserExists(username); err == nil {
return errUserExists
}
return bs.db.Update(func(tx *bolt.Tx) error {
hash, err := bcrypt.GenerateFromPassword(validatePassword(password), bcryptCost)
v, err := bs.Marshal(&User{
Username: username,
@ -247,11 +293,11 @@ func (bs *BoltStore) DeleteUser(username string) error {
if username == defUser {
return errors.New("user '" + defUser + "' cannot be deleted")
}
if err := bs.UserExists(username); err != nil {
return err
}
return bs.db.Update(func(tx *bolt.Tx) error {
if bs.UserExists(username) {
return tx.Bucket([]byte(userTable)).Delete([]byte(username))
}
return ErrNotFound
return tx.Bucket([]byte(userTable)).Delete([]byte(username))
})
}
@ -266,13 +312,11 @@ func NewGOB() Marshaler {
return gobMarshaler{}
}
func (gobMarshaler) Marshal(v interface{}) ([]byte, error) {
b := new(bytes.Buffer)
err := gob.NewEncoder(b).Encode(v)
if err != nil {
return nil, err
}
return b.Bytes(), nil
func (gobMarshaler) Marshal(v interface{}) (b []byte, err error) {
buf := new(bytes.Buffer)
err = gob.NewEncoder(buf).Encode(v)
b = buf.Bytes()
return
}
func (gobMarshaler) Unmarshal(data []byte, v interface{}) error {

View file

@ -1 +1,2 @@
example/example
port80
port80and443

View file

@ -1,4 +1,4 @@
Copyright (C) 2016 Marius Schellenberger
Copyright (C) 2017 Marius Schellenberger
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -1,3 +1,5 @@
# godrop - drop privileges
Godrop is a simple library to drop privileges on linux maschines.
See the examples directory on how to use the `Drop` and `MultiDrop` functions.

View file

@ -1,133 +1,87 @@
// Copyright (C) 2017 Marius Schellenberger
// Package godrop provides a simple privileges dropping library
package godrop
import (
"errors"
"io/ioutil"
"fmt"
"net"
"os"
"os/exec"
"regexp"
"strconv"
"os/signal"
"syscall"
)
func readFile(name, file string) (id int, err error) {
id = -1
if name == "" {
return
}
f, err := os.Open(file)
if err != nil {
return
}
b, err := ioutil.ReadAll(f)
if err != nil {
return
}
r := regexp.MustCompile(name + ":.*:(\\d+)")
m := r.FindAllStringSubmatch(string(b), 1)
if len(m) == 1 {
if len(m[0]) == 2 {
return strconv.Atoi(m[0][1])
}
}
return
}
// UID of user
func UID(user string) (int, error) {
return readFile(user, "/etc/passwd")
}
// GID of group
func GID(group string) (int, error) {
return readFile(group, "/etc/group")
func errf(err error) error {
return fmt.Errorf("godrop: %s", err)
}
// Config represents the drop config
type Config struct {
User string
Group string
// User is the user to drop privileges to.
User string
// Group is the group to drop privileges to.
Group string
// Chroot is the directory to chroot into. Leave this emptry for no chroot.
// When compiling without cgo, make sure the chroot directory contains the /etc/passwd and /etc/group files.
Chroot string
// Set to true, to run the process in the foreground.
Foreground bool
}
// Drop will spawn a new process, hand over the listening socket file descriptor and terminate itself after
// Drop will spawn a new process and hand over the listening socket file descriptor
func Drop(c Config, f func() (net.Listener, error)) error {
uid, err := UID(c.User)
if err != nil {
return err
}
gid, err := GID(c.Group)
if err != nil {
return err
}
switch os.Getuid() {
case 0:
ln, err := f()
if err != nil {
return err
}
l, ok := ln.(*net.TCPListener)
if !ok {
return errors.New("godrop: interface conversion failed")
}
f, err := l.File()
if err != nil {
return err
}
cmd := exec.Command(os.Args[0], os.Args[1:]...)
cmd.ExtraFiles = []*os.File{f}
cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: c.Chroot,
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
Setsid: true,
}
if err := cmd.Start(); err != nil {
return err
}
cmd.Process.Release()
os.Exit(0)
case uid:
return nil
}
return errors.New("godrop: droping priviledges failed")
return MultiDrop(c, func() ([]net.Listener, error) {
l, err := f()
return []net.Listener{l}, err
})
}
// MultiDrop will spawn a new process, hand over the all listening sockets and terminate itself after
// MultiDrop will spawn a new process and hand over the all listening sockets
func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
uid, err := UID(c.User)
uid, err := userID(c.User)
if err != nil {
return err
return errf(err)
}
gid, err := GID(c.Group)
if uid == 0 {
return errf(errors.New("unable to drop privileges to uid 0 (root)"))
}
gid, err := groupID(c.Group)
if err != nil {
return err
return errf(err)
}
switch os.Getuid() {
case 0:
cmd := exec.Command(os.Args[0], os.Args[1:]...)
ln, err := f()
if err != nil {
return err
return errf(err)
}
for _, v := range ln {
l, ok := v.(*net.TCPListener)
if !ok {
return errors.New("godrop: interface conversion failed")
for i, v := range ln {
var f *os.File
switch l := v.(type) {
case *net.TCPListener:
f, err = l.File()
l.Close()
case *net.UnixListener:
f, err = l.File()
l.Close()
default:
return errf(fmt.Errorf("index: %d listener is not type of either *net.TCPListener or *net.UnixListener", i))
}
f, err := l.File()
if err != nil {
return err
return errf(fmt.Errorf("index: %d %s", i, err))
}
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
}
if c.Foreground {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: c.Chroot,
Credential: &syscall.Credential{
@ -138,23 +92,35 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
}
if err := cmd.Start(); err != nil {
return err
return errf(err)
}
if c.Foreground {
go func() {
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
sig := <-sigs
cmd.Process.Signal(sig)
}()
_ = cmd.Wait()
os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus)))
}
cmd.Process.Release()
os.Exit(0)
case uid:
return nil
}
return errors.New("godrop: droping priviledges failed")
return errf(errors.New("dropping priviledges failed"))
}
// GetListener returns the listener socket of file descriptor 3
func GetListener() (net.Listener, error) {
return net.FileListener(os.NewFile(3, "[socket]"))
return GetListenerFd(3)
}
// GetListenerFd returns the listener socket of the given file descriptor
func GetListenerFd(fd int) (net.Listener, error) {
return net.FileListener(os.NewFile(uintptr(fd), "[socket]"))
f := os.NewFile(uintptr(fd), "")
defer f.Close()
return net.FileListener(f)
}

View file

@ -0,0 +1,29 @@
// Copyright (C) 2017 Marius Schellenberger
package godrop
import "strconv"
func atoi(a string) (int, error) {
i, err := strconv.Atoi(a)
if err != nil {
return -1, err
}
return i, nil
}
func userID(username string) (uid int, err error) {
id, err := lookupUID(username)
if err != nil {
return -1, err
}
return atoi(id)
}
func groupID(name string) (int, error) {
id, err := lookupGID(name)
if err != nil {
return -1, err
}
return atoi(id)
}

View file

@ -0,0 +1,23 @@
// Copyright (C) 2017 Marius Schellenberger
// +build cgo
package godrop
import "os/user"
func lookupUID(username string) (string, error) {
u, err := user.Lookup(username)
if err != nil {
return "", err
}
return u.Uid, err
}
func lookupGID(name string) (string, error) {
g, err := user.LookupGroup(name)
if err != nil {
return "", err
}
return g.Gid, err
}

View file

@ -0,0 +1,43 @@
// Copyright (C) 2017 Marius Schellenberger
// +build darwin dragonfly freebsd android linux netbsd openbsd solaris
// +build !cgo
package godrop
import (
"io/ioutil"
"os"
"regexp"
)
func readFile(name, file string) (id string, err error) {
id = "-1"
if name == "" {
return
}
f, err := os.Open(file)
if err != nil {
return
}
b, err := ioutil.ReadAll(f)
if err != nil {
return
}
r := regexp.MustCompile(name + `:.*?:(\d+):`)
m := r.FindAllStringSubmatch(string(b), 1)
if len(m) == 1 {
if len(m[0]) == 2 {
return m[0][1], nil
}
}
return
}
func lookupUID(username string) (string, error) {
return readFile(username, "/etc/passwd")
}
func lookupGID(name string) (string, error) {
return readFile(name, "/etc/group")
}

View file

@ -8,10 +8,11 @@ matrix:
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: tip
install:
- go get golang.org/x/tools/cmd/vet
- # Skip
script:
- go get -t -v ./...

View file

@ -1,19 +1,44 @@
mux
gorilla/mux
===
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
http://www.gorillatoolkit.org/pkg/mux
Package `gorilla/mux` implements a request router and dispatcher.
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
their respective handler.
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
* URL hosts and paths can have variables with an optional regular expression.
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
---
* [Install](#install)
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Listing Routes](#listing-routes)
* [Static Files](#static-files)
* [Registered URLs](#registered-urls)
* [Full Example](#full-example)
---
## Install
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
```sh
go get -u github.com/gorilla/mux
```
## Examples
Let's start registering a couple of URL paths and handlers:
@ -41,12 +66,17 @@ r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
```go
vars := mux.Vars(request)
category := vars["category"]
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Category: %v\n", vars["category"])
}
```
And this is all you need to know about the basic usage. More advanced options are explained below.
### Matching Routes
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
```go
@ -118,7 +148,7 @@ Then register routes in the subrouter:
```go
s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
```
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
@ -138,6 +168,73 @@ s.HandleFunc("/{key}/", ProductHandler)
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```
### Listing Routes
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
```go
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
return
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.HandleFunc("/products", handler)
r.HandleFunc("/articles", handler)
r.HandleFunc("/articles/{id}", handler)
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
t, err := route.GetPathTemplate()
if err != nil {
return err
}
fmt.Println(t)
return nil
})
http.Handle("/", r)
}
```
### Static Files
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
request that matches "/static/*". This makes it easy to serve static files with mux:
```go
func main() {
var dir string
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
flag.Parse()
r := mux.NewRouter()
// This will serve files under http://localhost:8000/static/<filename>
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
```
### Registered URLs
Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
@ -219,7 +316,7 @@ package main
import (
"net/http"
"log"
"github.com/gorilla/mux"
)
@ -233,7 +330,7 @@ func main() {
r.HandleFunc("/", YourHandler)
// Bind to a port and pass our router in
http.ListenAndServe(":8000", r)
log.Fatal(http.ListenAndServe(":8000", r))
}
```

26
vendor/github.com/gorilla/mux/context_gorilla.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
// +build !go1.7
package mux
import (
"net/http"
"github.com/gorilla/context"
)
func contextGet(r *http.Request, key interface{}) interface{} {
return context.Get(r, key)
}
func contextSet(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
context.Set(r, key, val)
return r
}
func contextClear(r *http.Request) {
context.Clear(r)
}

24
vendor/github.com/gorilla/mux/context_native.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
// +build go1.7
package mux
import (
"context"
"net/http"
)
func contextGet(r *http.Request, key interface{}) interface{} {
return r.Context().Value(key)
}
func contextSet(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
return r.WithContext(context.WithValue(r.Context(), key, val))
}
func contextClear(r *http.Request) {
return
}

34
vendor/github.com/gorilla/mux/doc.go generated vendored
View file

@ -47,12 +47,21 @@ variable will be anything until the next slash. For example:
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
The names are used to create a map of route variables which can be retrieved
calling mux.Vars():
vars := mux.Vars(request)
category := vars["category"]
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
when capturing groups were present.
And this is all you need to know about the basic usage. More advanced options
are explained below.
@ -136,6 +145,31 @@ the inner routes use it as base for their paths:
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
Note that the path provided to PathPrefix() represents a "wildcard": calling
PathPrefix("/static/").Handler(...) means that the handler will be passed any
request that matches "/static/*". This makes it easy to serve static files with mux:
func main() {
var dir string
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
flag.Parse()
r := mux.NewRouter()
// This will serve files under http://localhost:8000/static/<filename>
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built,

117
vendor/github.com/gorilla/mux/mux.go generated vendored
View file

@ -10,8 +10,7 @@ import (
"net/http"
"path"
"regexp"
"github.com/gorilla/context"
"strings"
)
// NewRouter returns a new router instance.
@ -48,8 +47,14 @@ type Router struct {
namedRoutes map[string]*Route
// See Router.StrictSlash(). This defines the flag for new routes.
strictSlash bool
// If true, do not clear the request context after handling the request
// See Router.SkipClean(). This defines the flag for new routes.
skipClean bool
// If true, do not clear the request context after handling the request.
// This has no effect when go1.7+ is used, since the context is stored
// on the request itself.
KeepContext bool
// see Router.UseEncodedPath(). This defines a flag for all routes.
useEncodedPath bool
}
// Match matches registered routes against the request.
@ -73,32 +78,38 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Clean path to canonical form and redirect.
if p := cleanPath(req.URL.Path); p != req.URL.Path {
if !r.skipClean {
path := req.URL.Path
if r.useEncodedPath {
path = getPath(req)
}
// Clean path to canonical form and redirect.
if p := cleanPath(path); p != path {
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
// http://code.google.com/p/go/issues/detail?id=5252
url := *req.URL
url.Path = p
p = url.String()
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
// http://code.google.com/p/go/issues/detail?id=5252
url := *req.URL
url.Path = p
p = url.String()
w.Header().Set("Location", p)
w.WriteHeader(http.StatusMovedPermanently)
return
w.Header().Set("Location", p)
w.WriteHeader(http.StatusMovedPermanently)
return
}
}
var match RouteMatch
var handler http.Handler
if r.Match(req, &match) {
handler = match.Handler
setVars(req, match.Vars)
setCurrentRoute(req, match.Route)
req = setVars(req, match.Vars)
req = setCurrentRoute(req, match.Route)
}
if handler == nil {
handler = http.NotFoundHandler()
}
if !r.KeepContext {
defer context.Clear(req)
defer contextClear(req)
}
handler.ServeHTTP(w, req)
}
@ -133,6 +144,34 @@ func (r *Router) StrictSlash(value bool) *Router {
return r
}
// SkipClean defines the path cleaning behaviour for new routes. The initial
// value is false. Users should be careful about which routes are not cleaned
//
// When true, if the route path is "/path//to", it will remain with the double
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
//
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
// become /fetch/http/xkcd.com/534
func (r *Router) SkipClean(value bool) *Router {
r.skipClean = value
return r
}
// UseEncodedPath tells the router to match the encoded original path
// to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
// This behavior has the drawback of needing to match routes against
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
// to r.URL.Path will not affect routing when this flag is on and thus may
// induce unintended behavior.
//
// If not called, the router will match the unencoded path to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
func (r *Router) UseEncodedPath() *Router {
r.useEncodedPath = true
return r
}
// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------
@ -170,7 +209,7 @@ func (r *Router) buildVars(m map[string]string) map[string]string {
// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
route := &Route{parent: r, strictSlash: r.strictSlash}
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
r.routes = append(r.routes, route)
return route
}
@ -268,6 +307,9 @@ func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
if err == SkipRouter {
continue
}
if err != nil {
return err
}
for _, sr := range t.matchers {
if h, ok := sr.(*Router); ok {
err := h.walk(walkFn, ancestors)
@ -308,7 +350,7 @@ const (
// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
if rv := context.Get(r, varsKey); rv != nil {
if rv := contextGet(r, varsKey); rv != nil {
return rv.(map[string]string)
}
return nil
@ -320,28 +362,46 @@ func Vars(r *http.Request) map[string]string {
// after the handler returns, unless the KeepContext option is set on the
// Router.
func CurrentRoute(r *http.Request) *Route {
if rv := context.Get(r, routeKey); rv != nil {
if rv := contextGet(r, routeKey); rv != nil {
return rv.(*Route)
}
return nil
}
func setVars(r *http.Request, val interface{}) {
if val != nil {
context.Set(r, varsKey, val)
}
func setVars(r *http.Request, val interface{}) *http.Request {
return contextSet(r, varsKey, val)
}
func setCurrentRoute(r *http.Request, val interface{}) {
if val != nil {
context.Set(r, routeKey, val)
}
func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
return contextSet(r, routeKey, val)
}
// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------
// getPath returns the escaped path if possible; doing what URL.EscapedPath()
// which was added in go1.5 does
func getPath(req *http.Request) string {
if req.RequestURI != "" {
// Extract the path from RequestURI (which is escaped unlike URL.Path)
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
// for < 1.5 server side workaround
// http://localhost/path/here?v=1 -> /path/here
path := req.RequestURI
path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
path = strings.TrimPrefix(path, req.URL.Host)
if i := strings.LastIndex(path, "?"); i > -1 {
path = path[:i]
}
if i := strings.LastIndex(path, "#"); i > -1 {
path = path[:i]
}
return path
}
return req.URL.Path
}
// cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package.
func cleanPath(p string) string {
@ -357,6 +417,7 @@ func cleanPath(p string) string {
if p[len(p)-1] == '/' && np != "/" {
np += "/"
}
return np
}

View file

@ -24,7 +24,7 @@ import (
// Previously we accepted only Python-like identifiers for variable
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
// name and pattern can't be empty, and names can't contain a colon.
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
// Check if it is well-formed.
idxs, errBraces := braceIndices(tpl)
if errBraces != nil {
@ -109,16 +109,24 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
if errCompile != nil {
return nil, errCompile
}
// Check for capturing groups which used to work in older versions
if reg.NumSubexp() != len(idxs)/2 {
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
}
// Done!
return &routeRegexp{
template: template,
matchHost: matchHost,
matchQuery: matchQuery,
strictSlash: strictSlash,
regexp: reg,
reverse: reverse.String(),
varsN: varsN,
varsR: varsR,
template: template,
matchHost: matchHost,
matchQuery: matchQuery,
strictSlash: strictSlash,
useEncodedPath: useEncodedPath,
regexp: reg,
reverse: reverse.String(),
varsN: varsN,
varsR: varsR,
}, nil
}
@ -133,6 +141,9 @@ type routeRegexp struct {
matchQuery bool
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
strictSlash bool
// Determines whether to use encoded path from getPath function or unencoded
// req.URL.Path for path matching
useEncodedPath bool
// Expanded regexp.
regexp *regexp.Regexp
// Reverse template.
@ -149,8 +160,11 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
if r.matchQuery {
return r.matchQueryString(req)
}
return r.regexp.MatchString(req.URL.Path)
path := req.URL.Path
if r.useEncodedPath {
path = getPath(req)
}
return r.regexp.MatchString(path)
}
return r.regexp.MatchString(getHost(req))
@ -253,14 +267,18 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
extractVars(host, matches, v.host.varsN, m.Vars)
}
}
path := req.URL.Path
if r.useEncodedPath {
path = getPath(req)
}
// Store path variables.
if v.path != nil {
matches := v.path.regexp.FindStringSubmatchIndex(req.URL.Path)
matches := v.path.regexp.FindStringSubmatchIndex(path)
if len(matches) > 0 {
extractVars(req.URL.Path, matches, v.path.varsN, m.Vars)
extractVars(path, matches, v.path.varsN, m.Vars)
// Check if we should redirect.
if v.path.strictSlash {
p1 := strings.HasSuffix(req.URL.Path, "/")
p1 := strings.HasSuffix(path, "/")
p2 := strings.HasSuffix(v.path.template, "/")
if p1 != p2 {
u, _ := url.Parse(req.URL.String())
@ -299,14 +317,7 @@ func getHost(r *http.Request) string {
}
func extractVars(input string, matches []int, names []string, output map[string]string) {
matchesCount := 0
prevEnd := -1
for i := 2; i < len(matches) && matchesCount < len(names); i += 2 {
if prevEnd < matches[i+1] {
value := input[matches[i]:matches[i+1]]
output[names[matchesCount]] = value
prevEnd = matches[i+1]
matchesCount++
}
for i, name := range names {
output[name] = input[matches[2*i+2]:matches[2*i+3]]
}
}

View file

@ -26,6 +26,11 @@ type Route struct {
// If true, when the path pattern is "/path/", accessing "/path" will
// redirect to the former and vice versa.
strictSlash bool
// If true, when the path pattern is "/path//to", accessing "/path//to"
// will not redirect
skipClean bool
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
useEncodedPath bool
// If true, this route never matches: it is only used to build URLs.
buildOnly bool
// The name used to build URLs.
@ -36,6 +41,10 @@ type Route struct {
buildVarsFunc BuildVarsFunc
}
func (r *Route) SkipClean() bool {
return r.skipClean
}
// Match matches the route against the request.
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
if r.buildOnly || r.err != nil {
@ -144,14 +153,14 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
}
r.regexp = r.getRegexpGroup()
if !matchHost && !matchQuery {
if len(tpl) == 0 || tpl[0] != '/' {
if tpl == "/" && (len(tpl) == 0 || tpl[0] != '/') {
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
}
if r.regexp.path != nil {
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
}
}
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
if err != nil {
return err
}

View file

@ -1,8 +1,22 @@
language: go
sudo: false
go:
- 1.3
- 1.4
- 1.5
- tip
matrix:
include:
- go: 1.3
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: tip
allow_failures:
- go: tip
install:
- # skip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

View file

@ -40,33 +40,40 @@ Let's start with an example that shows the sessions API in a nutshell:
}
```
First we initialize a session store calling NewCookieStore() and passing a
First we initialize a session store calling `NewCookieStore()` and passing a
secret key used to authenticate the session. Inside the handler, we call
store.Get() to retrieve an existing session or a new one. Then we set some
session values in session.Values, which is a map[interface{}]interface{}.
And finally we call session.Save() to save the session in the response.
`store.Get()` to retrieve an existing session or a new one. Then we set some
session values in session.Values, which is a `map[interface{}]interface{}`.
And finally we call `session.Save()` to save the session in the response.
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
with
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
as or else you will leak memory! An easy way to do this is to wrap the top-level
mux when calling http.ListenAndServe:
More examples are available [on the Gorilla
website](http://www.gorillatoolkit.org/pkg/sessions).
## Store Implementations
Other implementations of the sessions.Store interface:
Other implementations of the `sessions.Store` interface:
* [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
* [github.com/hnakamur/gaesessions](https://github.com/hnakamur/gaesessions) - Memcache on GAE
* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
* [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
* [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
* [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
## License
## License
BSD licensed. See the LICENSE file for details.
BSD licensed. See the LICENSE file for details.

View file

@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
/*
Package gorilla/sessions provides cookie and filesystem sessions and
Package sessions provides cookie and filesystem sessions and
infrastructure for custom session backends.
The key features are:
@ -33,7 +33,7 @@ Let's start with an example that shows the sessions API in a nutshell:
// existing session: Get() always returns a session, even if empty.
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@ -76,7 +76,7 @@ flashes, call session.Flashes(). Here is an example:
// Get a session.
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@ -118,10 +118,10 @@ so it is easy to register new datatypes for storage in sessions:
}
As it's not possible to pass a raw type as a parameter to a function, gob.Register()
relies on us passing it an empty pointer to the type as a parameter. In the example
above we've passed it a pointer to a struct and a pointer to a custom type
representing a map[string]interface. This will then allow us to serialise/deserialise
values of those types to and from our sessions.
relies on us passing it a value of the desired type. In the example above we've passed
it a pointer to a struct and a pointer to a custom type representing a
map[string]interface. (We could have passed non-pointer values if we wished.) This will
then allow us to serialise/deserialise values of those types to and from our sessions.
Note that because session values are stored in a map[string]interface{}, there's
a need to type-assert data when retrieving it. We'll use the Person struct we registered above:
@ -129,7 +129,7 @@ a need to type-assert data when retrieving it. We'll use the Person struct we re
func MyHandler(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

102
vendor/github.com/gorilla/sessions/lex.go generated vendored Normal file
View file

@ -0,0 +1,102 @@
// This file contains code adapted from the Go standard library
// https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go
package sessions
import "strings"
var isTokenTable = [127]bool{
'!': true,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'*': true,
'+': true,
'-': true,
'.': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
}
func isToken(r rune) bool {
i := int(r)
return i < len(isTokenTable) && isTokenTable[i]
}
func isNotToken(r rune) bool {
return !isToken(r)
}
func isCookieNameValid(raw string) bool {
if raw == "" {
return false
}
return strings.IndexFunc(raw, isNotToken) < 0
}

View file

@ -45,7 +45,10 @@ func NewSession(store Store, name string) *Session {
// Session stores the values and optional configuration for a session.
type Session struct {
ID string
// The ID of the session, generated by stores. It should not be used for
// user data.
ID string
// Values contains the user-data for the session.
Values map[interface{}]interface{}
Options *Options
IsNew bool
@ -142,6 +145,9 @@ type Registry struct {
//
// It returns a new session if there are no sessions registered for the name.
func (s *Registry) Get(store Store, name string) (session *Session, err error) {
if !isCookieNameValid(name) {
return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
}
if info, ok := s.sessions[name]; ok {
session, err = info.s, info.e
} else {

View file

@ -156,7 +156,7 @@ func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
// FilesystemStore stores sessions in the filesystem.
//
// It also serves as a referece for custom stores.
// It also serves as a reference for custom stores.
//
// This store is still experimental and not well tested. Feedback is welcome.
type FilesystemStore struct {
@ -205,8 +205,22 @@ func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
}
// Save adds a single session to the response.
//
// If the Options.MaxAge of the session is <= 0 then the session file will be
// deleted from the store path. With this process it enforces the properly
// session cookie handling so no need to trust in the cookie management in the
// web browser.
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
session *Session) error {
// Delete if max-age is <= 0
if session.Options.MaxAge <= 0 {
if err := s.erase(session); err != nil {
return err
}
http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
return nil
}
if session.ID == "" {
// Because the ID is used in the filename, encode it to
// use alphanumeric characters only.
@ -268,3 +282,14 @@ func (s *FilesystemStore) load(session *Session) error {
}
return nil
}
// delete session file
func (s *FilesystemStore) erase(session *Session) error {
filename := filepath.Join(s.path, "session_"+session.ID)
fileMutex.RLock()
defer fileMutex.RUnlock()
err := os.Remove(filename)
return err
}

View file

@ -1,30 +1,18 @@
sudo: false
# Travis CI (http://travis-ci.org/) is a continuous integration service for
# open source projects. This file configures it to run unit tests for
# blackfriday.
language: go
go:
- 1.5.4
- 1.6.2
- tip
matrix:
include:
- go: 1.2.2
script:
- go get -t -v ./...
- go test -v -race ./...
- go: 1.3.3
script:
- go get -t -v ./...
- go test -v -race ./...
- go: 1.4.3
script:
- go get -t -v ./...
- go test -v -race ./...
allow_failures:
- go: tip
fast_finish: true
- 1.2
- 1.3
- 1.4
- 1.5
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
- go get -d -t -v ./...
- go build -v ./...
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...
- go test -v ./...

View file

@ -1,4 +1,4 @@
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) [![GoDoc](https://godoc.org/github.com/russross/blackfriday?status.svg)](https://godoc.org/github.com/russross/blackfriday)
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday)
===========
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
@ -114,7 +114,7 @@ All features of Sundown are supported, including:
know and send me the input that does it.
NOTE: "safety" in this context means *runtime safety only*. In order to
protect yourself against JavaScript injection in untrusted content, see
protect yourself agains JavaScript injection in untrusted content, see
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
* **Fast processing**. It is fast enough to render on-demand in
@ -242,8 +242,6 @@ are a few of note:
point. In particular, it does not do any inline escaping, so input
that happens to look like LaTeX code will be passed through without
modification.
* [Md2Vim](https://github.com/FooSoft/md2vim): transforms markdown files into vimdoc format.
Todo

View file

@ -320,11 +320,6 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int {
return size
}
// check for HTML CDATA
if size := p.htmlCDATA(out, data, doRender); size > 0 {
return size
}
// no special case recognized
return 0
}
@ -402,10 +397,12 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int {
return i
}
func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int {
// html block needs to end with a blank line
if i := p.isEmpty(data[start:]); i > 0 {
size := start + i
// HTML comment, lax form
func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int {
i := p.inlineHtmlComment(out, data)
// needs to end with a blank line
if j := p.isEmpty(data[i:]); j > 0 {
size := i + j
if doRender {
// trim trailing newlines
end := size
@ -419,35 +416,6 @@ func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRe
return 0
}
// HTML comment, lax form
func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int {
i := p.inlineHTMLComment(out, data)
return p.renderHTMLBlock(out, data, i, doRender)
}
// HTML CDATA section
func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int {
const cdataTag = "<![cdata["
const cdataTagLen = len(cdataTag)
if len(data) < cdataTagLen+1 {
return 0
}
if !bytes.Equal(bytes.ToLower(data[:cdataTagLen]), []byte(cdataTag)) {
return 0
}
i := cdataTagLen
// scan for an end-of-comment marker, across lines if necessary
for i < len(data) && !(data[i-2] == ']' && data[i-1] == ']' && data[i] == '>') {
i++
}
i++
// no end-of-comment marker
if i >= len(data) {
return 0
}
return p.renderHTMLBlock(out, data, i, doRender)
}
// HR, which is the only self-closing block tag considered
func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int {
if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
@ -464,7 +432,19 @@ func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int {
}
if data[i] == '>' {
return p.renderHTMLBlock(out, data, i+1, doRender)
i++
if j := p.isEmpty(data[i:]); j > 0 {
size := i + j
if doRender {
// trim newlines
end := size
for end > 0 && data[end-1] == '\n' {
end--
}
p.r.BlockHtml(out, data[:end])
}
return size
}
}
return 0
@ -1153,7 +1133,6 @@ gatherlines:
// and move on to the next line
if p.isEmpty(data[line:i]) > 0 {
containsBlankLine = true
raw.Write(data[line:i])
line = i
continue
}
@ -1174,14 +1153,6 @@ gatherlines:
p.dliPrefix(chunk) > 0:
if containsBlankLine {
// end the list if the type changed after a blank line
if indent <= itemIndent &&
((*flags&LIST_TYPE_ORDERED != 0 && p.uliPrefix(chunk) > 0) ||
(*flags&LIST_TYPE_ORDERED == 0 && p.oliPrefix(chunk) > 0)) {
*flags |= LIST_ITEM_END_OF_LIST
break gatherlines
}
*flags |= LIST_ITEM_CONTAINS_BLOCK
}
@ -1229,10 +1200,17 @@ gatherlines:
// a blank line means this should be parsed as a block
case containsBlankLine:
raw.WriteByte('\n')
*flags |= LIST_ITEM_CONTAINS_BLOCK
}
containsBlankLine = false
// if this line was preceeded by one or more blanks,
// re-introduce the blank into the buffer
if containsBlankLine {
containsBlankLine = false
raw.WriteByte('\n')
}
// add the line into the working buffer without prefix
raw.Write(data[line+indent : i])

View file

@ -240,8 +240,6 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
i++
}
brace := 0
// look for the matching closing bracket
for level := 1; level > 0 && i < len(data); i++ {
switch {
@ -275,8 +273,8 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
i++
}
switch {
// inline style link
switch {
case i < len(data) && data[i] == '(':
// skip initial whitespace
i++
@ -287,27 +285,14 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
linkB := i
// look for link end: ' " ), check for new opening braces and take this
// into account, this may lead for overshooting and probably will require
// some fine-tuning.
// look for link end: ' " )
findlinkend:
for i < len(data) {
switch {
case data[i] == '\\':
i += 2
case data[i] == '(':
brace++
i++
case data[i] == ')':
if brace <= 0 {
break findlinkend
}
brace--
i++
case data[i] == '\'' || data[i] == '"':
case data[i] == ')' || data[i] == '\'' || data[i] == '"':
break findlinkend
default:
@ -575,7 +560,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
return i
}
func (p *parser) inlineHTMLComment(out *bytes.Buffer, data []byte) int {
func (p *parser) inlineHtmlComment(out *bytes.Buffer, data []byte) int {
if len(data) < 5 {
return 0
}
@ -599,7 +584,7 @@ func leftAngle(p *parser, out *bytes.Buffer, data []byte, offset int) int {
data = data[offset:]
altype := LINK_TYPE_NOT_AUTOLINK
end := tagLength(data, &altype)
if size := p.inlineHTMLComment(out, data); size > 0 {
if size := p.inlineHtmlComment(out, data); size > 0 {
end = size
}
if end > 2 {

View file

@ -25,7 +25,7 @@ import (
"unicode/utf8"
)
const VERSION = "1.5"
const VERSION = "1.4"
// These are the supported markdown parsing extensions.
// OR these values together to select multiple extensions.
@ -105,46 +105,46 @@ const (
// blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping.
var blockTags = map[string]struct{}{
"blockquote": {},
"del": {},
"div": {},
"dl": {},
"fieldset": {},
"form": {},
"h1": {},
"h2": {},
"h3": {},
"h4": {},
"h5": {},
"h6": {},
"iframe": {},
"ins": {},
"math": {},
"noscript": {},
"ol": {},
"pre": {},
"p": {},
"script": {},
"style": {},
"table": {},
"ul": {},
"blockquote": struct{}{},
"del": struct{}{},
"div": struct{}{},
"dl": struct{}{},
"fieldset": struct{}{},
"form": struct{}{},
"h1": struct{}{},
"h2": struct{}{},
"h3": struct{}{},
"h4": struct{}{},
"h5": struct{}{},
"h6": struct{}{},
"iframe": struct{}{},
"ins": struct{}{},
"math": struct{}{},
"noscript": struct{}{},
"ol": struct{}{},
"pre": struct{}{},
"p": struct{}{},
"script": struct{}{},
"style": struct{}{},
"table": struct{}{},
"ul": struct{}{},
// HTML5
"address": {},
"article": {},
"aside": {},
"canvas": {},
"figcaption": {},
"figure": {},
"footer": {},
"header": {},
"hgroup": {},
"main": {},
"nav": {},
"output": {},
"progress": {},
"section": {},
"video": {},
"address": struct{}{},
"article": struct{}{},
"aside": struct{}{},
"canvas": struct{}{},
"figcaption": struct{}{},
"figure": struct{}{},
"footer": struct{}{},
"header": struct{}{},
"hgroup": struct{}{},
"main": struct{}{},
"nav": struct{}{},
"output": struct{}{},
"progress": struct{}{},
"section": struct{}{},
"video": struct{}{},
}
// Renderer is the rendering interface.
@ -635,12 +635,12 @@ func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffse
i++
}
linkOffset = i
if i == len(data) {
return
}
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
i++
}
if i == len(data) {
return
}
linkEnd = i
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
linkOffset++

2
vendor/github.com/urfave/cli/.gitignore generated vendored Normal file
View file

@ -0,0 +1,2 @@
*.coverprofile
node_modules/

39
vendor/github.com/urfave/cli/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,39 @@
language: go
sudo: false
cache:
directories:
- node_modules
go:
- 1.2.x
- 1.3.x
- 1.4.2
- 1.5.x
- 1.6.x
- 1.7.x
- master
matrix:
allow_failures:
- go: master
include:
- go: 1.6.x
os: osx
- go: 1.7.x
os: osx
before_script:
- go get github.com/urfave/gfmrun/... || true
- go get golang.org/x/tools/... || true
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
npm install markdown-toc ;
fi
script:
- ./runtests gen
- ./runtests vet
- ./runtests test
- ./runtests gfmrun
- ./runtests toc

392
vendor/github.com/urfave/cli/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,392 @@
# Change Log
**ATTN**: This project uses [semantic versioning](http://semver.org/).
## [Unreleased]
## [1.19.1] - 2016-11-21
### Fixed
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
the `Action` for a command would cause it to error rather than calling the
function. Should not have a affected declarative cases using `func(c
*cli.Context) err)`.
- Shell completion now handles the case where the user specifies
`--generate-bash-completion` immediately after a flag that takes an argument.
Previously it call the application with `--generate-bash-completion` as the
flag value.
## [1.19.0] - 2016-11-19
### Added
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
- A `Description` field was added to `App` for a more detailed description of
the application (similar to the existing `Description` field on `Command`)
- Flag type code generation via `go generate`
- Write to stderr and exit 1 if action returns non-nil error
- Added support for TOML to the `altsrc` loader
- `SkipArgReorder` was added to allow users to skip the argument reordering.
This is useful if you want to consider all "flags" after an argument as
arguments rather than flags (the default behavior of the stdlib `flag`
library). This is backported functionality from the [removal of the flag
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
2
- For formatted errors (those implementing `ErrorFormatter`), the errors will
be formatted during output. Compatible with `pkg/errors`.
### Changed
- Raise minimum tested/supported Go version to 1.2+
### Fixed
- Consider empty environment variables as set (previously environment variables
with the equivalent of `""` would be skipped rather than their value used).
- Return an error if the value in a given environment variable cannot be parsed
as the flag type. Previously these errors were silently swallowed.
- Print full error when an invalid flag is specified (which includes the invalid flag)
- `App.Writer` defaults to `stdout` when `nil`
- If no action is specified on a command or app, the help is now printed instead of `panic`ing
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
- Correctly show help message if `-h` is provided to a subcommand
- `context.(Global)IsSet` now respects environment variables. Previously it
would return `false` if a flag was specified in the environment rather than
as an argument
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
as `altsrc` where Go would complain that the types didn't match
## [1.18.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
## [1.18.0] - 2016-06-27
### Added
- `./runtests` test runner with coverage tracking by default
- testing on OS X
- testing on Windows
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code
### Changed
- Use spaces for alignment in help/usage output instead of tabs, making the
output alignment consistent regardless of tab width
### Fixed
- Printing of command aliases in help text
- Printing of visible flags for both struct and struct pointer flags
- Display the `help` subcommand when using `CommandCategories`
- No longer swallows `panic`s that occur within the `Action`s themselves when
detecting the signature of the `Action` field
## [1.17.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.17.0] - 2016-05-09
### Added
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
- Support for hiding commands by setting `Hidden: true` -- this will hide the
commands in help output
### Changed
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
quoted in help text output.
- All flag types now include `(default: {value})` strings following usage when a
default value can be (reasonably) detected.
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
with non-slice flag types
- Apps now exit with a code of 3 if an unknown subcommand is specified
(previously they printed "No help topic for...", but still exited 0. This
makes it easier to script around apps built using `cli` since they can trust
that a 0 exit code indicated a successful execution.
- cleanups based on [Go Report Card
feedback](https://goreportcard.com/report/github.com/urfave/cli)
## [1.16.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.16.0] - 2016-05-02
### Added
- `Hidden` field on all flag struct types to omit from generated help text
### Changed
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
generated help text via the `Hidden` field
### Fixed
- handling of error values in `HandleAction` and `HandleExitCoder`
## [1.15.0] - 2016-04-30
### Added
- This file!
- Support for placeholders in flag usage strings
- `App.Metadata` map for arbitrary data/state management
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
parsing.
- Support for nested lookup of dot-delimited keys in structures loaded from
YAML.
### Changed
- The `App.Action` and `Command.Action` now prefer a return signature of
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
`error` is returned, there may be two outcomes:
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
automatically
- Else the error is bubbled up and returned from `App.Run`
- Specifying an `Action` with the legacy return signature of
`func(*cli.Context)` will produce a deprecation message to stderr
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
from `App.Run`
- Specifying an `Action` func that has an invalid (input) signature will
produce a non-zero exit from `App.Run`
### Deprecated
- <a name="deprecated-cli-app-runandexitonerror"></a>
`cli.App.RunAndExitOnError`, which should now be done by returning an error
that fulfills `cli.ExitCoder` to `cli.App.Run`.
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
### Fixed
- Added missing `*cli.Context.GlobalFloat64` method
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
### Added
- Codebeat badge
- Support for categorization via `CategorizedHelp` and `Categories` on app.
### Changed
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
### Fixed
- Ensure version is not shown in help text when `HideVersion` set.
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
### Added
- YAML file input support.
- `NArg` method on context.
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
### Added
- Custom usage error handling.
- Custom text support in `USAGE` section of help output.
- Improved help messages for empty strings.
- AppVeyor CI configuration.
### Changed
- Removed `panic` from default help printer func.
- De-duping and optimizations.
### Fixed
- Correctly handle `Before`/`After` at command level when no subcommands.
- Case of literal `-` argument causing flag reordering.
- Environment variable hints on Windows.
- Docs updates.
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
### Changed
- Use `path.Base` in `Name` and `HelpName`
- Export `GetName` on flag types.
### Fixed
- Flag parsing when skipping is enabled.
- Test output cleanup.
- Move completion check to account for empty input case.
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
### Added
- Destination scan support for flags.
- Testing against `tip` in Travis CI config.
### Changed
- Go version in Travis CI config.
### Fixed
- Removed redundant tests.
- Use correct example naming in tests.
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
### Fixed
- Remove unused var in bash completion.
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
### Added
- Coverage and reference logos in README.
### Fixed
- Use specified values in help and version parsing.
- Only display app version and help message once.
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
### Added
- More tests for existing functionality.
- `ArgsUsage` at app and command level for help text flexibility.
### Fixed
- Honor `HideHelp` and `HideVersion` in `App.Run`.
- Remove juvenile word from README.
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
### Added
- `FullName` on command with accompanying help output update.
- Set default `$PROG` in bash completion.
### Changed
- Docs formatting.
### Fixed
- Removed self-referential imports in tests.
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
### Added
- Support for `Copyright` at app level.
- `Parent` func at context level to walk up context lineage.
### Fixed
- Global flag processing at top level.
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
### Added
- Aggregate errors from `Before`/`After` funcs.
- Doc comments on flag structs.
- Include non-global flags when checking version and help.
- Travis CI config updates.
### Fixed
- Ensure slice type flags have non-nil values.
- Collect global flags from the full command hierarchy.
- Docs prose.
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
### Changed
- `HelpPrinter` signature includes output writer.
### Fixed
- Specify go 1.1+ in docs.
- Set `Writer` when running command as app.
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
### Added
- Multiple author support.
- `NumFlags` at context level.
- `Aliases` at command level.
### Deprecated
- `ShortName` at command level.
### Fixed
- Subcommand help output.
- Backward compatible support for deprecated `Author` and `Email` fields.
- Docs regarding `Names`/`Aliases`.
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
### Added
- `After` hook func support at app and command level.
### Fixed
- Use parsed context when running command as subcommand.
- Docs prose.
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
### Added
- Support for hiding `-h / --help` flags, but not `help` subcommand.
- Stop flag parsing after `--`.
### Fixed
- Help text for generic flags to specify single value.
- Use double quotes in output for defaults.
- Use `ParseInt` instead of `ParseUint` for int environment var values.
- Use `0` as base when parsing int environment var values.
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
### Added
- Support for environment variable lookup "cascade".
- Support for `Stdout` on app for output redirection.
### Fixed
- Print command help instead of app help in `ShowCommandHelp`.
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
### Added
- Docs and example code updates.
### Changed
- Default `-v / --version` flag made optional.
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
### Added
- `FlagNames` at context level.
- Exposed `VersionPrinter` var for more control over version output.
- Zsh completion hook.
- `AUTHOR` section in default app help template.
- Contribution guidelines.
- `DurationFlag` type.
## [1.2.0] - 2014-08-02
### Added
- Support for environment variable defaults on flags plus tests.
## [1.1.0] - 2014-07-15
### Added
- Bash completion.
- Optional hiding of built-in help command.
- Optional skipping of flag parsing at command level.
- `Author`, `Email`, and `Compiled` metadata on app.
- `Before` hook func support at app and command level.
- `CommandNotFound` func support at app level.
- Command reference available on context.
- `GenericFlag` type.
- `Float64Flag` type.
- `BoolTFlag` type.
- `IsSet` flag helper on context.
- More flag lookup funcs at context level.
- More tests &amp; docs.
### Changed
- Help template updates to account for presence/absence of flags.
- Separated subcommand help template.
- Exposed `HelpPrinter` var for more control over help output.
## [1.0.0] - 2013-11-01
### Added
- `help` flag in default app flag set and each command flag set.
- Custom handling of argument parsing errors.
- Command lookup by name at app level.
- `StringSliceFlag` type and supporting `StringSlice` type.
- `IntSliceFlag` type and supporting `IntSlice` type.
- Slice type flag lookups by name at context level.
- Export of app and command help functions.
- More tests &amp; docs.
## 0.1.0 - 2013-07-22
### Added
- Initial implementation.
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0

21
vendor/github.com/urfave/cli/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Jeremy Saenz & Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1364
vendor/github.com/urfave/cli/README.md generated vendored Normal file

File diff suppressed because it is too large Load diff

492
vendor/github.com/urfave/cli/app.go generated vendored Normal file
View file

@ -0,0 +1,492 @@
package cli
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"time"
)
var (
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
)
// App is the main structure of a cli application. It is recommended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to path.Base(os.Args[0])
Name string
// Full name of command for help, defaults to Name
HelpName string
// Description of the program.
Usage string
// Text to override the USAGE section of help
UsageText string
// Description of the program argument format.
ArgsUsage string
// Version of the program
Version string
// Description of the program
Description string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool
// Populate on app startup, only gettable through method Categories()
categories CommandCategories
// An action to execute when the bash-completion flag is set
BashComplete BashCompleteFunc
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before BeforeFunc
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
// The action to execute when no subcommands are specified
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
// *Note*: support for the deprecated `Action` signature will be removed in a future version
Action interface{}
// Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc
// Execute this function if an usage error occurs
OnUsageError OnUsageErrorFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
// ErrWriter writes error output
ErrWriter io.Writer
// Other custom info
Metadata map[string]interface{}
didSetup bool
}
// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// NewApp creates a new cli Application with some reasonable defaults for Name,
// Usage, Version and Action.
func NewApp() *App {
return &App{
Name: filepath.Base(os.Args[0]),
HelpName: filepath.Base(os.Args[0]),
Usage: "A new cli application",
UsageText: "",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
// Setup runs initialization code to ensure all data structures are ready for
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
// will return early if setup has already happened.
func (a *App) Setup() {
if a.didSetup {
return
}
a.didSetup = true
if a.Author != "" || a.Email != "" {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
a.categories = CommandCategories{}
for _, command := range a.Commands {
a.categories = a.categories.AddCommand(command.Category, command)
}
sort.Sort(a.categories)
if a.Metadata == nil {
a.Metadata = make(map[string]interface{})
}
if a.Writer == nil {
a.Writer = os.Stdout
}
}
// Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
a.Setup()
// handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
// parse flags
set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
ShowAppHelp(context)
return nerr
}
context.shellComplete = shellComplete
if checkCompletions(context) {
return nil
}
if err != nil {
if a.OnUsageError != nil {
err := a.OnUsageError(context, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context)
return err
}
if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context)
return nil
}
if !a.HideVersion && checkVersion(context) {
ShowVersion(context)
return nil
}
if a.After != nil {
defer func() {
if afterErr := a.After(context); afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
beforeErr := a.Before(context)
if beforeErr != nil {
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
ShowAppHelp(context)
HandleExitCoder(beforeErr)
err = beforeErr
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
if a.Action == nil {
a.Action = helpCommand.Action
}
// Run default Action
err = HandleAction(a.Action, context)
HandleExitCoder(err)
return err
}
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
//
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
// to cli.App.Run. This will cause the application to exit with the given eror
// code in the cli.ExitCoder
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(a.errWriter(), err)
OsExiter(1)
}
}
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
// generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// parse flags
set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
}
return nerr
}
if checkCompletions(context) {
return nil
}
if err != nil {
if a.OnUsageError != nil {
err = a.OnUsageError(context, err, true)
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowSubcommandHelp(context)
return err
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(context) {
return nil
}
} else {
if checkCommandHelp(ctx, context.Args().First()) {
return nil
}
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
beforeErr := a.Before(context)
if beforeErr != nil {
HandleExitCoder(beforeErr)
err = beforeErr
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
err = HandleAction(a.Action, context)
HandleExitCoder(err)
return err
}
// Command returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
}
}
return nil
}
// Categories returns a slice containing all the categories with the commands they contain
func (a *App) Categories() CommandCategories {
return a.categories
}
// VisibleCategories returns a slice of categories and commands that are
// Hidden=false
func (a *App) VisibleCategories() []*CommandCategory {
ret := []*CommandCategory{}
for _, category := range a.categories {
if visible := func() *CommandCategory {
for _, command := range category.Commands {
if !command.Hidden {
return category
}
}
return nil
}(); visible != nil {
ret = append(ret, visible)
}
}
return ret
}
// VisibleCommands returns a slice of the Commands with Hidden=false
func (a *App) VisibleCommands() []Command {
ret := []Command{}
for _, command := range a.Commands {
if !command.Hidden {
ret = append(ret, command)
}
}
return ret
}
// VisibleFlags returns a slice of the Flags with Hidden=false
func (a *App) VisibleFlags() []Flag {
return visibleFlags(a.Flags)
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
return true
}
}
return false
}
func (a *App) errWriter() io.Writer {
// When the app ErrWriter is nil use the package level one.
if a.ErrWriter == nil {
return ErrWriter
}
return a.ErrWriter
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = " <" + a.Email + ">"
}
return fmt.Sprintf("%v%v", a.Name, e)
}
// HandleAction attempts to figure out which Action signature was used. If
// it's an ActionFunc or a func with the legacy signature for Action, the func
// is run!
func HandleAction(action interface{}, context *Context) (err error) {
if a, ok := action.(ActionFunc); ok {
return a(context)
} else if a, ok := action.(func(*Context) error); ok {
return a(context)
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
a(context)
return nil
} else {
return errInvalidActionType
}
}

24
vendor/github.com/urfave/cli/appveyor.yml generated vendored Normal file
View file

@ -0,0 +1,24 @@
version: "{build}"
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\urfave\cli
environment:
GOPATH: C:\gopath
GOVERSION: 1.6
PYTHON: C:\Python27-x64
PYTHON_VERSION: 2.7.x
PYTHON_ARCH: 64
install:
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version
- go env
- go get github.com/urfave/gfmrun/...
- go get -v -t ./...
build_script:
- python runtests vet
- python runtests test
- python runtests gfmrun

44
vendor/github.com/urfave/cli/category.go generated vendored Normal file
View file

@ -0,0 +1,44 @@
package cli
// CommandCategories is a slice of *CommandCategory.
type CommandCategories []*CommandCategory
// CommandCategory is a category containing commands.
type CommandCategory struct {
Name string
Commands Commands
}
func (c CommandCategories) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c CommandCategories) Len() int {
return len(c)
}
func (c CommandCategories) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
// AddCommand adds a command to a category.
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
for _, commandCategory := range c {
if commandCategory.Name == category {
commandCategory.Commands = append(commandCategory.Commands, command)
return c
}
}
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
}
// VisibleCommands returns a slice of the Commands with Hidden=false
func (c *CommandCategory) VisibleCommands() []Command {
ret := []Command{}
for _, command := range c.Commands {
if !command.Hidden {
ret = append(ret, command)
}
}
return ret
}

21
vendor/github.com/urfave/cli/cli.go generated vendored Normal file
View file

@ -0,0 +1,21 @@
// Package cli provides a minimal framework for creating and organizing command line
// Go applications. cli is designed to be easy to understand and write, the most simple
// cli application can be written as follows:
// func main() {
// cli.NewApp().Run(os.Args)
// }
//
// Of course this application does not do much, so let's make this an actual application:
// func main() {
// app := cli.NewApp()
// app.Name = "greet"
// app.Usage = "say a greeting"
// app.Action = func(c *cli.Context) error {
// println("Greetings")
// }
//
// app.Run(os.Args)
// }
package cli
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go

286
vendor/github.com/urfave/cli/command.go generated vendored Normal file
View file

@ -0,0 +1,286 @@
package cli
import (
"fmt"
"io/ioutil"
"sort"
"strings"
)
// Command is a subcommand for a cli.App.
type Command struct {
// The name of the command
Name string
// short name of the command. Typically one character (deprecated, use `Aliases`)
ShortName string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command
Usage string
// Custom text to show on USAGE section of help
UsageText string
// A longer explanation of how the command works
Description string
// A short description of the arguments of this command
ArgsUsage string
// The category the command is part of
Category string
// The function to call when checking for bash command completions
BashComplete BashCompleteFunc
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before BeforeFunc
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
// The function to call when this command is invoked
Action interface{}
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
// of deprecation period has passed, maybe?
// Execute this function if a usage error occurs.
OnUsageError OnUsageErrorFunc
// List of child commands
Subcommands Commands
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Skip argument reordering which attempts to move flags before arguments,
// but only works if all flags appear after all arguments. This behavior was
// removed n version 2 since it only works under specific conditions so we
// backport here by exposing it as an option for compatibility.
SkipArgReorder bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide this command from help or completion
Hidden bool
// Full name of command for help, defaults to full command name, including parent commands.
HelpName string
commandNamePath []string
}
// FullName returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
}
// Commands is a slice of Command
type Commands []Command
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) (err error) {
if len(c.Subcommands) > 0 {
return c.startApp(ctx)
}
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
}
set, err := flagSet(c.Name, c.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
} else if !c.SkipArgReorder {
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if arg == "-" {
// Do nothing. A dash alone is not really a flag.
continue
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
}
if firstFlagIndex > -1 {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())
}
} else {
err = set.Parse(ctx.Args().Tail())
}
nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
}
context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) {
return nil
}
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(ctx, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
if checkCommandHelp(context, c.Name) {
return nil
}
if c.After != nil {
defer func() {
afterErr := c.After(context)
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if c.Before != nil {
err = c.Before(context)
if err != nil {
fmt.Fprintln(ctx.App.Writer, err)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
HandleExitCoder(err)
return err
}
}
if c.Action == nil {
c.Action = helpSubcommand.Action
}
context.Command = c
err = HandleAction(c.Action, context)
if err != nil {
HandleExitCoder(err)
}
return err
}
// Names returns the names including short names and aliases.
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// HasName returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
}
}
return false
}
func (c Command) startApp(ctx *Context) error {
app := NewApp()
app.Metadata = ctx.App.Metadata
// set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.HelpName == "" {
app.HelpName = c.HelpName
} else {
app.HelpName = app.Name
}
if c.Description != "" {
app.Usage = c.Description
} else {
app.Usage = c.Usage
}
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
app.categories = CommandCategories{}
for _, command := range c.Subcommands {
app.categories = app.categories.AddCommand(command.Category, command)
}
sort.Sort(app.categories)
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
app.After = c.After
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpSubcommand.Action
}
for index, cc := range app.Commands {
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
}
return app.RunAsSubcommand(ctx)
}
// VisibleFlags returns a slice of the Flags with Hidden=false
func (c Command) VisibleFlags() []Flag {
return visibleFlags(c.Flags)
}

276
vendor/github.com/urfave/cli/context.go generated vendored Normal file
View file

@ -0,0 +1,276 @@
package cli
import (
"errors"
"flag"
"reflect"
"strings"
"syscall"
)
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific Args and
// parsed command-line options.
type Context struct {
App *App
Command Command
shellComplete bool
flagSet *flag.FlagSet
setFlags map[string]bool
parentContext *Context
}
// NewContext creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
c := &Context{App: app, flagSet: set, parentContext: parentCtx}
if parentCtx != nil {
c.shellComplete = parentCtx.shellComplete
}
return c
}
// NumFlags returns the number of flags set
func (c *Context) NumFlags() int {
return c.flagSet.NFlag()
}
// Set sets a context flag to a value.
func (c *Context) Set(name, value string) error {
return c.flagSet.Set(name, value)
}
// GlobalSet sets a context flag to a value on the global flagset
func (c *Context) GlobalSet(name, value string) error {
return globalContext(c).flagSet.Set(name, value)
}
// IsSet determines if the flag was actually set
func (c *Context) IsSet(name string) bool {
if c.setFlags == nil {
c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true
})
c.flagSet.VisitAll(func(f *flag.Flag) {
if _, ok := c.setFlags[f.Name]; ok {
return
}
c.setFlags[f.Name] = false
})
// XXX hack to support IsSet for flags with EnvVar
//
// There isn't an easy way to do this with the current implementation since
// whether a flag was set via an environment variable is very difficult to
// determine here. Instead, we intend to introduce a backwards incompatible
// change in version 2 to add `IsSet` to the Flag interface to push the
// responsibility closer to where the information required to determine
// whether a flag is set by non-standard means such as environment
// variables is avaliable.
//
// See https://github.com/urfave/cli/issues/294 for additional discussion
flags := c.Command.Flags
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
if c.App != nil {
flags = c.App.Flags
}
}
for _, f := range flags {
eachName(f.GetName(), func(name string) {
if isSet, ok := c.setFlags[name]; isSet || !ok {
return
}
val := reflect.ValueOf(f)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
envVarValue := val.FieldByName("EnvVar")
if !envVarValue.IsValid() {
return
}
eachName(envVarValue.String(), func(envVar string) {
envVar = strings.TrimSpace(envVar)
if _, ok := syscall.Getenv(envVar); ok {
c.setFlags[name] = true
return
}
})
})
}
}
return c.setFlags[name]
}
// GlobalIsSet determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if ctx.IsSet(name) {
return true
}
}
return false
}
// FlagNames returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags {
name := strings.Split(flag.GetName(), ",")[0]
if name == "help" {
continue
}
names = append(names, name)
}
return
}
// GlobalFlagNames returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags {
name := strings.Split(flag.GetName(), ",")[0]
if name == "help" || name == "version" {
continue
}
names = append(names, name)
}
return
}
// Parent returns the parent context, if any
func (c *Context) Parent() *Context {
return c.parentContext
}
// value returns the value of the flag coressponding to `name`
func (c *Context) value(name string) interface{} {
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
}
// Args contains apps console arguments
type Args []string
// Args returns the command line arguments associated with the context.
func (c *Context) Args() Args {
args := Args(c.flagSet.Args())
return args
}
// NArg returns the number of the command line arguments.
func (c *Context) NArg() int {
return len(c.Args())
}
// Get returns the nth argument, or else a blank string
func (a Args) Get(n int) string {
if len(a) > n {
return a[n]
}
return ""
}
// First returns the first argument, or else a blank string
func (a Args) First() string {
return a.Get(0)
}
// Tail returns the rest of the arguments (not the first one)
// or else an empty string slice
func (a Args) Tail() []string {
if len(a) >= 2 {
return []string(a)[1:]
}
return []string{}
}
// Present checks if there are any arguments present
func (a Args) Present() bool {
return len(a) != 0
}
// Swap swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
}
func globalContext(ctx *Context) *Context {
if ctx == nil {
return nil
}
for {
if ctx.parentContext == nil {
return ctx
}
ctx = ctx.parentContext
}
}
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
return nil
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case *StringSlice:
default:
set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := strings.Split(f.GetName(), ",")
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}

110
vendor/github.com/urfave/cli/errors.go generated vendored Normal file
View file

@ -0,0 +1,110 @@
package cli
import (
"fmt"
"io"
"os"
"strings"
)
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
var OsExiter = os.Exit
// ErrWriter is used to write errors to the user. This can be anything
// implementing the io.Writer interface and defaults to os.Stderr.
var ErrWriter io.Writer = os.Stderr
// MultiError is an error that wraps multiple errors.
type MultiError struct {
Errors []error
}
// NewMultiError creates a new MultiError. Pass in one or more errors.
func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
}
// Error implements the error interface.
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code
type ExitCoder interface {
error
ExitCode() int
}
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
type ExitError struct {
exitCode int
message interface{}
}
// NewExitError makes a new *ExitError
func NewExitError(message interface{}, exitCode int) *ExitError {
return &ExitError{
exitCode: exitCode,
message: message,
}
}
// Error returns the string message, fulfilling the interface required by
// `error`
func (ee *ExitError) Error() string {
return fmt.Sprintf("%v", ee.message)
}
// ExitCode returns the exit code, fulfilling the interface required by
// `ExitCoder`
func (ee *ExitError) ExitCode() int {
return ee.exitCode
}
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
// given exit code. If the given error is a MultiError, then this func is
// called on all members of the Errors slice.
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(ErrorFormatter); ok {
fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
fmt.Fprintln(ErrWriter, err)
}
}
OsExiter(exitErr.ExitCode())
return
}
if multiErr, ok := err.(MultiError); ok {
for _, merr := range multiErr.Errors {
HandleExitCoder(merr)
}
return
}
if err.Error() != "" {
if _, ok := err.(ErrorFormatter); ok {
fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
fmt.Fprintln(ErrWriter, err)
}
}
OsExiter(1)
}

93
vendor/github.com/urfave/cli/flag-types.json generated vendored Normal file
View file

@ -0,0 +1,93 @@
[
{
"name": "Bool",
"type": "bool",
"value": false,
"context_default": "false",
"parser": "strconv.ParseBool(f.Value.String())"
},
{
"name": "BoolT",
"type": "bool",
"value": false,
"doctail": " that is true by default",
"context_default": "false",
"parser": "strconv.ParseBool(f.Value.String())"
},
{
"name": "Duration",
"type": "time.Duration",
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
"context_default": "0",
"parser": "time.ParseDuration(f.Value.String())"
},
{
"name": "Float64",
"type": "float64",
"context_default": "0",
"parser": "strconv.ParseFloat(f.Value.String(), 64)"
},
{
"name": "Generic",
"type": "Generic",
"dest": false,
"context_default": "nil",
"context_type": "interface{}"
},
{
"name": "Int64",
"type": "int64",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
},
{
"name": "Int",
"type": "int",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
"parser_cast": "int(parsed)"
},
{
"name": "IntSlice",
"type": "*IntSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]int",
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
},
{
"name": "Int64Slice",
"type": "*Int64Slice",
"dest": false,
"context_default": "nil",
"context_type": "[]int64",
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
},
{
"name": "String",
"type": "string",
"context_default": "\"\"",
"parser": "f.Value.String(), error(nil)"
},
{
"name": "StringSlice",
"type": "*StringSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]string",
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
},
{
"name": "Uint64",
"type": "uint64",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
},
{
"name": "Uint",
"type": "uint",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
"parser_cast": "uint(parsed)"
}
]

799
vendor/github.com/urfave/cli/flag.go generated vendored Normal file
View file

@ -0,0 +1,799 @@
package cli
import (
"flag"
"fmt"
"reflect"
"runtime"
"strconv"
"strings"
"syscall"
"time"
)
const defaultPlaceholder = "value"
// BashCompletionFlag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{
Name: "generate-bash-completion",
Hidden: true,
}
// VersionFlag prints the version for the application
var VersionFlag = BoolFlag{
Name: "version, v",
Usage: "print the version",
}
// HelpFlag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true)
var HelpFlag = BoolFlag{
Name: "help, h",
Usage: "show help",
}
// FlagStringer converts a flag definition to a string. This is used by help
// to display a flag.
var FlagStringer FlagStringFunc = stringifyFlag
// FlagsByName is a slice of Flag.
type FlagsByName []Flag
func (f FlagsByName) Len() int {
return len(f)
}
func (f FlagsByName) Less(i, j int) bool {
return f[i].GetName() < f[j].GetName()
}
func (f FlagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recommended that
// this interface be implemented.
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
GetName() string
}
// errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors
type errorableFlag interface {
Flag
ApplyWithError(*flag.FlagSet) error
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags {
//TODO remove in v2 when errorableFlag is removed
if ef, ok := f.(errorableFlag); ok {
if err := ef.ApplyWithError(set); err != nil {
return nil, err
}
} else {
f.Apply(set)
}
}
return set, nil
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
}
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
// Ignores parsing errors
func (f GenericFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if err := val.Set(envVal); err != nil {
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
}
break
}
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
return nil
}
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
*f = append(*f, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f)
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return *f
}
// Get returns the slice of strings set by this flag
func (f *StringSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int {
return *f
}
// Get returns the slice of ints set by this flag
func (f *IntSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
type Int64Slice []int64
// Set parses the value into an integer and appends it to the list of values
func (f *Int64Slice) Set(value string) error {
tmp, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
*f = append(*f, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *Int64Slice) String() string {
return fmt.Sprintf("%#v", *f)
}
// Value returns the slice of ints set by this flag
func (f *Int64Slice) Value() []int64 {
return *f
}
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &Int64Slice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &Int64Slice{}
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
val := false
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolTFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
val := true
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
return
}
set.String(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = int(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Int(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValInt
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Int64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Int64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f UintFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.UintVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Uint64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint64(envValInt)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Uint64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f DurationFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValDuration
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Duration(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Float64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal, ok := syscall.Getenv(envVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err != nil {
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = float64(envValFloat)
break
}
}
}
eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Float64(name, f.Value, f.Usage)
})
return nil
}
func visibleFlags(fl []Flag) []Flag {
visible := []Flag{}
for _, flag := range fl {
if !flagValue(flag).FieldByName("Hidden").Bool() {
visible = append(visible, flag)
}
}
return visible
}
func prefixFor(name string) (prefix string) {
if len(name) == 1 {
prefix = "-"
} else {
prefix = "--"
}
return
}
// Returns the placeholder, if any, and the unquoted usage string.
func unquoteUsage(usage string) (string, string) {
for i := 0; i < len(usage); i++ {
if usage[i] == '`' {
for j := i + 1; j < len(usage); j++ {
if usage[j] == '`' {
name := usage[i+1 : j]
usage = usage[:i] + name + usage[j+1:]
return name, usage
}
}
break
}
}
return "", usage
}
func prefixedNames(fullName, placeholder string) string {
var prefixed string
parts := strings.Split(fullName, ",")
for i, name := range parts {
name = strings.Trim(name, " ")
prefixed += prefixFor(name) + name
if placeholder != "" {
prefixed += " " + placeholder
}
if i < len(parts)-1 {
prefixed += ", "
}
}
return prefixed
}
func withEnvHint(envVar, str string) string {
envText := ""
if envVar != "" {
prefix := "$"
suffix := ""
sep := ", $"
if runtime.GOOS == "windows" {
prefix = "%"
suffix = "%"
sep = "%, %"
}
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
}
return str + envText
}
func flagValue(f Flag) reflect.Value {
fv := reflect.ValueOf(f)
for fv.Kind() == reflect.Ptr {
fv = reflect.Indirect(fv)
}
return fv
}
func stringifyFlag(f Flag) string {
fv := flagValue(f)
switch f.(type) {
case IntSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyIntSliceFlag(f.(IntSliceFlag)))
case Int64SliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
case StringSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyStringSliceFlag(f.(StringSliceFlag)))
}
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
needsPlaceholder := false
defaultValueString := ""
val := fv.FieldByName("Value")
if val.IsValid() {
needsPlaceholder = true
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
if val.Kind() == reflect.String && val.String() != "" {
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
}
}
if defaultValueString == " (default: )" {
defaultValueString = ""
}
if needsPlaceholder && placeholder == "" {
placeholder = defaultPlaceholder
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
return withEnvHint(fv.FieldByName("EnvVar").String(),
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
}
func stringifyIntSliceFlag(f IntSliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
}
func stringifyInt64SliceFlag(f Int64SliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
}
func stringifyStringSliceFlag(f StringSliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() {
if len(s) > 0 {
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
}
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
}
func stringifySliceFlag(usage, name string, defaultVals []string) string {
placeholder, usage := unquoteUsage(usage)
if placeholder == "" {
placeholder = defaultPlaceholder
}
defaultVal := ""
if len(defaultVals) > 0 {
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
}

627
vendor/github.com/urfave/cli/flag_generated.go generated vendored Normal file
View file

@ -0,0 +1,627 @@
package cli
import (
"flag"
"strconv"
"time"
)
// WARNING: This file is generated!
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolFlag) GetName() string {
return f.Name
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// GlobalBool looks up the value of a global BoolFlag, returns
// false if not found
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// BoolTFlag is a flag with type bool that is true by default
type BoolTFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f BoolTFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f BoolTFlag) GetName() string {
return f.Name
}
// BoolT looks up the value of a local BoolTFlag, returns
// false if not found
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// GlobalBoolT looks up the value of a global BoolTFlag, returns
// false if not found
func (c *Context) GlobalBoolT(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBoolT(name, fs)
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value time.Duration
Destination *time.Duration
}
// String returns a readable representation of this value
// (for usage defaults)
func (f DurationFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f DurationFlag) GetName() string {
return f.Name
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// GlobalDuration looks up the value of a global DurationFlag, returns
// 0 if not found
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
parsed, err := time.ParseDuration(f.Value.String())
if err != nil {
return 0
}
return parsed
}
return 0
}
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value float64
Destination *float64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Float64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Float64Flag) GetName() string {
return f.Name
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// GlobalFloat64 looks up the value of a global Float64Flag, returns
// 0 if not found
func (c *Context) GlobalFloat64(name string) float64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value Generic
}
// String returns a readable representation of this value
// (for usage defaults)
func (f GenericFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f GenericFlag) GetName() string {
return f.Name
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// GlobalGeneric looks up the value of a global GenericFlag, returns
// nil if not found
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value, error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int64
Destination *int64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64Flag) GetName() string {
return f.Name
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
return lookupInt64(name, c.flagSet)
}
// GlobalInt64 looks up the value of a global Int64Flag, returns
// 0 if not found
func (c *Context) GlobalInt64(name string) int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64(name, fs)
}
return 0
}
func lookupInt64(name string, set *flag.FlagSet) int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value int
Destination *int
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntFlag) GetName() string {
return f.Name
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// GlobalInt looks up the value of a global IntFlag, returns
// 0 if not found
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return int(parsed)
}
return 0
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *IntSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f IntSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f IntSliceFlag) GetName() string {
return f.Name
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
// nil if not found
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *Int64Slice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Int64SliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Int64SliceFlag) GetName() string {
return f.Name
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
return lookupInt64Slice(name, c.flagSet)
}
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
// nil if not found
func (c *Context) GlobalInt64Slice(name string) []int64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringFlag) GetName() string {
return f.Name
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// GlobalString looks up the value of a global StringFlag, returns
// "" if not found
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value *StringSlice
}
// String returns a readable representation of this value
// (for usage defaults)
func (f StringSliceFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f StringSliceFlag) GetName() string {
return f.Name
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
// nil if not found
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint64
Destination *uint64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f Uint64Flag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f Uint64Flag) GetName() string {
return f.Name
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
return lookupUint64(name, c.flagSet)
}
// GlobalUint64 looks up the value of a global Uint64Flag, returns
// 0 if not found
func (c *Context) GlobalUint64(name string) uint64 {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint64(name, fs)
}
return 0
}
func lookupUint64(name string, set *flag.FlagSet) uint64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Usage string
EnvVar string
Hidden bool
Value uint
Destination *uint
}
// String returns a readable representation of this value
// (for usage defaults)
func (f UintFlag) String() string {
return FlagStringer(f)
}
// GetName returns the name of the flag
func (f UintFlag) GetName() string {
return f.Name
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
return lookupUint(name, c.flagSet)
}
// GlobalUint looks up the value of a global UintFlag, returns
// 0 if not found
func (c *Context) GlobalUint(name string) uint {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupUint(name, fs)
}
return 0
}
func lookupUint(name string, set *flag.FlagSet) uint {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return uint(parsed)
}
return 0
}

28
vendor/github.com/urfave/cli/funcs.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package cli
// BashCompleteFunc is an action to execute when the bash-completion flag is set
type BashCompleteFunc func(*Context)
// BeforeFunc is an action to execute before any subcommands are run, but after
// the context is ready if a non-nil error is returned, no subcommands are run
type BeforeFunc func(*Context) error
// AfterFunc is an action to execute after any subcommands are run, but after the
// subcommand has finished it is run even if Action() panics
type AfterFunc func(*Context) error
// ActionFunc is the action to execute when no subcommands are specified
type ActionFunc func(*Context) error
// CommandNotFoundFunc is executed if the proper command cannot be found
type CommandNotFoundFunc func(*Context, string)
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
// customized usage error messages. This function is able to replace the
// original error messages. If this function is not set, the "Incorrect usage"
// is displayed and the execution is interrupted.
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
// FlagStringFunc is used by the help generation to display a flag, which is
// expected to be a single line.
type FlagStringFunc func(Flag) string

255
vendor/github.com/urfave/cli/generate-flag-types generated vendored Normal file
View file

@ -0,0 +1,255 @@
#!/usr/bin/env python
"""
The flag types that ship with the cli library have many things in common, and
so we can take advantage of the `go generate` command to create much of the
source code from a list of definitions. These definitions attempt to cover
the parts that vary between flag types, and should evolve as needed.
An example of the minimum definition needed is:
{
"name": "SomeType",
"type": "sometype",
"context_default": "nil"
}
In this example, the code generated for the `cli` package will include a type
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
Fetching values by name via `*cli.Context` will default to a value of `nil`.
A more complete, albeit somewhat redundant, example showing all available
definition keys is:
{
"name": "VeryMuchType",
"type": "*VeryMuchType",
"value": true,
"dest": false,
"doctail": " which really only wraps a []float64, oh well!",
"context_type": "[]float64",
"context_default": "nil",
"parser": "parseVeryMuchType(f.Value.String())",
"parser_cast": "[]float64(parsed)"
}
The meaning of each field is as follows:
name (string) - The type "name", which will be suffixed with
`Flag` when generating the type definition
for `cli` and the wrapper type for `altsrc`
type (string) - The type that the generated `Flag` type for `cli`
is expected to "contain" as its `.Value` member
value (bool) - Should the generated `cli` type have a `Value`
member?
dest (bool) - Should the generated `cli` type support a
destination pointer?
doctail (string) - Additional docs for the `cli` flag type comment
context_type (string) - The literal type used in the `*cli.Context`
reader func signature
context_default (string) - The literal value used as the default by the
`*cli.Context` reader funcs when no value is
present
parser (string) - Literal code used to parse the flag `f`,
expected to have a return signature of
(value, error)
parser_cast (string) - Literal code used to cast the `parsed` value
returned from the `parser` code
"""
from __future__ import print_function, unicode_literals
import argparse
import json
import os
import subprocess
import sys
import tempfile
import textwrap
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
def main(sysargs=sys.argv[:]):
parser = argparse.ArgumentParser(
description='Generate flag type code!',
formatter_class=_FancyFormatter)
parser.add_argument(
'package',
type=str, default='cli', choices=_WRITEFUNCS.keys(),
help='Package for which flag types will be generated'
)
parser.add_argument(
'-i', '--in-json',
type=argparse.FileType('r'),
default=sys.stdin,
help='Input JSON file which defines each type to be generated'
)
parser.add_argument(
'-o', '--out-go',
type=argparse.FileType('w'),
default=sys.stdout,
help='Output file/stream to which generated source will be written'
)
parser.epilog = __doc__
args = parser.parse_args(sysargs[1:])
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
return 0
def _generate_flag_types(writefunc, output_go, input_json):
types = json.load(input_json)
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
writefunc(tmp, types)
tmp.close()
new_content = subprocess.check_output(
['goimports', tmp.name]
).decode('utf-8')
print(new_content, file=output_go, end='')
output_go.flush()
os.remove(tmp.name)
def _set_typedef_defaults(typedef):
typedef.setdefault('doctail', '')
typedef.setdefault('context_type', typedef['type'])
typedef.setdefault('dest', True)
typedef.setdefault('value', True)
typedef.setdefault('parser', 'f.Value, error(nil)')
typedef.setdefault('parser_cast', 'parsed')
def _write_cli_flag_types(outfile, types):
_fwrite(outfile, """\
package cli
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is a flag with type {type}{doctail}
type {name}Flag struct {{
Name string
Usage string
EnvVar string
Hidden bool
""".format(**typedef))
if typedef['value']:
_fwrite(outfile, """\
Value {type}
""".format(**typedef))
if typedef['dest']:
_fwrite(outfile, """\
Destination *{type}
""".format(**typedef))
_fwrite(outfile, "\n}\n\n")
_fwrite(outfile, """\
// String returns a readable representation of this value
// (for usage defaults)
func (f {name}Flag) String() string {{
return FlagStringer(f)
}}
// GetName returns the name of the flag
func (f {name}Flag) GetName() string {{
return f.Name
}}
// {name} looks up the value of a local {name}Flag, returns
// {context_default} if not found
func (c *Context) {name}(name string) {context_type} {{
return lookup{name}(name, c.flagSet)
}}
// Global{name} looks up the value of a global {name}Flag, returns
// {context_default} if not found
func (c *Context) Global{name}(name string) {context_type} {{
if fs := lookupGlobalFlagSet(name, c); fs != nil {{
return lookup{name}(name, fs)
}}
return {context_default}
}}
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
f := set.Lookup(name)
if f != nil {{
parsed, err := {parser}
if err != nil {{
return {context_default}
}}
return {parser_cast}
}}
return {context_default}
}}
""".format(**typedef))
def _write_altsrc_flag_types(outfile, types):
_fwrite(outfile, """\
package altsrc
import (
"gopkg.in/urfave/cli.v1"
)
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is the flag type that wraps cli.{name}Flag to allow
// for other values to be specified
type {name}Flag struct {{
cli.{name}Flag
set *flag.FlagSet
}}
// New{name}Flag creates a new {name}Flag
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
return &{name}Flag{{{name}Flag: fl, set: nil}}
}}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.Apply
func (f *{name}Flag) Apply(set *flag.FlagSet) {{
f.set = set
f.{name}Flag.Apply(set)
}}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.ApplyWithError
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
f.set = set
return f.{name}Flag.ApplyWithError(set)
}}
""".format(**typedef))
def _fwrite(outfile, text):
print(textwrap.dedent(text), end='', file=outfile)
_WRITEFUNCS = {
'cli': _write_cli_flag_types,
'altsrc': _write_altsrc_flag_types
}
if __name__ == '__main__':
sys.exit(main())

294
vendor/github.com/urfave/cli/help.go generated vendored Normal file
View file

@ -0,0 +1,294 @@
package cli
import (
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"text/template"
)
// AppHelpTemplate is the text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION:
{{.Version}}{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if len .Authors}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT:
{{.Copyright}}{{end}}
`
// CommandHelpTemplate is the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
// SubcommandHelpTemplate is the text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
var helpCommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) error {
args := c.Args()
if args.Present() {
return ShowCommandHelp(c, args.First())
}
ShowAppHelp(c)
return nil
},
}
var helpSubcommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) error {
args := c.Args()
if args.Present() {
return ShowCommandHelp(c, args.First())
}
return ShowSubcommandHelp(c)
},
}
// Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{})
// HelpPrinter is a function that writes the help output. If not set a default
// is used. The function signature is:
// func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp
// VersionPrinter prints the version for the App
var VersionPrinter = printVersion
// ShowAppHelp is an action that displays the help.
func ShowAppHelp(c *Context) error {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
return nil
}
// DefaultAppComplete prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
if command.Hidden {
continue
}
for _, name := range command.Names() {
fmt.Fprintln(c.App.Writer, name)
}
}
}
// ShowCommandHelp prints help for the given command
func ShowCommandHelp(ctx *Context, command string) error {
// show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return nil
}
for _, c := range ctx.App.Commands {
if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return nil
}
}
if ctx.App.CommandNotFound == nil {
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
}
ctx.App.CommandNotFound(ctx, command)
return nil
}
// ShowSubcommandHelp prints help for the given subcommand
func ShowSubcommandHelp(c *Context) error {
return ShowCommandHelp(c, c.Command.Name)
}
// ShowVersion prints the version number of the App
func ShowVersion(c *Context) {
VersionPrinter(c)
}
func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
}
// ShowCompletions prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// ShowCommandCompletions prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil {
c.BashComplete(ctx)
}
}
func printHelp(out io.Writer, templ string, data interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
}
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data)
if err != nil {
// If the writer is closed, t.Execute will fail, and there's nothing
// we can do to recover.
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
}
return
}
w.Flush()
}
func checkVersion(c *Context) bool {
found := false
if VersionFlag.Name != "" {
eachName(VersionFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkHelp(c *Context) bool {
found := false
if HelpFlag.Name != "" {
eachName(HelpFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
}
})
}
return found
}
func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name)
return true
}
return false
}
func checkSubcommandHelp(c *Context) bool {
if c.Bool("h") || c.Bool("help") {
ShowSubcommandHelp(c)
return true
}
return false
}
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
if !a.EnableBashCompletion {
return false, arguments
}
pos := len(arguments) - 1
lastArg := arguments[pos]
if lastArg != "--"+BashCompletionFlag.Name {
return false, arguments
}
return true, arguments[:pos]
}
func checkCompletions(c *Context) bool {
if !c.shellComplete {
return false
}
if args := c.Args(); args.Present() {
name := args.First()
if cmd := c.App.Command(name); cmd != nil {
// let the command handle the completion
return false
}
}
ShowCompletions(c)
return true
}
func checkCommandCompletions(c *Context, name string) bool {
if !c.shellComplete {
return false
}
ShowCommandCompletions(c, name)
return true
}

122
vendor/github.com/urfave/cli/runtests generated vendored Normal file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env python
from __future__ import print_function
import argparse
import os
import sys
import tempfile
from subprocess import check_call, check_output
PACKAGE_NAME = os.environ.get(
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
)
def main(sysargs=sys.argv[:]):
targets = {
'vet': _vet,
'test': _test,
'gfmrun': _gfmrun,
'toc': _toc,
'gen': _gen,
}
parser = argparse.ArgumentParser()
parser.add_argument(
'target', nargs='?', choices=tuple(targets.keys()), default='test'
)
args = parser.parse_args(sysargs[1:])
targets[args.target]()
return 0
def _test():
if check_output('go version'.split()).split()[2] < 'go1.2':
_run('go test -v .')
return
coverprofiles = []
for subpackage in ['', 'altsrc']:
coverprofile = 'cli.coverprofile'
if subpackage != '':
coverprofile = '{}.coverprofile'.format(subpackage)
coverprofiles.append(coverprofile)
_run('go test -v'.split() + [
'-coverprofile={}'.format(coverprofile),
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
])
combined_name = _combine_coverprofiles(coverprofiles)
_run('go tool cover -func={}'.format(combined_name))
os.remove(combined_name)
def _gfmrun():
go_version = check_output('go version'.split()).split()[2]
if go_version < 'go1.3':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
def _vet():
_run('go vet ./...')
def _toc():
_run('node_modules/.bin/markdown-toc -i README.md')
_run('git diff --exit-code')
def _gen():
go_version = check_output('go version'.split()).split()[2]
if go_version < 'go1.5':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run('go generate ./...')
_run('git diff --exit-code')
def _run(command):
if hasattr(command, 'split'):
command = command.split()
print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
check_call(command)
def _gfmrun_count():
with open('README.md') as infile:
lines = infile.read().splitlines()
return len(filter(_is_go_runnable, lines))
def _is_go_runnable(line):
return line.startswith('package main')
def _combine_coverprofiles(coverprofiles):
combined = tempfile.NamedTemporaryFile(
suffix='.coverprofile', delete=False
)
combined.write('mode: set\n')
for coverprofile in coverprofiles:
with open(coverprofile, 'r') as infile:
for line in infile.readlines():
if not line.startswith('mode: '):
combined.write(line)
combined.flush()
name = combined.name
combined.close()
return name
if __name__ == '__main__':
sys.exit(main())

0
vendor/golang.org/x/sys/unix/mksyscall.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksyscall_solaris.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksysctl_openbsd.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksysnum_darwin.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksysnum_dragonfly.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksysnum_freebsd.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksysnum_linux.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksysnum_netbsd.pl generated vendored Executable file → Normal file
View file

0
vendor/golang.org/x/sys/unix/mksysnum_openbsd.pl generated vendored Executable file → Normal file
View file