diff --git a/.gitignore b/.gitignore index e237cc4..72c9fd3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,3 @@ bin/ # Cache *.swp -*.swo diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bbfc6e1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.5 + +MAINTAINER fatedier + +RUN echo "[common]\nbind_addr = 0.0.0.0\nbind_port = 7000\n[wiki]\npasswd = 123\nbind_addr = 0.0.0.0\nlisten_port = 80" > /usr/share/frps.ini + +ADD ./ /usr/share/frp/ + +RUN cd /usr/share/frp && make + +EXPOSE 80 +EXPOSE 7000 + +CMD ["/usr/share/frp/bin/frps -c /usr/share/frps.ini"] diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 70fd281..3614cb4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -10,6 +10,11 @@ "Comment": "v1.5.0-9-gfb7314f", "Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5" }, + { + "ImportPath": "github.com/docopt/docopt-go", + "Comment": "0.6.2", + "Rev": "784ddc588536785e7299f7272f39101f7faccc3f" + }, { "ImportPath": "github.com/vaughan0/go-ini", "Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" diff --git a/Godeps/_workspace/src/github.com/docopt/docopt-go/.gitignore b/Godeps/_workspace/src/github.com/docopt/docopt-go/.gitignore new file mode 100644 index 0000000..49ad16c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docopt/docopt-go/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +# coverage droppings +profile.cov diff --git a/Godeps/_workspace/src/github.com/docopt/docopt-go/.travis.yml b/Godeps/_workspace/src/github.com/docopt/docopt-go/.travis.yml new file mode 100644 index 0000000..65fad59 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docopt/docopt-go/.travis.yml @@ -0,0 +1,31 @@ +# Travis CI (http://travis-ci.org/) is a continuous integration +# service for open source projects. This file configures it +# to run unit tests for docopt-go. + +language: go + +go: + - 1.4 + - 1.5 + - tip + +matrix: + fast_finish: true + +before_install: + - go get golang.org/x/tools/cmd/vet + - go get golang.org/x/tools/cmd/cover + - go get github.com/golang/lint/golint + - go get github.com/mattn/goveralls + +install: + - go get -d -v ./... && go build -v ./... + +script: + - go vet -x ./... + - $HOME/gopath/bin/golint ./... + - go test -v ./... + - go test -covermode=count -coverprofile=profile.cov . + +after_script: + - $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci diff --git a/Godeps/_workspace/src/github.com/docopt/docopt-go/LICENSE b/Godeps/_workspace/src/github.com/docopt/docopt-go/LICENSE new file mode 100644 index 0000000..8841af1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docopt/docopt-go/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Keith Batten + +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. diff --git a/Godeps/_workspace/src/github.com/docopt/docopt-go/README.md b/Godeps/_workspace/src/github.com/docopt/docopt-go/README.md new file mode 100644 index 0000000..71c92aa --- /dev/null +++ b/Godeps/_workspace/src/github.com/docopt/docopt-go/README.md @@ -0,0 +1,88 @@ +docopt-go +========= + +[![Build Status](https://travis-ci.org/docopt/docopt.go.svg?branch=master)](https://travis-ci.org/docopt/docopt.go) +[![Coverage Status](https://coveralls.io/repos/docopt/docopt.go/badge.png)](https://coveralls.io/r/docopt/docopt.go) +[![GoDoc](https://godoc.org/github.com/docopt/docopt.go?status.png)](https://godoc.org/github.com/docopt/docopt.go) + +An implementation of [docopt](http://docopt.org/) in the +[Go](http://golang.org/) programming language. + +**docopt** helps you create *beautiful* command-line interfaces easily: + +```go +package main + +import ( + "fmt" + "github.com/docopt/docopt-go" +) + +func main() { + usage := `Naval Fate. + +Usage: + naval_fate ship new ... + naval_fate ship move [--speed=] + naval_fate ship shoot + naval_fate mine (set|remove) [--moored|--drifting] + naval_fate -h | --help + naval_fate --version + +Options: + -h --help Show this screen. + --version Show version. + --speed= Speed in knots [default: 10]. + --moored Moored (anchored) mine. + --drifting Drifting mine.` + + arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false) + fmt.Println(arguments) +} +``` + +**docopt** parses command-line arguments based on a help message. Don't +write parser code: a good help message already has all the necessary +information in it. + +## Installation + +⚠ Use the alias “docopt-go”. To use docopt in your Go code: + +```go +import "github.com/docopt/docopt-go" +``` + +To install docopt according to your `$GOPATH`: + +```console +$ go get github.com/docopt/docopt-go +``` + +## API + +```go +func Parse(doc string, argv []string, help bool, version string, + optionsFirst bool, exit ...bool) (map[string]interface{}, error) +``` +Parse `argv` based on the command-line interface described in `doc`. + +Given a conventional command-line help message, docopt creates a parser and +processes the arguments. See +https://github.com/docopt/docopt#help-message-format for a description of the +help message format. If `argv` is `nil`, `os.Args[1:]` is used. + +docopt returns a map of option names to the values parsed from `argv`, and an +error or `nil`. + +More documentation for docopt is available at +[GoDoc.org](https://godoc.org/github.com/docopt/docopt.go). + +## Testing + +All tests from the Python version are implemented and passing +at [Travis CI](https://travis-ci.org/docopt/docopt.go). New +language-agnostic tests have been added +to [test_golang.docopt](test_golang.docopt). + +To run tests for docopt-go, use `go test`. diff --git a/Godeps/_workspace/src/github.com/docopt/docopt-go/docopt.go b/Godeps/_workspace/src/github.com/docopt/docopt-go/docopt.go new file mode 100644 index 0000000..d929fc3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docopt/docopt-go/docopt.go @@ -0,0 +1,1239 @@ +// Licensed under terms of MIT license (see LICENSE-MIT) +// Copyright (c) 2013 Keith Batten, kbatten@gmail.com + +/* +Package docopt parses command-line arguments based on a help message. + +⚠ Use the alias “docopt-go”: + import "github.com/docopt/docopt-go" +or + $ go get github.com/docopt/docopt-go +*/ +package docopt + +import ( + "fmt" + "os" + "reflect" + "regexp" + "strings" + "unicode" +) + +/* +Parse `argv` based on the command-line interface described in `doc`. + +Given a conventional command-line help message, docopt creates a parser and +processes the arguments. See +https://github.com/docopt/docopt#help-message-format for a description of the +help message format. If `argv` is `nil`, `os.Args[1:]` is used. + +docopt returns a map of option names to the values parsed from `argv`, and an +error or `nil`. + +Set `help` to `false` to disable automatic help messages on `-h` or `--help`. +If `version` is a non-empty string, it will be printed when `--version` is +specified. Set `optionsFirst` to `true` to require that options always come +before positional arguments; otherwise they can overlap. + +By default, docopt calls `os.Exit(0)` if it handled a built-in option such as +`-h` or `--version`. If the user errored with a wrong command or options, +docopt exits with a return code of 1. To stop docopt from calling `os.Exit()` +and to handle your own return codes, pass an optional last parameter of `false` +for `exit`. +*/ +func Parse(doc string, argv []string, help bool, version string, + optionsFirst bool, exit ...bool) (map[string]interface{}, error) { + // if "false" was the (optional) last arg, don't call os.Exit() + exitOk := true + if len(exit) > 0 { + exitOk = exit[0] + } + args, output, err := parse(doc, argv, help, version, optionsFirst) + if _, ok := err.(*UserError); ok { + // the user gave us bad input + fmt.Fprintln(os.Stderr, output) + if exitOk { + os.Exit(1) + } + } else if len(output) > 0 && err == nil { + // the user asked for help or `--version` + fmt.Println(output) + if exitOk { + os.Exit(0) + } + } + return args, err +} + +// parse and return a map of args, output and all errors +func parse(doc string, argv []string, help bool, version string, optionsFirst bool) (args map[string]interface{}, output string, err error) { + if argv == nil && len(os.Args) > 1 { + argv = os.Args[1:] + } + + usageSections := parseSection("usage:", doc) + + if len(usageSections) == 0 { + err = newLanguageError("\"usage:\" (case-insensitive) not found.") + return + } + if len(usageSections) > 1 { + err = newLanguageError("More than one \"usage:\" (case-insensitive).") + return + } + usage := usageSections[0] + + options := parseDefaults(doc) + formal, err := formalUsage(usage) + if err != nil { + output = handleError(err, usage) + return + } + + pat, err := parsePattern(formal, &options) + if err != nil { + output = handleError(err, usage) + return + } + + patternArgv, err := parseArgv(newTokenList(argv, errorUser), &options, optionsFirst) + if err != nil { + output = handleError(err, usage) + return + } + patFlat, err := pat.flat(patternOption) + if err != nil { + output = handleError(err, usage) + return + } + patternOptions := patFlat.unique() + + patFlat, err = pat.flat(patternOptionSSHORTCUT) + if err != nil { + output = handleError(err, usage) + return + } + for _, optionsShortcut := range patFlat { + docOptions := parseDefaults(doc) + optionsShortcut.children = docOptions.unique().diff(patternOptions) + } + + if output = extras(help, version, patternArgv, doc); len(output) > 0 { + return + } + + err = pat.fix() + if err != nil { + output = handleError(err, usage) + return + } + matched, left, collected := pat.match(&patternArgv, nil) + if matched && len(*left) == 0 { + patFlat, err = pat.flat(patternDefault) + if err != nil { + output = handleError(err, usage) + return + } + args = append(patFlat, *collected...).dictionary() + return + } + + err = newUserError("") + output = handleError(err, usage) + return +} + +func handleError(err error, usage string) string { + if _, ok := err.(*UserError); ok { + return strings.TrimSpace(fmt.Sprintf("%s\n%s", err, usage)) + } + return "" +} + +func parseSection(name, source string) []string { + p := regexp.MustCompile(`(?im)^([^\n]*` + name + `[^\n]*\n?(?:[ \t].*?(?:\n|$))*)`) + s := p.FindAllString(source, -1) + if s == nil { + s = []string{} + } + for i, v := range s { + s[i] = strings.TrimSpace(v) + } + return s +} + +func parseDefaults(doc string) patternList { + defaults := patternList{} + p := regexp.MustCompile(`\n[ \t]*(-\S+?)`) + for _, s := range parseSection("options:", doc) { + // FIXME corner case "bla: options: --foo" + _, _, s = stringPartition(s, ":") // get rid of "options:" + split := p.Split("\n"+s, -1)[1:] + match := p.FindAllStringSubmatch("\n"+s, -1) + for i := range split { + optionDescription := match[i][1] + split[i] + if strings.HasPrefix(optionDescription, "-") { + defaults = append(defaults, parseOption(optionDescription)) + } + } + } + return defaults +} + +func parsePattern(source string, options *patternList) (*pattern, error) { + tokens := tokenListFromPattern(source) + result, err := parseExpr(tokens, options) + if err != nil { + return nil, err + } + if tokens.current() != nil { + return nil, tokens.errorFunc("unexpected ending: %s" + strings.Join(tokens.tokens, " ")) + } + return newRequired(result...), nil +} + +func parseArgv(tokens *tokenList, options *patternList, optionsFirst bool) (patternList, error) { + /* + Parse command-line argument vector. + + If options_first: + argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; + else: + argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; + */ + parsed := patternList{} + for tokens.current() != nil { + if tokens.current().eq("--") { + for _, v := range tokens.tokens { + parsed = append(parsed, newArgument("", v)) + } + return parsed, nil + } else if tokens.current().hasPrefix("--") { + pl, err := parseLong(tokens, options) + if err != nil { + return nil, err + } + parsed = append(parsed, pl...) + } else if tokens.current().hasPrefix("-") && !tokens.current().eq("-") { + ps, err := parseShorts(tokens, options) + if err != nil { + return nil, err + } + parsed = append(parsed, ps...) + } else if optionsFirst { + for _, v := range tokens.tokens { + parsed = append(parsed, newArgument("", v)) + } + return parsed, nil + } else { + parsed = append(parsed, newArgument("", tokens.move().String())) + } + } + return parsed, nil +} + +func parseOption(optionDescription string) *pattern { + optionDescription = strings.TrimSpace(optionDescription) + options, _, description := stringPartition(optionDescription, " ") + options = strings.Replace(options, ",", " ", -1) + options = strings.Replace(options, "=", " ", -1) + + short := "" + long := "" + argcount := 0 + var value interface{} + value = false + + reDefault := regexp.MustCompile(`(?i)\[default: (.*)\]`) + for _, s := range strings.Fields(options) { + if strings.HasPrefix(s, "--") { + long = s + } else if strings.HasPrefix(s, "-") { + short = s + } else { + argcount = 1 + } + if argcount > 0 { + matched := reDefault.FindAllStringSubmatch(description, -1) + if len(matched) > 0 { + value = matched[0][1] + } else { + value = nil + } + } + } + return newOption(short, long, argcount, value) +} + +func parseExpr(tokens *tokenList, options *patternList) (patternList, error) { + // expr ::= seq ( '|' seq )* ; + seq, err := parseSeq(tokens, options) + if err != nil { + return nil, err + } + if !tokens.current().eq("|") { + return seq, nil + } + var result patternList + if len(seq) > 1 { + result = patternList{newRequired(seq...)} + } else { + result = seq + } + for tokens.current().eq("|") { + tokens.move() + seq, err = parseSeq(tokens, options) + if err != nil { + return nil, err + } + if len(seq) > 1 { + result = append(result, newRequired(seq...)) + } else { + result = append(result, seq...) + } + } + if len(result) > 1 { + return patternList{newEither(result...)}, nil + } + return result, nil +} + +func parseSeq(tokens *tokenList, options *patternList) (patternList, error) { + // seq ::= ( atom [ '...' ] )* ; + result := patternList{} + for !tokens.current().match(true, "]", ")", "|") { + atom, err := parseAtom(tokens, options) + if err != nil { + return nil, err + } + if tokens.current().eq("...") { + atom = patternList{newOneOrMore(atom...)} + tokens.move() + } + result = append(result, atom...) + } + return result, nil +} + +func parseAtom(tokens *tokenList, options *patternList) (patternList, error) { + // atom ::= '(' expr ')' | '[' expr ']' | 'options' | long | shorts | argument | command ; + tok := tokens.current() + result := patternList{} + if tokens.current().match(false, "(", "[") { + tokens.move() + var matching string + pl, err := parseExpr(tokens, options) + if err != nil { + return nil, err + } + if tok.eq("(") { + matching = ")" + result = patternList{newRequired(pl...)} + } else if tok.eq("[") { + matching = "]" + result = patternList{newOptional(pl...)} + } + moved := tokens.move() + if !moved.eq(matching) { + return nil, tokens.errorFunc("unmatched '%s', expected: '%s' got: '%s'", tok, matching, moved) + } + return result, nil + } else if tok.eq("options") { + tokens.move() + return patternList{newOptionsShortcut()}, nil + } else if tok.hasPrefix("--") && !tok.eq("--") { + return parseLong(tokens, options) + } else if tok.hasPrefix("-") && !tok.eq("-") && !tok.eq("--") { + return parseShorts(tokens, options) + } else if tok.hasPrefix("<") && tok.hasSuffix(">") || tok.isUpper() { + return patternList{newArgument(tokens.move().String(), nil)}, nil + } + return patternList{newCommand(tokens.move().String(), false)}, nil +} + +func parseLong(tokens *tokenList, options *patternList) (patternList, error) { + // long ::= '--' chars [ ( ' ' | '=' ) chars ] ; + long, eq, v := stringPartition(tokens.move().String(), "=") + var value interface{} + var opt *pattern + if eq == "" && v == "" { + value = nil + } else { + value = v + } + + if !strings.HasPrefix(long, "--") { + return nil, newError("long option '%s' doesn't start with --", long) + } + similar := patternList{} + for _, o := range *options { + if o.long == long { + similar = append(similar, o) + } + } + if tokens.err == errorUser && len(similar) == 0 { // if no exact match + similar = patternList{} + for _, o := range *options { + if strings.HasPrefix(o.long, long) { + similar = append(similar, o) + } + } + } + if len(similar) > 1 { // might be simply specified ambiguously 2+ times? + similarLong := make([]string, len(similar)) + for i, s := range similar { + similarLong[i] = s.long + } + return nil, tokens.errorFunc("%s is not a unique prefix: %s?", long, strings.Join(similarLong, ", ")) + } else if len(similar) < 1 { + argcount := 0 + if eq == "=" { + argcount = 1 + } + opt = newOption("", long, argcount, false) + *options = append(*options, opt) + if tokens.err == errorUser { + var val interface{} + if argcount > 0 { + val = value + } else { + val = true + } + opt = newOption("", long, argcount, val) + } + } else { + opt = newOption(similar[0].short, similar[0].long, similar[0].argcount, similar[0].value) + if opt.argcount == 0 { + if value != nil { + return nil, tokens.errorFunc("%s must not have an argument", opt.long) + } + } else { + if value == nil { + if tokens.current().match(true, "--") { + return nil, tokens.errorFunc("%s requires argument", opt.long) + } + moved := tokens.move() + if moved != nil { + value = moved.String() // only set as string if not nil + } + } + } + if tokens.err == errorUser { + if value != nil { + opt.value = value + } else { + opt.value = true + } + } + } + + return patternList{opt}, nil +} + +func parseShorts(tokens *tokenList, options *patternList) (patternList, error) { + // shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ; + tok := tokens.move() + if !tok.hasPrefix("-") || tok.hasPrefix("--") { + return nil, newError("short option '%s' doesn't start with -", tok) + } + left := strings.TrimLeft(tok.String(), "-") + parsed := patternList{} + for left != "" { + var opt *pattern + short := "-" + left[0:1] + left = left[1:] + similar := patternList{} + for _, o := range *options { + if o.short == short { + similar = append(similar, o) + } + } + if len(similar) > 1 { + return nil, tokens.errorFunc("%s is specified ambiguously %d times", short, len(similar)) + } else if len(similar) < 1 { + opt = newOption(short, "", 0, false) + *options = append(*options, opt) + if tokens.err == errorUser { + opt = newOption(short, "", 0, true) + } + } else { // why copying is necessary here? + opt = newOption(short, similar[0].long, similar[0].argcount, similar[0].value) + var value interface{} + if opt.argcount > 0 { + if left == "" { + if tokens.current().match(true, "--") { + return nil, tokens.errorFunc("%s requires argument", short) + } + value = tokens.move().String() + } else { + value = left + left = "" + } + } + if tokens.err == errorUser { + if value != nil { + opt.value = value + } else { + opt.value = true + } + } + } + parsed = append(parsed, opt) + } + return parsed, nil +} + +func newTokenList(source []string, err errorType) *tokenList { + errorFunc := newError + if err == errorUser { + errorFunc = newUserError + } else if err == errorLanguage { + errorFunc = newLanguageError + } + return &tokenList{source, errorFunc, err} +} + +func tokenListFromString(source string) *tokenList { + return newTokenList(strings.Fields(source), errorUser) +} + +func tokenListFromPattern(source string) *tokenList { + p := regexp.MustCompile(`([\[\]\(\)\|]|\.\.\.)`) + source = p.ReplaceAllString(source, ` $1 `) + p = regexp.MustCompile(`\s+|(\S*<.*?>)`) + split := p.Split(source, -1) + match := p.FindAllStringSubmatch(source, -1) + var result []string + l := len(split) + for i := 0; i < l; i++ { + if len(split[i]) > 0 { + result = append(result, split[i]) + } + if i < l-1 && len(match[i][1]) > 0 { + result = append(result, match[i][1]) + } + } + return newTokenList(result, errorLanguage) +} + +func formalUsage(section string) (string, error) { + _, _, section = stringPartition(section, ":") // drop "usage:" + pu := strings.Fields(section) + + if len(pu) == 0 { + return "", newLanguageError("no fields found in usage (perhaps a spacing error).") + } + + result := "( " + for _, s := range pu[1:] { + if s == pu[0] { + result += ") | ( " + } else { + result += s + " " + } + } + result += ")" + + return result, nil +} + +func extras(help bool, version string, options patternList, doc string) string { + if help { + for _, o := range options { + if (o.name == "-h" || o.name == "--help") && o.value == true { + return strings.Trim(doc, "\n") + } + } + } + if version != "" { + for _, o := range options { + if (o.name == "--version") && o.value == true { + return version + } + } + } + return "" +} + +type errorType int + +const ( + errorUser errorType = iota + errorLanguage +) + +func (e errorType) String() string { + switch e { + case errorUser: + return "errorUser" + case errorLanguage: + return "errorLanguage" + } + return "" +} + +// UserError records an error with program arguments. +type UserError struct { + msg string + Usage string +} + +func (e UserError) Error() string { + return e.msg +} +func newUserError(msg string, f ...interface{}) error { + return &UserError{fmt.Sprintf(msg, f...), ""} +} + +// LanguageError records an error with the doc string. +type LanguageError struct { + msg string +} + +func (e LanguageError) Error() string { + return e.msg +} +func newLanguageError(msg string, f ...interface{}) error { + return &LanguageError{fmt.Sprintf(msg, f...)} +} + +var newError = fmt.Errorf + +type tokenList struct { + tokens []string + errorFunc func(string, ...interface{}) error + err errorType +} +type token string + +func (t *token) eq(s string) bool { + if t == nil { + return false + } + return string(*t) == s +} +func (t *token) match(matchNil bool, tokenStrings ...string) bool { + if t == nil && matchNil { + return true + } else if t == nil && !matchNil { + return false + } + + for _, tok := range tokenStrings { + if tok == string(*t) { + return true + } + } + return false +} +func (t *token) hasPrefix(prefix string) bool { + if t == nil { + return false + } + return strings.HasPrefix(string(*t), prefix) +} +func (t *token) hasSuffix(suffix string) bool { + if t == nil { + return false + } + return strings.HasSuffix(string(*t), suffix) +} +func (t *token) isUpper() bool { + if t == nil { + return false + } + return isStringUppercase(string(*t)) +} +func (t *token) String() string { + if t == nil { + return "" + } + return string(*t) +} + +func (tl *tokenList) current() *token { + if len(tl.tokens) > 0 { + return (*token)(&(tl.tokens[0])) + } + return nil +} + +func (tl *tokenList) length() int { + return len(tl.tokens) +} + +func (tl *tokenList) move() *token { + if len(tl.tokens) > 0 { + t := tl.tokens[0] + tl.tokens = tl.tokens[1:] + return (*token)(&t) + } + return nil +} + +type patternType uint + +const ( + // leaf + patternArgument patternType = 1 << iota + patternCommand + patternOption + + // branch + patternRequired + patternOptionAL + patternOptionSSHORTCUT // Marker/placeholder for [options] shortcut. + patternOneOrMore + patternEither + + patternLeaf = patternArgument + + patternCommand + + patternOption + patternBranch = patternRequired + + patternOptionAL + + patternOptionSSHORTCUT + + patternOneOrMore + + patternEither + patternAll = patternLeaf + patternBranch + patternDefault = 0 +) + +func (pt patternType) String() string { + switch pt { + case patternArgument: + return "argument" + case patternCommand: + return "command" + case patternOption: + return "option" + case patternRequired: + return "required" + case patternOptionAL: + return "optional" + case patternOptionSSHORTCUT: + return "optionsshortcut" + case patternOneOrMore: + return "oneormore" + case patternEither: + return "either" + case patternLeaf: + return "leaf" + case patternBranch: + return "branch" + case patternAll: + return "all" + case patternDefault: + return "default" + } + return "" +} + +type pattern struct { + t patternType + + children patternList + + name string + value interface{} + + short string + long string + argcount int +} + +type patternList []*pattern + +func newBranchPattern(t patternType, pl ...*pattern) *pattern { + var p pattern + p.t = t + p.children = make(patternList, len(pl)) + copy(p.children, pl) + return &p +} + +func newRequired(pl ...*pattern) *pattern { + return newBranchPattern(patternRequired, pl...) +} + +func newEither(pl ...*pattern) *pattern { + return newBranchPattern(patternEither, pl...) +} + +func newOneOrMore(pl ...*pattern) *pattern { + return newBranchPattern(patternOneOrMore, pl...) +} + +func newOptional(pl ...*pattern) *pattern { + return newBranchPattern(patternOptionAL, pl...) +} + +func newOptionsShortcut() *pattern { + var p pattern + p.t = patternOptionSSHORTCUT + return &p +} + +func newLeafPattern(t patternType, name string, value interface{}) *pattern { + // default: value=nil + var p pattern + p.t = t + p.name = name + p.value = value + return &p +} + +func newArgument(name string, value interface{}) *pattern { + // default: value=nil + return newLeafPattern(patternArgument, name, value) +} + +func newCommand(name string, value interface{}) *pattern { + // default: value=false + var p pattern + p.t = patternCommand + p.name = name + p.value = value + return &p +} + +func newOption(short, long string, argcount int, value interface{}) *pattern { + // default: "", "", 0, false + var p pattern + p.t = patternOption + p.short = short + p.long = long + if long != "" { + p.name = long + } else { + p.name = short + } + p.argcount = argcount + if value == false && argcount > 0 { + p.value = nil + } else { + p.value = value + } + return &p +} + +func (p *pattern) flat(types patternType) (patternList, error) { + if p.t&patternLeaf != 0 { + if types == patternDefault { + types = patternAll + } + if p.t&types != 0 { + return patternList{p}, nil + } + return patternList{}, nil + } + + if p.t&patternBranch != 0 { + if p.t&types != 0 { + return patternList{p}, nil + } + result := patternList{} + for _, child := range p.children { + childFlat, err := child.flat(types) + if err != nil { + return nil, err + } + result = append(result, childFlat...) + } + return result, nil + } + return nil, newError("unknown pattern type: %d, %d", p.t, types) +} + +func (p *pattern) fix() error { + err := p.fixIdentities(nil) + if err != nil { + return err + } + p.fixRepeatingArguments() + return nil +} + +func (p *pattern) fixIdentities(uniq patternList) error { + // Make pattern-tree tips point to same object if they are equal. + if p.t&patternBranch == 0 { + return nil + } + if uniq == nil { + pFlat, err := p.flat(patternDefault) + if err != nil { + return err + } + uniq = pFlat.unique() + } + for i, child := range p.children { + if child.t&patternBranch == 0 { + ind, err := uniq.index(child) + if err != nil { + return err + } + p.children[i] = uniq[ind] + } else { + err := child.fixIdentities(uniq) + if err != nil { + return err + } + } + } + return nil +} + +func (p *pattern) fixRepeatingArguments() { + // Fix elements that should accumulate/increment values. + var either []patternList + + for _, child := range p.transform().children { + either = append(either, child.children) + } + for _, cas := range either { + casMultiple := patternList{} + for _, e := range cas { + if cas.count(e) > 1 { + casMultiple = append(casMultiple, e) + } + } + for _, e := range casMultiple { + if e.t == patternArgument || e.t == patternOption && e.argcount > 0 { + switch e.value.(type) { + case string: + e.value = strings.Fields(e.value.(string)) + case []string: + default: + e.value = []string{} + } + } + if e.t == patternCommand || e.t == patternOption && e.argcount == 0 { + e.value = 0 + } + } + } +} + +func (p *pattern) match(left *patternList, collected *patternList) (bool, *patternList, *patternList) { + if collected == nil { + collected = &patternList{} + } + if p.t&patternRequired != 0 { + l := left + c := collected + for _, p := range p.children { + var matched bool + matched, l, c = p.match(l, c) + if !matched { + return false, left, collected + } + } + return true, l, c + } else if p.t&patternOptionAL != 0 || p.t&patternOptionSSHORTCUT != 0 { + for _, p := range p.children { + _, left, collected = p.match(left, collected) + } + return true, left, collected + } else if p.t&patternOneOrMore != 0 { + if len(p.children) != 1 { + panic("OneOrMore.match(): assert len(p.children) == 1") + } + l := left + c := collected + var lAlt *patternList + matched := true + times := 0 + for matched { + // could it be that something didn't match but changed l or c? + matched, l, c = p.children[0].match(l, c) + if matched { + times++ + } + if lAlt == l { + break + } + lAlt = l + } + if times >= 1 { + return true, l, c + } + return false, left, collected + } else if p.t&patternEither != 0 { + type outcomeStruct struct { + matched bool + left *patternList + collected *patternList + length int + } + outcomes := []outcomeStruct{} + for _, p := range p.children { + matched, l, c := p.match(left, collected) + outcome := outcomeStruct{matched, l, c, len(*l)} + if matched { + outcomes = append(outcomes, outcome) + } + } + if len(outcomes) > 0 { + minLen := outcomes[0].length + minIndex := 0 + for i, v := range outcomes { + if v.length < minLen { + minIndex = i + } + } + return outcomes[minIndex].matched, outcomes[minIndex].left, outcomes[minIndex].collected + } + return false, left, collected + } else if p.t&patternLeaf != 0 { + pos, match := p.singleMatch(left) + var increment interface{} + if match == nil { + return false, left, collected + } + leftAlt := make(patternList, len((*left)[:pos]), len((*left)[:pos])+len((*left)[pos+1:])) + copy(leftAlt, (*left)[:pos]) + leftAlt = append(leftAlt, (*left)[pos+1:]...) + sameName := patternList{} + for _, a := range *collected { + if a.name == p.name { + sameName = append(sameName, a) + } + } + + switch p.value.(type) { + case int, []string: + switch p.value.(type) { + case int: + increment = 1 + case []string: + switch match.value.(type) { + case string: + increment = []string{match.value.(string)} + default: + increment = match.value + } + } + if len(sameName) == 0 { + match.value = increment + collectedMatch := make(patternList, len(*collected), len(*collected)+1) + copy(collectedMatch, *collected) + collectedMatch = append(collectedMatch, match) + return true, &leftAlt, &collectedMatch + } + switch sameName[0].value.(type) { + case int: + sameName[0].value = sameName[0].value.(int) + increment.(int) + case []string: + sameName[0].value = append(sameName[0].value.([]string), increment.([]string)...) + } + return true, &leftAlt, collected + } + collectedMatch := make(patternList, len(*collected), len(*collected)+1) + copy(collectedMatch, *collected) + collectedMatch = append(collectedMatch, match) + return true, &leftAlt, &collectedMatch + } + panic("unmatched type") +} + +func (p *pattern) singleMatch(left *patternList) (int, *pattern) { + if p.t&patternArgument != 0 { + for n, pat := range *left { + if pat.t&patternArgument != 0 { + return n, newArgument(p.name, pat.value) + } + } + return -1, nil + } else if p.t&patternCommand != 0 { + for n, pat := range *left { + if pat.t&patternArgument != 0 { + if pat.value == p.name { + return n, newCommand(p.name, true) + } + break + } + } + return -1, nil + } else if p.t&patternOption != 0 { + for n, pat := range *left { + if p.name == pat.name { + return n, pat + } + } + return -1, nil + } + panic("unmatched type") +} + +func (p *pattern) String() string { + if p.t&patternOption != 0 { + return fmt.Sprintf("%s(%s, %s, %d, %+v)", p.t, p.short, p.long, p.argcount, p.value) + } else if p.t&patternLeaf != 0 { + return fmt.Sprintf("%s(%s, %+v)", p.t, p.name, p.value) + } else if p.t&patternBranch != 0 { + result := "" + for i, child := range p.children { + if i > 0 { + result += ", " + } + result += child.String() + } + return fmt.Sprintf("%s(%s)", p.t, result) + } + panic("unmatched type") +} + +func (p *pattern) transform() *pattern { + /* + Expand pattern into an (almost) equivalent one, but with single Either. + + Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) + Quirks: [-a] => (-a), (-a...) => (-a -a) + */ + result := []patternList{} + groups := []patternList{patternList{p}} + parents := patternRequired + + patternOptionAL + + patternOptionSSHORTCUT + + patternEither + + patternOneOrMore + for len(groups) > 0 { + children := groups[0] + groups = groups[1:] + var child *pattern + for _, c := range children { + if c.t&parents != 0 { + child = c + break + } + } + if child != nil { + children.remove(child) + if child.t&patternEither != 0 { + for _, c := range child.children { + r := patternList{} + r = append(r, c) + r = append(r, children...) + groups = append(groups, r) + } + } else if child.t&patternOneOrMore != 0 { + r := patternList{} + r = append(r, child.children.double()...) + r = append(r, children...) + groups = append(groups, r) + } else { + r := patternList{} + r = append(r, child.children...) + r = append(r, children...) + groups = append(groups, r) + } + } else { + result = append(result, children) + } + } + either := patternList{} + for _, e := range result { + either = append(either, newRequired(e...)) + } + return newEither(either...) +} + +func (p *pattern) eq(other *pattern) bool { + return reflect.DeepEqual(p, other) +} + +func (pl patternList) unique() patternList { + table := make(map[string]bool) + result := patternList{} + for _, v := range pl { + if !table[v.String()] { + table[v.String()] = true + result = append(result, v) + } + } + return result +} + +func (pl patternList) index(p *pattern) (int, error) { + for i, c := range pl { + if c.eq(p) { + return i, nil + } + } + return -1, newError("%s not in list", p) +} + +func (pl patternList) count(p *pattern) int { + count := 0 + for _, c := range pl { + if c.eq(p) { + count++ + } + } + return count +} + +func (pl patternList) diff(l patternList) patternList { + lAlt := make(patternList, len(l)) + copy(lAlt, l) + result := make(patternList, 0, len(pl)) + for _, v := range pl { + if v != nil { + match := false + for i, w := range lAlt { + if w.eq(v) { + match = true + lAlt[i] = nil + break + } + } + if match == false { + result = append(result, v) + } + } + } + return result +} + +func (pl patternList) double() patternList { + l := len(pl) + result := make(patternList, l*2) + copy(result, pl) + copy(result[l:2*l], pl) + return result +} + +func (pl *patternList) remove(p *pattern) { + (*pl) = pl.diff(patternList{p}) +} + +func (pl patternList) dictionary() map[string]interface{} { + dict := make(map[string]interface{}) + for _, a := range pl { + dict[a.name] = a.value + } + return dict +} + +func stringPartition(s, sep string) (string, string, string) { + sepPos := strings.Index(s, sep) + if sepPos == -1 { // no seperator found + return s, "", "" + } + split := strings.SplitN(s, sep, 2) + return split[0], sep, split[1] +} + +// returns true if all cased characters in the string are uppercase +// and there are there is at least one cased charcter +func isStringUppercase(s string) bool { + if strings.ToUpper(s) != s { + return false + } + for _, c := range []rune(s) { + if unicode.IsUpper(c) { + return true + } + } + return false +} diff --git a/Godeps/_workspace/src/github.com/docopt/docopt-go/test_golang.docopt b/Godeps/_workspace/src/github.com/docopt/docopt-go/test_golang.docopt new file mode 100644 index 0000000..323fd67 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docopt/docopt-go/test_golang.docopt @@ -0,0 +1,9 @@ +r"""usage: prog [NAME_-2]...""" +$ prog 10 20 +{"NAME_-2": ["10", "20"]} + +$ prog 10 +{"NAME_-2": ["10"]} + +$ prog +{"NAME_-2": []} diff --git a/Godeps/_workspace/src/github.com/docopt/docopt-go/testcases.docopt b/Godeps/_workspace/src/github.com/docopt/docopt-go/testcases.docopt new file mode 100644 index 0000000..efe9a07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docopt/docopt-go/testcases.docopt @@ -0,0 +1,957 @@ +r"""Usage: prog + +""" +$ prog +{} + +$ prog --xxx +"user-error" + + +r"""Usage: prog [options] + +Options: -a All. + +""" +$ prog +{"-a": false} + +$ prog -a +{"-a": true} + +$ prog -x +"user-error" + + +r"""Usage: prog [options] + +Options: --all All. + +""" +$ prog +{"--all": false} + +$ prog --all +{"--all": true} + +$ prog --xxx +"user-error" + + +r"""Usage: prog [options] + +Options: -v, --verbose Verbose. + +""" +$ prog --verbose +{"--verbose": true} + +$ prog --ver +{"--verbose": true} + +$ prog -v +{"--verbose": true} + + +r"""Usage: prog [options] + +Options: -p PATH + +""" +$ prog -p home/ +{"-p": "home/"} + +$ prog -phome/ +{"-p": "home/"} + +$ prog -p +"user-error" + + +r"""Usage: prog [options] + +Options: --path + +""" +$ prog --path home/ +{"--path": "home/"} + +$ prog --path=home/ +{"--path": "home/"} + +$ prog --pa home/ +{"--path": "home/"} + +$ prog --pa=home/ +{"--path": "home/"} + +$ prog --path +"user-error" + + +r"""Usage: prog [options] + +Options: -p PATH, --path= Path to files. + +""" +$ prog -proot +{"--path": "root"} + + +r"""Usage: prog [options] + +Options: -p --path PATH Path to files. + +""" +$ prog -p root +{"--path": "root"} + +$ prog --path root +{"--path": "root"} + + +r"""Usage: prog [options] + +Options: + -p PATH Path to files [default: ./] + +""" +$ prog +{"-p": "./"} + +$ prog -phome +{"-p": "home"} + + +r"""UsAgE: prog [options] + +OpTiOnS: --path= Path to files + [dEfAuLt: /root] + +""" +$ prog +{"--path": "/root"} + +$ prog --path=home +{"--path": "home"} + + +r"""usage: prog [options] + +options: + -a Add + -r Remote + -m Message + +""" +$ prog -a -r -m Hello +{"-a": true, + "-r": true, + "-m": "Hello"} + +$ prog -armyourass +{"-a": true, + "-r": true, + "-m": "yourass"} + +$ prog -a -r +{"-a": true, + "-r": true, + "-m": null} + + +r"""Usage: prog [options] + +Options: --version + --verbose + +""" +$ prog --version +{"--version": true, + "--verbose": false} + +$ prog --verbose +{"--version": false, + "--verbose": true} + +$ prog --ver +"user-error" + +$ prog --verb +{"--version": false, + "--verbose": true} + + +r"""usage: prog [-a -r -m ] + +options: + -a Add + -r Remote + -m Message + +""" +$ prog -armyourass +{"-a": true, + "-r": true, + "-m": "yourass"} + + +r"""usage: prog [-armmsg] + +options: -a Add + -r Remote + -m Message + +""" +$ prog -a -r -m Hello +{"-a": true, + "-r": true, + "-m": "Hello"} + + +r"""usage: prog -a -b + +options: + -a + -b + +""" +$ prog -a -b +{"-a": true, "-b": true} + +$ prog -b -a +{"-a": true, "-b": true} + +$ prog -a +"user-error" + +$ prog +"user-error" + + +r"""usage: prog (-a -b) + +options: -a + -b + +""" +$ prog -a -b +{"-a": true, "-b": true} + +$ prog -b -a +{"-a": true, "-b": true} + +$ prog -a +"user-error" + +$ prog +"user-error" + + +r"""usage: prog [-a] -b + +options: -a + -b + +""" +$ prog -a -b +{"-a": true, "-b": true} + +$ prog -b -a +{"-a": true, "-b": true} + +$ prog -a +"user-error" + +$ prog -b +{"-a": false, "-b": true} + +$ prog +"user-error" + + +r"""usage: prog [(-a -b)] + +options: -a + -b + +""" +$ prog -a -b +{"-a": true, "-b": true} + +$ prog -b -a +{"-a": true, "-b": true} + +$ prog -a +"user-error" + +$ prog -b +"user-error" + +$ prog +{"-a": false, "-b": false} + + +r"""usage: prog (-a|-b) + +options: -a + -b + +""" +$ prog -a -b +"user-error" + +$ prog +"user-error" + +$ prog -a +{"-a": true, "-b": false} + +$ prog -b +{"-a": false, "-b": true} + + +r"""usage: prog [ -a | -b ] + +options: -a + -b + +""" +$ prog -a -b +"user-error" + +$ prog +{"-a": false, "-b": false} + +$ prog -a +{"-a": true, "-b": false} + +$ prog -b +{"-a": false, "-b": true} + + +r"""usage: prog """ +$ prog 10 +{"": "10"} + +$ prog 10 20 +"user-error" + +$ prog +"user-error" + + +r"""usage: prog []""" +$ prog 10 +{"": "10"} + +$ prog 10 20 +"user-error" + +$ prog +{"": null} + + +r"""usage: prog """ +$ prog 10 20 40 +{"": "10", "": "20", "": "40"} + +$ prog 10 20 +"user-error" + +$ prog +"user-error" + + +r"""usage: prog [ ]""" +$ prog 10 20 40 +{"": "10", "": "20", "": "40"} + +$ prog 10 20 +{"": "10", "": "20", "": null} + +$ prog +"user-error" + + +r"""usage: prog [ | ]""" +$ prog 10 20 40 +"user-error" + +$ prog 20 40 +{"": null, "": "20", "": "40"} + +$ prog +{"": null, "": null, "": null} + + +r"""usage: prog ( --all | ) + +options: + --all + +""" +$ prog 10 --all +{"": "10", "--all": true, "": null} + +$ prog 10 +{"": null, "--all": false, "": "10"} + +$ prog +"user-error" + + +r"""usage: prog [ ]""" +$ prog 10 20 +{"": ["10", "20"]} + +$ prog 10 +{"": ["10"]} + +$ prog +{"": []} + + +r"""usage: prog [( )]""" +$ prog 10 20 +{"": ["10", "20"]} + +$ prog 10 +"user-error" + +$ prog +{"": []} + + +r"""usage: prog NAME...""" +$ prog 10 20 +{"NAME": ["10", "20"]} + +$ prog 10 +{"NAME": ["10"]} + +$ prog +"user-error" + + +r"""usage: prog [NAME]...""" +$ prog 10 20 +{"NAME": ["10", "20"]} + +$ prog 10 +{"NAME": ["10"]} + +$ prog +{"NAME": []} + + +r"""usage: prog [NAME...]""" +$ prog 10 20 +{"NAME": ["10", "20"]} + +$ prog 10 +{"NAME": ["10"]} + +$ prog +{"NAME": []} + + +r"""usage: prog [NAME [NAME ...]]""" +$ prog 10 20 +{"NAME": ["10", "20"]} + +$ prog 10 +{"NAME": ["10"]} + +$ prog +{"NAME": []} + + +r"""usage: prog (NAME | --foo NAME) + +options: --foo + +""" +$ prog 10 +{"NAME": "10", "--foo": false} + +$ prog --foo 10 +{"NAME": "10", "--foo": true} + +$ prog --foo=10 +"user-error" + + +r"""usage: prog (NAME | --foo) [--bar | NAME] + +options: --foo +options: --bar + +""" +$ prog 10 +{"NAME": ["10"], "--foo": false, "--bar": false} + +$ prog 10 20 +{"NAME": ["10", "20"], "--foo": false, "--bar": false} + +$ prog --foo --bar +{"NAME": [], "--foo": true, "--bar": true} + + +r"""Naval Fate. + +Usage: + prog ship new ... + prog ship [] move [--speed=] + prog ship shoot + prog mine (set|remove) [--moored|--drifting] + prog -h | --help + prog --version + +Options: + -h --help Show this screen. + --version Show version. + --speed= Speed in knots [default: 10]. + --moored Mored (anchored) mine. + --drifting Drifting mine. + +""" +$ prog ship Guardian move 150 300 --speed=20 +{"--drifting": false, + "--help": false, + "--moored": false, + "--speed": "20", + "--version": false, + "": ["Guardian"], + "": "150", + "": "300", + "mine": false, + "move": true, + "new": false, + "remove": false, + "set": false, + "ship": true, + "shoot": false} + + +r"""usage: prog --hello""" +$ prog --hello +{"--hello": true} + + +r"""usage: prog [--hello=]""" +$ prog +{"--hello": null} + +$ prog --hello wrld +{"--hello": "wrld"} + + +r"""usage: prog [-o]""" +$ prog +{"-o": false} + +$ prog -o +{"-o": true} + + +r"""usage: prog [-opr]""" +$ prog -op +{"-o": true, "-p": true, "-r": false} + + +r"""usage: prog --aabb | --aa""" +$ prog --aa +{"--aabb": false, "--aa": true} + +$ prog --a +"user-error" # not a unique prefix + +# +# Counting number of flags +# + +r"""Usage: prog -v""" +$ prog -v +{"-v": true} + + +r"""Usage: prog [-v -v]""" +$ prog +{"-v": 0} + +$ prog -v +{"-v": 1} + +$ prog -vv +{"-v": 2} + + +r"""Usage: prog -v ...""" +$ prog +"user-error" + +$ prog -v +{"-v": 1} + +$ prog -vv +{"-v": 2} + +$ prog -vvvvvv +{"-v": 6} + + +r"""Usage: prog [-v | -vv | -vvv] + +This one is probably most readable user-friednly variant. + +""" +$ prog +{"-v": 0} + +$ prog -v +{"-v": 1} + +$ prog -vv +{"-v": 2} + +$ prog -vvvv +"user-error" + + +r"""usage: prog [--ver --ver]""" +$ prog --ver --ver +{"--ver": 2} + + +# +# Counting commands +# + +r"""usage: prog [go]""" +$ prog go +{"go": true} + + +r"""usage: prog [go go]""" +$ prog +{"go": 0} + +$ prog go +{"go": 1} + +$ prog go go +{"go": 2} + +$ prog go go go +"user-error" + +r"""usage: prog go...""" +$ prog go go go go go +{"go": 5} + +# +# [options] does not include options from usage-pattern +# +r"""usage: prog [options] [-a] + +options: -a + -b +""" +$ prog -a +{"-a": true, "-b": false} + +$ prog -aa +"user-error" + +# +# Test [options] shourtcut +# + +r"""Usage: prog [options] A +Options: + -q Be quiet + -v Be verbose. + +""" +$ prog arg +{"A": "arg", "-v": false, "-q": false} + +$ prog -v arg +{"A": "arg", "-v": true, "-q": false} + +$ prog -q arg +{"A": "arg", "-v": false, "-q": true} + +# +# Test single dash +# + +r"""usage: prog [-]""" + +$ prog - +{"-": true} + +$ prog +{"-": false} + +# +# If argument is repeated, its value should always be a list +# + +r"""usage: prog [NAME [NAME ...]]""" + +$ prog a b +{"NAME": ["a", "b"]} + +$ prog +{"NAME": []} + +# +# Option's argument defaults to null/None +# + +r"""usage: prog [options] +options: + -a Add + -m Message + +""" +$ prog -a +{"-m": null, "-a": true} + +# +# Test options without description +# + +r"""usage: prog --hello""" +$ prog --hello +{"--hello": true} + +r"""usage: prog [--hello=]""" +$ prog +{"--hello": null} + +$ prog --hello wrld +{"--hello": "wrld"} + +r"""usage: prog [-o]""" +$ prog +{"-o": false} + +$ prog -o +{"-o": true} + +r"""usage: prog [-opr]""" +$ prog -op +{"-o": true, "-p": true, "-r": false} + +r"""usage: git [-v | --verbose]""" +$ prog -v +{"-v": true, "--verbose": false} + +r"""usage: git remote [-v | --verbose]""" +$ prog remote -v +{"remote": true, "-v": true, "--verbose": false} + +# +# Test empty usage pattern +# + +r"""usage: prog""" +$ prog +{} + +r"""usage: prog + prog +""" +$ prog 1 2 +{"": "1", "": "2"} + +$ prog +{"": null, "": null} + +r"""usage: prog + prog +""" +$ prog +{"": null, "": null} + +# +# Option's argument should not capture default value from usage pattern +# + +r"""usage: prog [--file=]""" +$ prog +{"--file": null} + +r"""usage: prog [--file=] + +options: --file + +""" +$ prog +{"--file": null} + +r"""Usage: prog [-a ] + +Options: -a, --address TCP address [default: localhost:6283]. + +""" +$ prog +{"--address": "localhost:6283"} + +# +# If option with argument could be repeated, +# its arguments should be accumulated into a list +# + +r"""usage: prog --long= ...""" + +$ prog --long one +{"--long": ["one"]} + +$ prog --long one --long two +{"--long": ["one", "two"]} + +# +# Test multiple elements repeated at once +# + +r"""usage: prog (go --speed=)...""" +$ prog go left --speed=5 go right --speed=9 +{"go": 2, "": ["left", "right"], "--speed": ["5", "9"]} + +# +# Required options should work with option shortcut +# + +r"""usage: prog [options] -a + +options: -a + +""" +$ prog -a +{"-a": true} + +# +# If option could be repeated its defaults should be split into a list +# + +r"""usage: prog [-o ]... + +options: -o [default: x] + +""" +$ prog -o this -o that +{"-o": ["this", "that"]} + +$ prog +{"-o": ["x"]} + +r"""usage: prog [-o ]... + +options: -o [default: x y] + +""" +$ prog -o this +{"-o": ["this"]} + +$ prog +{"-o": ["x", "y"]} + +# +# Test stacked option's argument +# + +r"""usage: prog -pPATH + +options: -p PATH + +""" +$ prog -pHOME +{"-p": "HOME"} + +# +# Issue 56: Repeated mutually exclusive args give nested lists sometimes +# + +r"""Usage: foo (--xx=x|--yy=y)...""" +$ prog --xx=1 --yy=2 +{"--xx": ["1"], "--yy": ["2"]} + +# +# POSIXly correct tokenization +# + +r"""usage: prog []""" +$ prog f.txt +{"": "f.txt"} + +r"""usage: prog [--input=]...""" +$ prog --input a.txt --input=b.txt +{"--input": ["a.txt", "b.txt"]} + +# +# Issue 85: `[options]` shourtcut with multiple subcommands +# + +r"""usage: prog good [options] + prog fail [options] + +options: --loglevel=N + +""" +$ prog fail --loglevel 5 +{"--loglevel": "5", "fail": true, "good": false} + +# +# Usage-section syntax +# + +r"""usage:prog --foo""" +$ prog --foo +{"--foo": true} + +r"""PROGRAM USAGE: prog --foo""" +$ prog --foo +{"--foo": true} + +r"""Usage: prog --foo + prog --bar +NOT PART OF SECTION""" +$ prog --foo +{"--foo": true, "--bar": false} + +r"""Usage: + prog --foo + prog --bar + +NOT PART OF SECTION""" +$ prog --foo +{"--foo": true, "--bar": false} + +r"""Usage: + prog --foo + prog --bar +NOT PART OF SECTION""" +$ prog --foo +{"--foo": true, "--bar": false} + +# +# Options-section syntax +# + +r"""Usage: prog [options] + +global options: --foo +local options: --baz + --bar +other options: + --egg + --spam +-not-an-option- + +""" +$ prog --baz --egg +{"--foo": false, "--baz": true, "--bar": false, "--egg": true, "--spam": false} diff --git a/Makefile b/Makefile index 797e11f..4dbf0d9 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,9 @@ build: godep fmt frps frpc godep: @go get github.com/tools/godep - godep restore fmt: - @GOPATH=$(NEW_GOPATH) godep go fmt ./... + GOPATH=$(NEW_GOPATH) godep go fmt ./... frps: GOPATH=$(NEW_GOPATH) godep go build -o bin/frps ./src/frp/cmd/frps @@ -19,4 +18,4 @@ frpc: GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc test: - @GOPATH=$(NEW_GOPATH) godep go test ./... + @GOPATH=$(NEW_GOPATH) godep go test -v ./... diff --git a/README.md b/README.md index 766797f..28a1361 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,32 @@ [![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp) -A fast reverse proxy. +## What is frp? + +frp is a fast reverse proxy which can help you expose a local server behind a NAT or firewall to the internet. + +## Status + +frp is under development and you can try it with available version 0.1. + +## Quick Start + +Read the [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md) + +## Architecture + +![architecture](doc/pic/architecture.png) + +## What can I do with frp? + +* Expose any http service behind a NAT or firewall to the internet by a server with public IP address. +* Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address. +* Inspect all http requests/responses that are transmitted over the tunnel(future). + +## Contributing + +Interested in getting involved? We would love to help you! + +For simple bug fixes, just submit a PR with the fix and we can discuss the fix directly in the PR. If the fix is more complex, start with an issue. + +If you have some wanderful ideas, send email to fatedier@gmail.com. diff --git a/conf/frpc.ini b/conf/frpc.ini index 447cdc8..09dfeb7 100644 --- a/conf/frpc.ini +++ b/conf/frpc.ini @@ -1,14 +1,14 @@ -# common是必须的section +# [common] is integral section [common] -server_addr = 127.0.0.1 +server_addr = 0.0.0.0 server_port = 7000 -log_file = ./frpc.log +# console or real logFile path like ./frpc.log +log_file = console # debug, info, warn, error log_level = debug -# file, console -log_way = console -# test1即为name +# test1 is the proxy name same as server's configuration [test1] passwd = 123 +local_ip = 127.0.0.1 local_port = 22 diff --git a/conf/frps.ini b/conf/frps.ini index 0c44cb1..0358a7a 100644 --- a/conf/frps.ini +++ b/conf/frps.ini @@ -1,14 +1,13 @@ -# common是必须的section +# [common] is integral section [common] bind_addr = 0.0.0.0 bind_port = 7000 -log_file = ./frps.log +# console or real logFile path like ./frps.log +log_file = console # debug, info, warn, error log_level = debug -# file, console -log_way = console -# test1即为name +# test1 is the proxy name, client will use this name and passwd to connect to server [test1] passwd = 123 bind_addr = 0.0.0.0 diff --git a/doc/pic/architecture.png b/doc/pic/architecture.png new file mode 100644 index 0000000..2ee54fc Binary files /dev/null and b/doc/pic/architecture.png differ diff --git a/doc/quick_start_en.md b/doc/quick_start_en.md new file mode 100644 index 0000000..46fa3c2 --- /dev/null +++ b/doc/quick_start_en.md @@ -0,0 +1,68 @@ +# Quick Start + +frp is easier to use compared with other similar projects. + +We will use a simple demo to demonstrate how to create a connection to server A's ssh port by server B with public IP address x.x.x.x(replace to the real IP address of your server). + +### Download SourceCode + +`go get github.com/fatedier/frp` is recommended, then the code will be copied to the directory `$GOPATH/src/github.com/fatedier/frp`. + +Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`. + +### Compile + +Enter the root directory and execute `make`, then wait until finished. + +**bin** include all executable programs when **conf** include corresponding configuration files. + +### Pre-requirement + +* Go environment. Version of go >= 1.4. +* Godep (if not exist, go get will be executed to download godep when compiling) + +### Deploy + +1. Move `./bin/frps` and `./conf/frps.ini` to any directory of server B. +2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of server A. +3. Modify all configuration files, details in next paragraph. +4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in server B. +5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in server A. +6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in server A). + +### Configuration files + +#### frps.ini + +```ini +[common] +bind_addr = 0.0.0.0 +# for accept connections from frpc +bind_port = 7000 +log_file = ./frps.log +log_level = info + +# test is the custom name of proxy and there can be many proxies with unique name in one configure file +[test] +passwd = 123 +bind_addr = 0.0.0.0 +# finally we connect to server A by this port +listen_port = 6000 +``` + +#### frpc.ini + +```ini +[common] +# server address of frps +server_addr = x.x.x.x +server_port = 7000 +log_file = ./frpc.log +log_level = info + +# test is proxy name same with configure in frps.ini +[test] +passwd = 123 +# local port which need to be transferred +local_port = 22 +``` diff --git a/doc/quick_start_zh.md b/doc/quick_start_zh.md new file mode 100644 index 0000000..61fb3bd --- /dev/null +++ b/doc/quick_start_zh.md @@ -0,0 +1,66 @@ +# frp 使用文档 + +frp 相比于其他项目而言非常易于部署和使用,这里我们用一个简单的示例演示如何通过一台拥有公网IP地址的服务器B,访问处于内网环境中的服务器A的ssh端口,服务器B的IP地址为 x.x.x.x(测试时替换为真实的IP地址)。 + +### 下载源码 + +推荐直接使用 `go get github.com/fatedier/frp` 下载源代码安装,执行命令后代码将会拷贝到 `$GOPATH/src/github.com/fatedier/frp` 目录下。 + +或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。 + +### 编译 + +进入下载后的源码根目录,执行 `make` 命令,等待编译完成。 + +编译完成后, **bin** 目录下是编译好的可执行文件,**conf** 目录下是示例配置文件。 + +### 依赖 + +* go 1.4 以上版本 +* godep (如果检查不存在,编译时会通过 go get 命令安装) + +### 部署 + +1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至服务器B任意目录。 +2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至服务器A任意目录。 +3. 修改两边的配置文件,见下一节说明。 +4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`。 +5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`。 +6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接服务器A({user}替换为服务器A上存在的真实用户)。 + +### 配置文件 + +#### frps.ini + +```ini +[common] +bind_addr = 0.0.0.0 +# 用于接收 frpc 连接的端口 +bind_port = 7000 +log_file = ./frps.log +log_level = info + +# test 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应 +[test] +passwd = 123 +bind_addr = 0.0.0.0 +# 最后将通过此端口访问后端服务 +listen_port = 6000 +``` + +#### frpc.ini + +```ini +[common] +# frps 所在服务器绑定的IP地址 +server_addr = x.x.x.x +server_port = 7000 +log_file = ./frpc.log +log_level = info + +# test需要和 frps.ini 中配置一致 +[test] +passwd = 123 +# 需要转发的本地端口 +local_port = 22 +``` diff --git a/src/frp/cmd/frpc/control.go b/src/frp/cmd/frpc/control.go index 799e8d7..137be06 100644 --- a/src/frp/cmd/frpc/control.go +++ b/src/frp/cmd/frpc/control.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( @@ -142,6 +156,7 @@ func startHeartBeat(c *conn.Conn) { for { time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second) if c != nil && !c.IsClosed() { + log.Debug("Send heartbeat to server") err = c.Write(string(request) + "\n") if err != nil { log.Error("Send hearbeat to server failed! Err:%v", err) @@ -151,5 +166,5 @@ func startHeartBeat(c *conn.Conn) { break } } - log.Debug("Heartbeat exit") + log.Debug("Heartbeat goroutine exit") } diff --git a/src/frp/cmd/frpc/main.go b/src/frp/cmd/frpc/main.go index 0177234..2f8a7f1 100644 --- a/src/frp/cmd/frpc/main.go +++ b/src/frp/cmd/frpc/main.go @@ -1,19 +1,93 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( + "fmt" "os" + "strconv" + "strings" "sync" + docopt "github.com/docopt/docopt-go" + "frp/models/client" "frp/utils/log" + "frp/utils/version" +) + +var ( + configFile string = "./frpc.ini" ) +var usage string = `frpc is the client of frp + +Usage: + frpc [-c config_file] [-L log_file] [--log-level=] [--server-addr=] + frpc -h | --help | --version + +Options: + -c config_file set config file + -L log_file set output log file, including console + --log-level= set log level: debug, info, warn, error + --server-addr= addr which frps is listening for, example: 0.0.0.0:7000 + -h --help show this screen + --version show version +` + func main() { - err := client.LoadConf("./frpc.ini") + // the configures parsed from file will be replaced by those from command line if exist + args, err := docopt.Parse(usage, nil, true, version.Full(), false) + + if args["-c"] != nil { + configFile = args["-c"].(string) + } + err = client.LoadConf(configFile) if err != nil { + fmt.Println(err) os.Exit(-1) } + if args["-L"] != nil { + if args["-L"].(string) == "console" { + client.LogWay = "console" + } else { + client.LogWay = "file" + client.LogFile = args["-L"].(string) + } + } + + if args["--log-level"] != nil { + client.LogLevel = args["--log-level"].(string) + } + + if args["--server-addr"] != nil { + addr := strings.Split(args["--server-addr"].(string), ":") + if len(addr) != 2 { + fmt.Println("--server-addr format error: example 0.0.0.0:7000") + os.Exit(1) + } + serverPort, err := strconv.ParseInt(addr[1], 10, 64) + if err != nil { + fmt.Println("--server-addr format error, example 0.0.0.0:7000") + os.Exit(1) + } + client.ServerAddr = addr[0] + client.ServerPort = serverPort + } + log.InitLog(client.LogWay, client.LogFile, client.LogLevel) // wait until all control goroutine exit diff --git a/src/frp/cmd/frps/control.go b/src/frp/cmd/frps/control.go index 06203aa..b60365b 100644 --- a/src/frp/cmd/frps/control.go +++ b/src/frp/cmd/frps/control.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( diff --git a/src/frp/cmd/frps/main.go b/src/frp/cmd/frps/main.go index c4ce4d7..4c918f0 100644 --- a/src/frp/cmd/frps/main.go +++ b/src/frp/cmd/frps/main.go @@ -1,19 +1,93 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( + "fmt" "os" + "strconv" + "strings" + + docopt "github.com/docopt/docopt-go" "frp/models/server" "frp/utils/conn" "frp/utils/log" + "frp/utils/version" +) + +var ( + configFile string = "./frps.ini" ) +var usage string = `frps is the server of frp + +Usage: + frps [-c config_file] [-L log_file] [--log-level=] [--addr=] + frps -h | --help | --version + +Options: + -c config_file set config file + -L log_file set output log file, including console + --log-level= set log level: debug, info, warn, error + --addr= listen addr for client, example: 0.0.0.0:7000 + -h --help show this screen + --version show version +` + func main() { - err := server.LoadConf("./frps.ini") + // the configures parsed from file will be replaced by those from command line if exist + args, err := docopt.Parse(usage, nil, true, version.Full(), false) + + if args["-c"] != nil { + configFile = args["-c"].(string) + } + err = server.LoadConf(configFile) if err != nil { + fmt.Println(err) os.Exit(-1) } + if args["-L"] != nil { + if args["-L"].(string) == "console" { + server.LogWay = "console" + } else { + server.LogWay = "file" + server.LogFile = args["-L"].(string) + } + } + + if args["--log-level"] != nil { + server.LogLevel = args["--log-level"].(string) + } + + if args["--addr"] != nil { + addr := strings.Split(args["--addr"].(string), ":") + if len(addr) != 2 { + fmt.Println("--addr format error: example 0.0.0.0:7000") + os.Exit(1) + } + bindPort, err := strconv.ParseInt(addr[1], 10, 64) + if err != nil { + fmt.Println("--addr format error, example 0.0.0.0:7000") + os.Exit(1) + } + server.BindAddr = addr[0] + server.BindPort = bindPort + } + log.InitLog(server.LogWay, server.LogFile, server.LogLevel) l, err := conn.Listen(server.BindAddr, server.BindPort) diff --git a/src/frp/models/client/client.go b/src/frp/models/client/client.go index 69d256d..ce99f09 100644 --- a/src/frp/models/client/client.go +++ b/src/frp/models/client/client.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package client import ( @@ -12,11 +26,12 @@ import ( type ProxyClient struct { Name string Passwd string + LocalIp string LocalPort int64 } func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) { - c, err = conn.ConnectServer("127.0.0.1", p.LocalPort) + c, err = conn.ConnectServer(p.LocalIp, p.LocalPort) if err != nil { log.Error("ProxyName [%s], connect to local port error, %v", p.Name, err) } diff --git a/src/frp/models/client/config.go b/src/frp/models/client/config.go index f82e6ae..e41fec8 100644 --- a/src/frp/models/client/config.go +++ b/src/frp/models/client/config.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package client import ( @@ -11,11 +25,11 @@ import ( var ( ServerAddr string = "0.0.0.0" ServerPort int64 = 7000 - LogFile string = "./frpc.log" - LogLevel string = "warn" - LogWay string = "file" - HeartBeatInterval int64 = 5 - HeartBeatTimeout int64 = 30 + LogFile string = "console" + LogWay string = "console" + LogLevel string = "info" + HeartBeatInterval int64 = 20 + HeartBeatTimeout int64 = 90 ) var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient) @@ -43,6 +57,11 @@ func LoadConf(confFile string) (err error) { tmpStr, ok = conf.Get("common", "log_file") if ok { LogFile = tmpStr + if LogFile == "console" { + LogWay = "console" + } else { + LogWay = "file" + } } tmpStr, ok = conf.Get("common", "log_level") @@ -50,12 +69,7 @@ func LoadConf(confFile string) (err error) { LogLevel = tmpStr } - tmpStr, ok = conf.Get("common", "log_way") - if ok { - LogWay = tmpStr - } - - // servers + // proxies for name, section := range conf { if name != "common" { proxyClient := &ProxyClient{} @@ -66,6 +80,12 @@ func LoadConf(confFile string) (err error) { return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyClient.Name) } + proxyClient.LocalIp, ok = section["local_ip"] + if !ok { + // use 127.0.0.1 as default + proxyClient.LocalIp = "127.0.0.1" + } + portStr, ok := section["local_port"] if ok { proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64) diff --git a/src/frp/models/consts/consts.go b/src/frp/models/consts/consts.go index d48332a..56191ff 100644 --- a/src/frp/models/consts/consts.go +++ b/src/frp/models/consts/consts.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package consts // server status diff --git a/src/frp/models/msg/msg.go b/src/frp/models/msg/msg.go index 6555296..5c62bfb 100644 --- a/src/frp/models/msg/msg.go +++ b/src/frp/models/msg/msg.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package msg type GeneralRes struct { diff --git a/src/frp/models/server/config.go b/src/frp/models/server/config.go index ec70071..b2eb63f 100644 --- a/src/frp/models/server/config.go +++ b/src/frp/models/server/config.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package server import ( @@ -10,11 +24,11 @@ import ( // common config var ( BindAddr string = "0.0.0.0" - BindPort int64 = 9527 - LogFile string = "./frps.log" - LogLevel string = "warn" - LogWay string = "file" - HeartBeatTimeout int64 = 30 + BindPort int64 = 7000 + LogFile string = "console" + LogWay string = "console" // console or file + LogLevel string = "info" + HeartBeatTimeout int64 = 90 UserConnTimeout int64 = 10 ) @@ -43,6 +57,11 @@ func LoadConf(confFile string) (err error) { tmpStr, ok = conf.Get("common", "log_file") if ok { LogFile = tmpStr + if LogFile == "console" { + LogWay = "console" + } else { + LogWay = "file" + } } tmpStr, ok = conf.Get("common", "log_level") @@ -50,11 +69,6 @@ func LoadConf(confFile string) (err error) { LogLevel = tmpStr } - tmpStr, ok = conf.Get("common", "log_way") - if ok { - LogWay = tmpStr - } - // servers for name, section := range conf { if name != "common" { diff --git a/src/frp/models/server/server.go b/src/frp/models/server/server.go index e8d6d81..0b2c3d4 100644 --- a/src/frp/models/server/server.go +++ b/src/frp/models/server/server.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package server import ( @@ -128,7 +142,9 @@ func (p *ProxyServer) Start() (err error) { func (p *ProxyServer) Close() { p.Lock() p.Status = consts.Idle - p.listener.Close() + if p.listener != nil { + p.listener.Close() + } close(p.ctlMsgChan) close(p.cliConnChan) p.userConnList = list.New() diff --git a/src/frp/utils/broadcast/broadcast.go b/src/frp/utils/broadcast/broadcast.go index 4d45012..940b783 100644 --- a/src/frp/utils/broadcast/broadcast.go +++ b/src/frp/utils/broadcast/broadcast.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package broadcast type Broadcast struct { diff --git a/src/frp/utils/broadcast/broadcast_test.go b/src/frp/utils/broadcast/broadcast_test.go index 3354adc..57aa5a8 100644 --- a/src/frp/utils/broadcast/broadcast_test.go +++ b/src/frp/utils/broadcast/broadcast_test.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package broadcast import ( @@ -15,7 +29,7 @@ var ( func TestBroadcast(t *testing.T) { b := NewBroadcast() if b == nil { - t.Errorf("New Broadcast error, nil return") + t.Fatalf("New Broadcast error, nil return") } defer b.Close() @@ -31,7 +45,7 @@ func TestBroadcast(t *testing.T) { wait.Wait() if succNum != totalNum { - t.Errorf("TotalNum %d, FailNum(timeout) %d", totalNum, totalNum-succNum) + t.Fatalf("TotalNum %d, FailNum(timeout) %d", totalNum, totalNum-succNum) } } diff --git a/src/frp/utils/conn/conn.go b/src/frp/utils/conn/conn.go index dceabd9..fbf4be9 100644 --- a/src/frp/utils/conn/conn.go +++ b/src/frp/utils/conn/conn.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package conn import ( diff --git a/src/frp/utils/log/log.go b/src/frp/utils/log/log.go index f6587cd..ed66ca0 100644 --- a/src/frp/utils/log/log.go +++ b/src/frp/utils/log/log.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package log import ( diff --git a/src/frp/utils/pcrypto/pcrypto.go b/src/frp/utils/pcrypto/pcrypto.go index 729db90..e260a3e 100644 --- a/src/frp/utils/pcrypto/pcrypto.go +++ b/src/frp/utils/pcrypto/pcrypto.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package pcrypto import ( diff --git a/src/frp/utils/pcrypto/pcrypto_test.go b/src/frp/utils/pcrypto/pcrypto_test.go index 73377e3..43e38f0 100644 --- a/src/frp/utils/pcrypto/pcrypto_test.go +++ b/src/frp/utils/pcrypto/pcrypto_test.go @@ -1,3 +1,17 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package pcrypto import ( @@ -11,7 +25,7 @@ func TestEncrypto(t *testing.T) { pp.Init([]byte("Hana")) res, err := pp.Encrypto([]byte("Just One Test!")) if err != nil { - t.Error(err) + t.Fatal(err) } fmt.Printf("[%x]\n", res) @@ -22,12 +36,12 @@ func TestDecrypto(t *testing.T) { pp.Init([]byte("Hana")) res, err := pp.Encrypto([]byte("Just One Test!")) if err != nil { - t.Error(err) + t.Fatal(err) } res, err = pp.Decrypto(res) if err != nil { - t.Error(err) + t.Fatal(err) } fmt.Printf("[%s]\n", string(res)) @@ -36,12 +50,12 @@ func TestDecrypto(t *testing.T) { func TestPKCS7Padding(t *testing.T) { ltt := []byte("Test_PKCS7Padding") ltt = PKCS7Padding(ltt, aes.BlockSize) - fmt.Printf("[%x]\n", (ltt)) + // fmt.Printf("[%x]\n", (ltt)) } func TestPKCS7UnPadding(t *testing.T) { ltt := []byte("Test_PKCS7Padding") ltt = PKCS7Padding(ltt, aes.BlockSize) ltt = PKCS7UnPadding(ltt) - fmt.Printf("[%x]\n", ltt) + // fmt.Printf("[%x]\n", ltt) } diff --git a/src/frp/utils/version/version.go b/src/frp/utils/version/version.go new file mode 100644 index 0000000..680fa8c --- /dev/null +++ b/src/frp/utils/version/version.go @@ -0,0 +1,58 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "strconv" + "strings" +) + +var version string = "0.2.0" + +func Full() string { + return version +} + +func Proto(v string) int64 { + arr := strings.Split(v, ".") + if len(arr) < 2 { + return 0 + } + res, _ := strconv.ParseInt(arr[0], 10, 64) + return res +} + +func Major(v string) int64 { + arr := strings.Split(v, ".") + if len(arr) < 2 { + return 0 + } + res, _ := strconv.ParseInt(arr[1], 10, 64) + return res +} + +func Minor(v string) int64 { + arr := strings.Split(v, ".") + if len(arr) < 2 { + return 0 + } + res, _ := strconv.ParseInt(arr[2], 10, 64) + return res +} + +// add every case there if server will not accept client's protocol and return false +func Compat(client string, server string) bool { + return true +} diff --git a/src/frp/utils/version/version_test.go b/src/frp/utils/version/version_test.go new file mode 100644 index 0000000..ed66186 --- /dev/null +++ b/src/frp/utils/version/version_test.go @@ -0,0 +1,56 @@ +// Copyright 2016 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "fmt" + "strconv" + "strings" + "testing" +) + +func TestFull(t *testing.T) { + version := Full() + arr := strings.Split(version, ".") + if len(arr) != 3 { + t.Fatalf("Version string error: %s", version) + } + + proto, err := strconv.ParseInt(arr[0], 10, 64) + if err != nil || proto < 0 { + t.Fatalf("Version proto error") + } + + major, err := strconv.ParseInt(arr[1], 10, 64) + if err != nil || major < 0 { + t.Fatalf("Version major error") + } + + minor, err := strconv.ParseInt(arr[2], 10, 64) + if err != nil || minor < 0 { + t.Fatalf("Version minor error") + } +} + +func TestVersion(t *testing.T) { + proto := Proto(Full()) + major := Major(Full()) + minor := Minor(Full()) + parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor) + version := Full() + if parseVerion != version { + t.Fatalf("Get version incorrect, version [%s], parseVerion [%s]", version, parseVerion) + } +}