diff --git a/cmd/godog/main.go b/cmd/godog/main.go index 1904cc5..f324ed0 100644 --- a/cmd/godog/main.go +++ b/cmd/godog/main.go @@ -12,14 +12,12 @@ import ( "syscall" "github.com/DATA-DOG/godog" + "github.com/DATA-DOG/godog/colors" ) var statusMatch = regexp.MustCompile("^exit status (\\d+)") var parsedStatus int -var stdout = io.Writer(os.Stdout) -var stderr = statusOutputFilter(os.Stderr) - func buildAndRun() (int, error) { var status int @@ -36,8 +34,8 @@ func buildAndRun() (int, error) { defer os.Remove(bin) cmd := exec.Command(bin, os.Args[1:]...) - cmd.Stdout = stdout - cmd.Stderr = stderr + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr cmd.Env = os.Environ() if err = cmd.Start(); err != nil { @@ -68,46 +66,37 @@ func main() { var tags, format, output string var concurrency int - flagSet := godog.FlagSet(&format, &tags, &defs, &sof, &noclr, &concurrency) + flagSet := godog.FlagSet(colors.Colored(os.Stdout), &format, &tags, &defs, &sof, &noclr, &concurrency) flagSet.BoolVar(&vers, "version", false, "Show current version.") flagSet.StringVar(&output, "o", "", "Build and output test runner executable to given target path.") flagSet.StringVar(&output, "output", "", "Build and output test runner executable to given target path.") - err := flagSet.Parse(os.Args[1:]) - if err != nil { - fmt.Fprintln(stderr, err) + if err := flagSet.Parse(os.Args[1:]); err != nil { + fmt.Fprintln(os.Stderr, err) os.Exit(1) } if len(output) > 0 { bin, err := filepath.Abs(output) if err != nil { - fmt.Fprintln(stderr, "could not locate absolute path for:", output, err) + fmt.Fprintln(os.Stderr, "could not locate absolute path for:", output, err) os.Exit(1) } if err = godog.Build(bin); err != nil { - fmt.Fprintln(stderr, "could not build binary at:", output, err) + fmt.Fprintln(os.Stderr, "could not build binary at:", output, err) os.Exit(1) } os.Exit(0) } - 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(os.Stdout, "Godog version is:", godog.Version) os.Exit(0) // should it be 0? } status, err := buildAndRun() if err != nil { - fmt.Fprintln(stderr, err) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } // it might be a case, that status might not be resolved diff --git a/cmd/godog/ansicolor_ansi.go b/colors/ansi_others.go similarity index 95% rename from cmd/godog/ansicolor_ansi.go rename to colors/ansi_others.go index b6858b6..4ce4e77 100644 --- a/cmd/godog/ansicolor_ansi.go +++ b/colors/ansi_others.go @@ -4,7 +4,7 @@ // +build !windows -package main +package colors import "io" diff --git a/cmd/godog/ansicolor_windows.go b/colors/ansi_windows.go similarity index 98% rename from cmd/godog/ansicolor_windows.go rename to colors/ansi_windows.go index 1772929..66401f9 100644 --- a/cmd/godog/ansicolor_windows.go +++ b/colors/ansi_windows.go @@ -4,7 +4,7 @@ // +build windows -package main +package colors import ( "bytes" @@ -351,7 +351,7 @@ func isParameterChar(b byte) bool { func (cw *ansiColorWriter) Write(p []byte) (int, error) { r, nw, first, last := 0, 0, 0, 0 - if cw.mode != DiscardNonColorEscSeq { + if cw.mode != discardNonColorEscSeq { cw.state = outsideCsiCode cw.resetBuffer() } @@ -388,7 +388,7 @@ func (cw *ansiColorWriter) Write(p []byte) (int, error) { } first = i + 1 result := parseEscapeSequence(ch, cw.paramBuf.Bytes()) - if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) { + if result == noConsole || (cw.mode == outputNonColorEscSeq && result == unknown) { cw.paramBuf.WriteByte(ch) nw, err := cw.flushBuffer() if err != nil { @@ -408,7 +408,7 @@ func (cw *ansiColorWriter) Write(p []byte) (int, error) { } } - if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode { + if cw.mode != discardNonColorEscSeq || cw.state == outsideCsiCode { nw, err = cw.w.Write(p[first:len(p)]) r += nw } diff --git a/colors/colors.go b/colors/colors.go new file mode 100644 index 0000000..df00cec --- /dev/null +++ b/colors/colors.go @@ -0,0 +1,59 @@ +package colors + +import ( + "fmt" + "strings" +) + +const ansiEscape = "\x1b" + +// a color code type +type color int + +// some ansi colors +const ( + black color = iota + 30 + red + green + yellow + blue + magenta + cyan + white +) + +func colorize(s interface{}, c color) string { + return fmt.Sprintf("%s[%dm%v%s[0m", ansiEscape, c, s, ansiEscape) +} + +type ColorFunc func(interface{}) string + +func Bold(fn ColorFunc) ColorFunc { + return ColorFunc(func(input interface{}) string { + return strings.Replace(fn(input), ansiEscape+"[", ansiEscape+"[1;", 1) + }) +} + +func Green(s interface{}) string { + return colorize(s, green) +} + +func Red(s interface{}) string { + return colorize(s, red) +} + +func Cyan(s interface{}) string { + return colorize(s, cyan) +} + +func Black(s interface{}) string { + return colorize(s, black) +} + +func Yellow(s interface{}) string { + return colorize(s, yellow) +} + +func White(s interface{}) string { + return colorize(s, white) +} diff --git a/cmd/godog/no_color.go b/colors/no_colors.go similarity index 93% rename from cmd/godog/no_color.go rename to colors/no_colors.go index 18c5d66..3381d11 100644 --- a/cmd/godog/no_color.go +++ b/colors/no_colors.go @@ -1,4 +1,4 @@ -package main +package colors import ( "bytes" @@ -11,7 +11,7 @@ type noColors struct { lastbuf bytes.Buffer } -func noColorsWriter(w io.Writer) io.Writer { +func Uncolored(w io.Writer) io.Writer { return &noColors{out: w} } diff --git a/cmd/godog/ansicolor.go b/colors/writer.go similarity index 81% rename from cmd/godog/ansicolor.go rename to colors/writer.go index 8a535ed..0d33659 100644 --- a/cmd/godog/ansicolor.go +++ b/colors/writer.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package main +package colors import "io" @@ -15,17 +15,17 @@ type outputMode int // color escape sequence. const ( _ outputMode = iota - DiscardNonColorEscSeq - OutputNonColorEscSeq + discardNonColorEscSeq + outputNonColorEscSeq ) -// NewAnsiColorWriter creates and initializes a new ansiColorWriter +// Colored creates and initializes a new ansiColorWriter // using io.Writer w as its initial contents. // In the console of Windows, which change the foreground and background // colors of the text by the escape sequence. // In the console of other systems, which writes to w all text. -func createAnsiColorWriter(w io.Writer) io.Writer { - return createModeAnsiColorWriter(w, DiscardNonColorEscSeq) +func Colored(w io.Writer) io.Writer { + return createModeAnsiColorWriter(w, discardNonColorEscSeq) } // NewModeAnsiColorWriter create and initializes a new ansiColorWriter diff --git a/flags.go b/flags.go index 6968726..3c2a93e 100644 --- a/flags.go +++ b/flags.go @@ -3,31 +3,35 @@ package godog import ( "flag" "fmt" + "io" "strings" + + "github.com/DATA-DOG/godog/colors" ) 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" + s(4) + "- dir " + colors.Yellow("(features/)") + "\n" + + s(4) + "- feature " + colors.Yellow("(*.feature)") + "\n" + + s(4) + "- scenario at specific line " + colors.Yellow("(*.feature:10)") + "\n" + + "If no feature paths are listed, suite tries " + colors.Yellow("features") + " 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) + "- " + colors.Yellow(`= 1`) + ": supports all types of formats.\n" + + s(4) + "- " + colors.Yellow(`>= 2`) + ": only supports " + colors.Yellow("progress") + ". 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" + s(4) + "- " + colors.Yellow(`"@wip"`) + ": run all scenarios with wip tag\n" + + s(4) + "- " + colors.Yellow(`"~@wip"`) + ": exclude all scenarios with wip tag\n" + + s(4) + "- " + colors.Yellow(`"@wip && ~@new"`) + ": run wip scenarios, but exclude new\n" + + s(4) + "- " + colors.Yellow(`"@wip,@undone"`) + ": run wip or undone scenarios" // FlagSet allows to manage flags by external suite runner -func FlagSet(format, tags *string, defs, sof, noclr *bool, cr *int) *flag.FlagSet { +func FlagSet(w io.Writer, 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" + // @TODO: sort by name + for name, desc := range AvailableFormatters() { + descFormatOption += s(4) + "- " + colors.Yellow(name) + ": " + desc + "\n" } descFormatOption = strings.TrimSpace(descFormatOption) @@ -42,7 +46,7 @@ func FlagSet(format, tags *string, defs, sof, noclr *bool, cr *int) *flag.FlagSe 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) + set.Usage = usage(set, w) return set } @@ -66,7 +70,7 @@ func (f *flagged) name() string { return name } -func usage(set *flag.FlagSet) func() { +func usage(set *flag.FlagSet, w io.Writer) func() { return func() { var list []*flagged var longest int @@ -102,7 +106,7 @@ func usage(set *flag.FlagSet) func() { 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]) + ret = append(ret, s(2)+colors.Green(name)+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) @@ -112,22 +116,22 @@ func usage(set *flag.FlagSet) func() { } // --- GENERAL --- - fmt.Println(cl("Usage:", yellow)) + fmt.Fprintln(w, colors.Yellow("Usage:")) 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") + fmt.Fprintln(w, "Builds a test package and runs given feature files.") + fmt.Fprintf(w, "Command should be run from the directory of tested package and contain buildable go source.\n\n") // --- ARGUMENTS --- - fmt.Println(cl("Arguments:", yellow)) + fmt.Fprintln(w, colors.Yellow("Arguments:")) // --> features - fmt.Println(opt("features", descFeaturesArgument)) + fmt.Fprintln(w, opt("features", descFeaturesArgument)) // --- OPTIONS --- - fmt.Println(cl("Options:", yellow)) + fmt.Fprintln(w, colors.Yellow("Options:")) for _, f := range list { - fmt.Println(opt(f.name(), f.descr)) + fmt.Fprintln(w, opt(f.name(), f.descr)) } - fmt.Println("") + fmt.Fprintln(w, "") } } diff --git a/fmt.go b/fmt.go index d17b0a8..aed5a52 100644 --- a/fmt.go +++ b/fmt.go @@ -11,6 +11,7 @@ import ( "time" "unicode" + "github.com/DATA-DOG/godog/colors" "github.com/DATA-DOG/godog/gherkin" ) @@ -78,6 +79,17 @@ func Format(name, description string, f FormatterFunc) { }) } +// AvailableFormatters gives a map of all +// formatters registered with their name as key +// and description as value +func AvailableFormatters() map[string]string { + fmts := make(map[string]string, len(formatters)) + for _, f := range formatters { + fmts[f.name] = f.description + } + return fmts +} + // Formatter is an interface for feature runner // output summary presentation. // @@ -111,7 +123,7 @@ const ( pending ) -func (st stepType) clr() color { +func (st stepType) clr() colors.ColorFunc { switch st { case passed: return green @@ -267,28 +279,28 @@ func (f *basefmt) Summary() { var steps, parts, scenarios []string nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending) if len(f.passed) > 0 { - steps = append(steps, cl(fmt.Sprintf("%d passed", len(f.passed)), green)) + steps = append(steps, green(fmt.Sprintf("%d passed", len(f.passed)))) } if len(f.failed) > 0 { passed -= len(f.failed) - parts = append(parts, cl(fmt.Sprintf("%d failed", len(f.failed)), red)) + parts = append(parts, red(fmt.Sprintf("%d failed", len(f.failed)))) steps = append(steps, parts[len(parts)-1]) } if len(f.pending) > 0 { passed -= len(f.pending) - parts = append(parts, cl(fmt.Sprintf("%d pending", len(f.pending)), yellow)) - steps = append(steps, cl(fmt.Sprintf("%d pending", len(f.pending)), yellow)) + parts = append(parts, yellow(fmt.Sprintf("%d pending", len(f.pending)))) + steps = append(steps, yellow(fmt.Sprintf("%d pending", len(f.pending)))) } if len(f.undefined) > 0 { passed -= undefined - parts = append(parts, cl(fmt.Sprintf("%d undefined", undefined), yellow)) - steps = append(steps, cl(fmt.Sprintf("%d undefined", len(f.undefined)), yellow)) + parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefined))) + steps = append(steps, yellow(fmt.Sprintf("%d undefined", len(f.undefined)))) } if len(f.skipped) > 0 { - steps = append(steps, cl(fmt.Sprintf("%d skipped", len(f.skipped)), cyan)) + steps = append(steps, cyan(fmt.Sprintf("%d skipped", len(f.skipped)))) } if passed > 0 { - scenarios = append(scenarios, cl(fmt.Sprintf("%d passed", passed), green)) + scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passed))) } scenarios = append(scenarios, parts...) elapsed := time.Since(f.started) @@ -308,8 +320,8 @@ func (f *basefmt) Summary() { fmt.Fprintln(f.out, elapsed) if text := f.snippets(); text != "" { - fmt.Fprintln(f.out, cl("\nYou can implement step definitions for undefined steps with these snippets:", yellow)) - fmt.Fprintln(f.out, cl(text, yellow)) + fmt.Fprintln(f.out, yellow("\nYou can implement step definitions for undefined steps with these snippets:")) + fmt.Fprintln(f.out, yellow(text)) } } diff --git a/fmt_pretty.go b/fmt_pretty.go index 5a25098..9d317b1 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -9,6 +9,7 @@ import ( "time" "unicode/utf8" + "github.com/DATA-DOG/godog/colors" "github.com/DATA-DOG/godog/gherkin" ) @@ -57,7 +58,7 @@ func (f *pretty) Feature(ft *gherkin.Feature, p string, c []byte) { fmt.Fprintln(f.out, "") } f.features = append(f.features, &feature{Path: p, Feature: ft}) - fmt.Fprintln(f.out, bcl(ft.Keyword+": ", white)+ft.Name) + fmt.Fprintln(f.out, whiteb(ft.Keyword+": ")+ft.Name) if strings.TrimSpace(ft.Description) != "" { for _, line := range strings.Split(ft.Description, "\n") { fmt.Fprintln(f.out, s(f.indent)+strings.TrimSpace(line)) @@ -110,7 +111,7 @@ func (f *pretty) Summary() { } } if len(failedScenarios) > 0 { - fmt.Fprintln(f.out, "\n--- "+cl("Failed scenarios:", red)+"\n") + fmt.Fprintln(f.out, "\n--- "+red("Failed scenarios:")+"\n") var unique []string for _, fail := range failedScenarios { var found bool @@ -126,7 +127,7 @@ func (f *pretty) Summary() { } for _, fail := range unique { - fmt.Fprintln(f.out, " "+cl(fail, red)) + fmt.Fprintln(f.out, " "+red(fail)) } } f.basefmt.Summary() @@ -134,7 +135,7 @@ func (f *pretty) Summary() { func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) { var msg string - clr := green + var clr colors.ColorFunc ex := outline.Examples[f.outlineNumExample] example, hasExamples := examples(ex) @@ -154,7 +155,7 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) { clr = res.typ.clr() case res.typ == undefined || res.typ == pending: clr = res.typ.clr() - case res.typ == skipped && clr == green: + case res.typ == skipped && clr == nil: clr = cyan } if printSteps { @@ -166,20 +167,20 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) { var pos int for i := 0; i < len(m); i++ { pair := m[i] - text += cl(ostep.Text[pos:pair[0]], cyan) - text += bcl(ostep.Text[pair[0]:pair[1]], cyan) + text += cyan(ostep.Text[pos:pair[0]]) + text += cyanb(ostep.Text[pair[0]:pair[1]]) pos = pair[1] } - text += cl(ostep.Text[pos:len(ostep.Text)], cyan) + text += cyan(ostep.Text[pos:len(ostep.Text)]) } else { - text = cl(ostep.Text, cyan) + text = cyan(ostep.Text) } - text += s(f.commentPos-f.length(ostep)+1) + cl(fmt.Sprintf("# %s", res.def.definitionID()), black) + text += s(f.commentPos-f.length(ostep)+1) + black(fmt.Sprintf("# %s", res.def.definitionID())) } else { - text = cl(ostep.Text, cyan) + text = cyan(ostep.Text) } // print the step outline - fmt.Fprintln(f.out, s(f.indent*2)+cl(strings.TrimSpace(ostep.Keyword), cyan)+" "+text) + fmt.Fprintln(f.out, s(f.indent*2)+cyan(strings.TrimSpace(ostep.Keyword))+" "+text) } } @@ -188,48 +189,51 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) { // an example table header if firstExample { fmt.Fprintln(f.out, "") - fmt.Fprintln(f.out, s(f.indent*2)+bcl(example.Keyword+": ", white)+example.Name) + fmt.Fprintln(f.out, s(f.indent*2)+whiteb(example.Keyword+": ")+example.Name) for i, cell := range example.TableHeader.Cells { - cells[i] = cl(cell.Value, cyan) + s(max[i]-len(cell.Value)) + cells[i] = cyan(cell.Value) + s(max[i]-len(cell.Value)) } fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |") } + if clr == nil { + clr = green + } // an example table row row := example.TableBody[len(example.TableBody)-f.outlineNumExamples] for i, cell := range row.Cells { - cells[i] = cl(cell.Value, clr) + s(max[i]-len(cell.Value)) + cells[i] = clr(cell.Value) + s(max[i]-len(cell.Value)) } fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |") // if there is an error if msg != "" { - fmt.Fprintln(f.out, s(f.indent*4)+bcl(msg, red)) + fmt.Fprintln(f.out, s(f.indent*4)+redb(msg)) } } -func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) { - text := s(f.indent*2) + cl(strings.TrimSpace(step.Keyword), c) + " " +func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c colors.ColorFunc) { + text := s(f.indent*2) + c(strings.TrimSpace(step.Keyword)) + " " switch { case def != nil: if m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]; len(m) > 0 { var pos, i int for pos, i = 0, 0; i < len(m); i++ { if math.Mod(float64(i), 2) == 0 { - text += cl(step.Text[pos:m[i]], c) + text += c(step.Text[pos:m[i]]) } else { - text += bcl(step.Text[pos:m[i]], c) + text += c(step.Text[pos:m[i]]) } pos = m[i] } - text += cl(step.Text[pos:len(step.Text)], c) + text += c(step.Text[pos:len(step.Text)]) } else { - text += cl(step.Text, c) + text += c(step.Text) } - text += s(f.commentPos-f.length(step)+1) + cl(fmt.Sprintf("# %s", def.definitionID()), black) + text += s(f.commentPos-f.length(step)+1) + black(fmt.Sprintf("# %s", def.definitionID())) default: - text += cl(step.Text, c) + text += c(step.Text) } fmt.Fprintln(f.out, text) @@ -239,13 +243,13 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) { case *gherkin.DocString: var ct string if len(t.ContentType) > 0 { - ct = " " + cl(t.ContentType, c) + ct = " " + c(t.ContentType) } - fmt.Fprintln(f.out, s(f.indent*3)+cl(t.Delimitter, c)+ct) + fmt.Fprintln(f.out, s(f.indent*3)+c(t.Delimitter)+ct) for _, ln := range strings.Split(t.Content, "\n") { - fmt.Fprintln(f.out, s(f.indent*3)+cl(ln, c)) + fmt.Fprintln(f.out, s(f.indent*3)+c(ln)) } - fmt.Fprintln(f.out, s(f.indent*3)+cl(t.Delimitter, c)) + fmt.Fprintln(f.out, s(f.indent*3)+c(t.Delimitter)) } } @@ -255,7 +259,7 @@ func (f *pretty) printStepKind(res *stepResult) { // first background step case f.bgSteps > 0 && f.bgSteps == len(f.feature.Background.Steps): f.commentPos = f.longestStep(f.feature.Background.Steps, f.length(f.feature.Background)) - fmt.Fprintln(f.out, "\n"+s(f.indent)+bcl(f.feature.Background.Keyword+": "+f.feature.Background.Name, white)) + fmt.Fprintln(f.out, "\n"+s(f.indent)+whiteb(f.feature.Background.Keyword+": "+f.feature.Background.Name)) f.bgSteps-- // subsequent background steps case f.bgSteps > 0: @@ -270,7 +274,7 @@ func (f *pretty) printStepKind(res *stepResult) { f.commentPos = bgLen } } - text := s(f.indent) + bcl(f.scenario.Keyword+": ", white) + f.scenario.Name + text := s(f.indent) + whiteb(f.scenario.Keyword+": ") + f.scenario.Name text += s(f.commentPos-f.length(f.scenario)+1) + f.line(f.scenario.Location) fmt.Fprintln(f.out, "\n"+text) f.scenarioKeyword = true @@ -289,7 +293,7 @@ func (f *pretty) printStepKind(res *stepResult) { f.commentPos = bgLen } } - text := s(f.indent) + bcl(f.outline.Keyword+": ", white) + f.outline.Name + text := s(f.indent) + whiteb(f.outline.Keyword+": ") + f.outline.Name text += s(f.commentPos-f.length(f.outline)+1) + f.line(f.outline.Location) fmt.Fprintln(f.out, "\n"+text) f.scenarioKeyword = true @@ -304,22 +308,22 @@ func (f *pretty) printStepKind(res *stepResult) { f.printStep(res.step, res.def, res.typ.clr()) if res.err != nil { - fmt.Fprintln(f.out, s(f.indent*2)+bcl(res.err, red)) + fmt.Fprintln(f.out, s(f.indent*2)+redb(res.err)) } if res.typ == pending { - fmt.Fprintln(f.out, s(f.indent*3)+cl("TODO: write pending definition", yellow)) + fmt.Fprintln(f.out, s(f.indent*3)+yellow("TODO: write pending definition")) } } // print table with aligned table cells -func (f *pretty) printTable(t *gherkin.DataTable, c color) { +func (f *pretty) printTable(t *gherkin.DataTable, c colors.ColorFunc) { var l = longest(t) var cols = make([]string, len(t.Rows[0].Cells)) for _, row := range t.Rows { for i, cell := range row.Cells { cols[i] = cell.Value + s(l[i]-len(cell.Value)) } - fmt.Fprintln(f.out, s(f.indent*3)+cl("| "+strings.Join(cols, " | ")+" |", c)) + fmt.Fprintln(f.out, s(f.indent*3)+c("| "+strings.Join(cols, " | ")+" |")) } } @@ -383,7 +387,7 @@ func (f *pretty) longestStep(steps []*gherkin.Step, base int) int { // a line number representation in feature file func (f *pretty) line(loc *gherkin.Location) string { - return cl(fmt.Sprintf("# %s:%d", f.features[len(f.features)-1].Path, loc.Line), black) + return black(fmt.Sprintf("# %s:%d", f.features[len(f.features)-1].Path, loc.Line)) } func (f *pretty) length(node interface{}) int { diff --git a/fmt_progress.go b/fmt_progress.go index dab45cb..8a20b1c 100644 --- a/fmt_progress.go +++ b/fmt_progress.go @@ -48,18 +48,18 @@ func (f *progress) Summary() { left := math.Mod(float64(f.steps), float64(f.stepsPerRow)) if left != 0 { if int(f.steps) > f.stepsPerRow { - fmt.Printf(s(f.stepsPerRow-int(left)) + fmt.Sprintf(" %d\n", f.steps)) + fmt.Fprintf(f.out, s(f.stepsPerRow-int(left))+fmt.Sprintf(" %d\n", f.steps)) } else { - fmt.Printf(" %d\n", f.steps) + fmt.Fprintf(f.out, " %d\n", f.steps) } } fmt.Fprintln(f.out, "") if len(f.failed) > 0 { - fmt.Fprintln(f.out, "\n--- "+cl("Failed steps:", red)+"\n") + fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n") for _, fail := range f.failed { - fmt.Fprintln(f.out, s(4)+cl(fail.step.Keyword+" "+fail.step.Text, red)+cl(" # "+fail.line(), black)) - fmt.Fprintln(f.out, s(6)+cl("Error: ", red)+bcl(fail.err, red)+"\n") + fmt.Fprintln(f.out, s(4)+red(fail.step.Keyword+" "+fail.step.Text)+black(" # "+fail.line())) + fmt.Fprintln(f.out, s(6)+red("Error: ")+redb(fail.err)+"\n") } } f.basefmt.Summary() @@ -68,15 +68,15 @@ func (f *progress) Summary() { func (f *progress) step(res *stepResult) { switch res.typ { case passed: - fmt.Print(cl(".", green)) + fmt.Fprint(f.out, green(".")) case skipped: - fmt.Print(cl("-", cyan)) + fmt.Fprint(f.out, cyan("-")) case failed: - fmt.Print(cl("F", red)) + fmt.Fprint(f.out, red("F")) case undefined: - fmt.Print(cl("U", yellow)) + fmt.Fprint(f.out, yellow("U")) case pending: - fmt.Print(cl("P", yellow)) + fmt.Fprint(f.out, yellow("P")) } f.steps++ if math.Mod(float64(f.steps), float64(f.stepsPerRow)) == 0 { diff --git a/suite.go b/suite.go index cffc630..7682bc4 100644 --- a/suite.go +++ b/suite.go @@ -387,7 +387,7 @@ func (s *Suite) printStepDefinitions() { n := utf8.RuneCountInString(def.Expr.String()) location := def.definitionID() spaces := strings.Repeat(" ", longest-n) - fmt.Println(cl(def.Expr.String(), yellow)+spaces, cl("# "+location, black)) + fmt.Println(yellow(def.Expr.String())+spaces, black("# "+location)) } if len(s.steps) == 0 { fmt.Println("there were no contexts registered, could not find any step definition..") diff --git a/utils.go b/utils.go index d51ab21..7d6fa86 100644 --- a/utils.go +++ b/utils.go @@ -4,37 +4,22 @@ import ( "fmt" "os" "strings" + + "github.com/DATA-DOG/godog/colors" ) // empty struct value takes no space allocation type void struct{} -// a color code type -type color int - -const ansiEscape = "\x1b" - -// some ansi colors -const ( - black color = iota + 30 - red - green - yellow - blue - magenta - cyan - white -) - -// colorizes foreground s with color c -func cl(s interface{}, c color) string { - return fmt.Sprintf("%s[%dm%v%s[0m", ansiEscape, c, s, ansiEscape) -} - -// colorizes foreground s with bold color c -func bcl(s interface{}, c color) string { - return fmt.Sprintf("%s[1;%dm%v%s[0m", ansiEscape, c, s, ansiEscape) -} +var red = colors.Red +var redb = colors.Bold(colors.Red) +var green = colors.Green +var black = colors.Black +var blackb = colors.Bold(colors.Black) +var yellow = colors.Yellow +var cyan = colors.Cyan +var cyanb = colors.Bold(colors.Cyan) +var whiteb = colors.Bold(colors.White) // repeats a space n times func s(n int) string {