From 9cc301f701f0d8fea679fb157b30cc7f856edd51 Mon Sep 17 00:00:00 2001 From: gedi Date: Wed, 8 Jun 2016 21:38:47 +0300 Subject: [PATCH] no-colors flag to disable ansi colors, closes #36 --- cmd/godog/main.go | 20 +++-- cmd/godog/no_color.go | 57 +++++++++++++ flags.go | 183 +++++++++++++++++++++++++++--------------- run.go | 10 +-- 4 files changed, 195 insertions(+), 75 deletions(-) create mode 100644 cmd/godog/no_color.go diff --git a/cmd/godog/main.go b/cmd/godog/main.go index ab6426b..91ed332 100644 --- a/cmd/godog/main.go +++ b/cmd/godog/main.go @@ -17,8 +17,8 @@ import ( var statusMatch = regexp.MustCompile("^exit status (\\d+)") var parsedStatus int -var stdout = createAnsiColorWriter(os.Stdout) -var stderr = createAnsiColorWriter(statusOutputFilter(os.Stderr)) +var stdout = io.Writer(os.Stdout) +var stderr = statusOutputFilter(os.Stderr) func buildAndRun() (int, error) { var status int @@ -75,19 +75,29 @@ func buildAndRun() (int, error) { } func main() { - var vers, defs, sof bool + var vers, defs, sof, noclr bool var tags, format string var concurrency int - flagSet := godog.FlagSet(&format, &tags, &defs, &sof, &vers, &concurrency) + flagSet := godog.FlagSet(&format, &tags, &defs, &sof, &noclr, &concurrency) + flagSet.BoolVar(&vers, "version", false, "Show current version.") + err := flagSet.Parse(os.Args[1:]) if err != nil { fmt.Fprintln(stderr, err) os.Exit(1) } + if noclr { + stdout = noColorsWriter(stdout) + stderr = noColorsWriter(stderr) + } else { + stdout = createAnsiColorWriter(stdout) + stderr = createAnsiColorWriter(stderr) + } + if vers { - fmt.Fprintln(stdout, "Godog version is", godog.Version) + fmt.Fprintln(stdout, "Godog version is:", godog.Version) os.Exit(0) // should it be 0? } diff --git a/cmd/godog/no_color.go b/cmd/godog/no_color.go new file mode 100644 index 0000000..18c5d66 --- /dev/null +++ b/cmd/godog/no_color.go @@ -0,0 +1,57 @@ +package main + +import ( + "bytes" + "fmt" + "io" +) + +type noColors struct { + out io.Writer + lastbuf bytes.Buffer +} + +func noColorsWriter(w io.Writer) io.Writer { + return &noColors{out: w} +} + +func (w *noColors) Write(data []byte) (n int, err error) { + er := bytes.NewBuffer(data) +loop: + for { + c1, _, err := er.ReadRune() + if err != nil { + break loop + } + if c1 != 0x1b { + fmt.Fprint(w.out, string(c1)) + continue + } + c2, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + break loop + } + if c2 != 0x5b { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + continue + } + + var buf bytes.Buffer + for { + c, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + w.lastbuf.Write(buf.Bytes()) + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + break + } + buf.Write([]byte(string(c))) + } + } + return len(data) - w.lastbuf.Len(), nil +} diff --git a/flags.go b/flags.go index 70039cb..6968726 100644 --- a/flags.go +++ b/flags.go @@ -3,74 +3,131 @@ package godog import ( "flag" "fmt" + "strings" ) +var descFeaturesArgument = "Optional feature(s) to run. Can be:\n" + + s(4) + "- dir " + cl("(features/)", yellow) + "\n" + + s(4) + "- feature " + cl("(*.feature)", yellow) + "\n" + + s(4) + "- scenario at specific line " + cl("(*.feature:10)", yellow) + "\n" + + "If no feature paths are listed, suite tries " + cl("features", yellow) + " path by default.\n" + +var descConcurrencyOption = "Run the test suite with concurrency level:\n" + + s(4) + "- " + cl(`= 1`, yellow) + ": supports all types of formats.\n" + + s(4) + "- " + cl(`>= 2`, yellow) + ": only supports " + cl("progress", yellow) + ". Note, that\n" + + s(4) + "your context needs to support parallel execution." + +var descTagsOption = "Filter scenarios by tags. Expression can be:\n" + + s(4) + "- " + cl(`"@wip"`, yellow) + ": run all scenarios with wip tag\n" + + s(4) + "- " + cl(`"~@wip"`, yellow) + ": exclude all scenarios with wip tag\n" + + s(4) + "- " + cl(`"@wip && ~@new"`, yellow) + ": run wip scenarios, but exclude new\n" + + s(4) + "- " + cl(`"@wip,@undone"`, yellow) + ": run wip or undone scenarios" + // FlagSet allows to manage flags by external suite runner -func FlagSet(format, tags *string, defs, sof, vers *bool, cl *int) *flag.FlagSet { +func FlagSet(format, tags *string, defs, sof, noclr *bool, cr *int) *flag.FlagSet { + descFormatOption := "How to format tests output. Available formats:\n" + for _, f := range formatters { + descFormatOption += s(4) + "- " + cl(f.name, yellow) + ": " + f.description + "\n" + } + descFormatOption = strings.TrimSpace(descFormatOption) + set := flag.NewFlagSet("godog", flag.ExitOnError) - set.StringVar(format, "format", "pretty", "") - set.StringVar(format, "f", "pretty", "") - set.StringVar(tags, "tags", "", "") - set.StringVar(tags, "t", "", "") - set.IntVar(cl, "concurrency", 1, "") - set.IntVar(cl, "c", 1, "") - set.BoolVar(defs, "definitions", false, "") - set.BoolVar(defs, "d", false, "") - set.BoolVar(sof, "stop-on-failure", false, "") - set.BoolVar(vers, "version", false, "") - set.Usage = usage + set.StringVar(format, "format", "pretty", descFormatOption) + set.StringVar(format, "f", "pretty", descFormatOption) + set.StringVar(tags, "tags", "", descTagsOption) + set.StringVar(tags, "t", "", descTagsOption) + set.IntVar(cr, "concurrency", 1, descConcurrencyOption) + set.IntVar(cr, "c", 1, descConcurrencyOption) + set.BoolVar(defs, "definitions", false, "Print all available step definitions.") + set.BoolVar(defs, "d", false, "Print all available step definitions.") + set.BoolVar(sof, "stop-on-failure", false, "Stop processing on first failed scenario.") + set.BoolVar(noclr, "no-colors", false, "Disable ansi colors.") + set.Usage = usage(set) return set } -func usage() { - // prints an option or argument with a description, or only description - opt := func(name, desc string) string { - if len(name) > 0 { - name += ":" - } - return s(2) + cl(name, green) + s(22-len(name)) + desc - } - - // --- GENERAL --- - fmt.Println(cl("Usage:", yellow)) - fmt.Printf(s(2) + "godog [options] []\n\n") - // description - fmt.Println("Builds a test package and runs given feature files.") - fmt.Printf("Command should be run from the directory of tested package and contain buildable go source.\n\n") - - // --- ARGUMENTS --- - fmt.Println(cl("Arguments:", yellow)) - // --> paths - fmt.Println(opt("features", "Optional feature(s) to run. Can be:")) - fmt.Println(opt("", s(4)+"- dir "+cl("(features/)", yellow))) - fmt.Println(opt("", s(4)+"- feature "+cl("(*.feature)", yellow))) - fmt.Println(opt("", s(4)+"- scenario at specific line "+cl("(*.feature:10)", yellow))) - fmt.Println(opt("", "If no feature paths are listed, suite tries "+cl("features", yellow)+" path by default.")) - fmt.Println("") - - // --- OPTIONS --- - fmt.Println(cl("Options:", yellow)) - // --> step definitions - fmt.Println(opt("-d, --definitions", "Print all available step definitions.")) - // --> concurrency - fmt.Println(opt("-c, --concurrency=1", "Run the test suite with concurrency level:")) - fmt.Println(opt("", s(4)+"- "+cl(`= 1`, yellow)+": supports all types of formats.")) - fmt.Println(opt("", s(4)+"- "+cl(`>= 2`, yellow)+": only supports "+cl("progress", yellow)+". Note, that")) - fmt.Println(opt("", s(4)+"your context needs to support parallel execution.")) - // --> format - fmt.Println(opt("-f, --format=pretty", "How to format tests output. Available formats:")) - for _, f := range formatters { - fmt.Println(opt("", s(4)+"- "+cl(f.name, yellow)+": "+f.description)) - } - // --> tags - fmt.Println(opt("-t, --tags", "Filter scenarios by tags. Expression can be:")) - fmt.Println(opt("", s(4)+"- "+cl(`"@wip"`, yellow)+": run all scenarios with wip tag")) - fmt.Println(opt("", s(4)+"- "+cl(`"~@wip"`, yellow)+": exclude all scenarios with wip tag")) - fmt.Println(opt("", s(4)+"- "+cl(`"@wip && ~@new"`, yellow)+": run wip scenarios, but exclude new")) - fmt.Println(opt("", s(4)+"- "+cl(`"@wip,@undone"`, yellow)+": run wip or undone scenarios")) - // --> stop on failure - fmt.Println(opt("--stop-on-failure", "Stop processing on first failed scenario.")) - // --> version - fmt.Println(opt("--version", "Show current "+cl("godog", yellow)+" version.")) - fmt.Println("") +type flagged struct { + short, long, descr, dflt string +} + +func (f *flagged) name() string { + var name string + switch { + case len(f.short) > 0 && len(f.long) > 0: + name = fmt.Sprintf("-%s, --%s", f.short, f.long) + case len(f.long) > 0: + name = fmt.Sprintf("--%s", f.long) + case len(f.short) > 0: + name = fmt.Sprintf("-%s", f.short) + } + if f.dflt != "true" && f.dflt != "false" { + name += "=" + f.dflt + } + return name +} + +func usage(set *flag.FlagSet) func() { + return func() { + var list []*flagged + var longest int + set.VisitAll(func(f *flag.Flag) { + var fl *flagged + for _, flg := range list { + if flg.descr == f.Usage { + fl = flg + break + } + } + if nil == fl { + fl = &flagged{ + dflt: f.DefValue, + descr: f.Usage, + } + list = append(list, fl) + } + if len(f.Name) > 2 { + fl.long = f.Name + } else { + fl.short = f.Name + } + }) + + for _, f := range list { + if len(f.name()) > longest { + longest = len(f.name()) + } + } + + // prints an option or argument with a description, or only description + opt := func(name, desc string) string { + var ret []string + lines := strings.Split(desc, "\n") + ret = append(ret, s(2)+cl(name, green)+s(longest+2-len(name))+lines[0]) + if len(lines) > 1 { + for _, ln := range lines[1:] { + ret = append(ret, s(2)+s(longest+2)+ln) + } + } + return strings.Join(ret, "\n") + } + + // --- GENERAL --- + fmt.Println(cl("Usage:", yellow)) + fmt.Printf(s(2) + "godog [options] []\n\n") + // description + fmt.Println("Builds a test package and runs given feature files.") + fmt.Printf("Command should be run from the directory of tested package and contain buildable go source.\n\n") + + // --- ARGUMENTS --- + fmt.Println(cl("Arguments:", yellow)) + // --> features + fmt.Println(opt("features", descFeaturesArgument)) + + // --- OPTIONS --- + fmt.Println(cl("Options:", yellow)) + for _, f := range list { + fmt.Println(opt(f.name(), f.descr)) + } + fmt.Println("") + } } diff --git a/run.go b/run.go index 8421269..09f2a41 100644 --- a/run.go +++ b/run.go @@ -53,18 +53,14 @@ func (r *runner) run() (failed bool) { // contextInitializer must be able to register // the step definitions and event handlers. func Run(contextInitializer func(suite *Suite)) int { - var vers, defs, sof bool + var defs, sof, noclr bool var tags, format string var concurrency int - flagSet := FlagSet(&format, &tags, &defs, &sof, &vers, &concurrency) + flagSet := FlagSet(&format, &tags, &defs, &sof, &noclr, &concurrency) err := flagSet.Parse(os.Args[1:]) fatal(err) - switch { - case vers: - fmt.Println(cl("Godog", green) + " version is " + cl(Version, yellow)) - return 0 - case defs: + if defs { s := &Suite{} contextInitializer(s) s.printStepDefinitions()