first commit

This commit is contained in:
2020-10-25 10:00:59 +03:00
commit 0bd6f67397
80 changed files with 4741 additions and 0 deletions

5
output/formatter/ansi.go Normal file
View File

@@ -0,0 +1,5 @@
package formatter
func Ansi() *Formatter {
return New()
}

View File

@@ -0,0 +1,79 @@
package formatter
import (
"bytes"
"context"
"regexp"
"gitoa.ru/go-4devs/console/output/style"
)
//nolint: gochecknoglobals
var re = regexp.MustCompile(`<(([a-z][^<>]+)|/([a-z][^<>]+)?)>`)
func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) {
return func(f *Formatter) {
f.styles = styles
}
}
func New(opts ...func(*Formatter)) *Formatter {
f := &Formatter{
styles: style.Find,
}
for _, opt := range opts {
opt(f)
}
return f
}
type Formatter struct {
styles func(string) (style.Style, error)
}
func (a *Formatter) Format(ctx context.Context, msg string) string {
var (
out bytes.Buffer
cur int
)
for _, idx := range re.FindAllStringIndex(msg, -1) {
tag := msg[idx[0]+1 : idx[1]-1]
if cur < idx[0] {
out.WriteString(msg[cur:idx[0]])
}
var (
st style.Style
err error
)
switch {
case tag[0:1] == "/":
st, err = a.styles(tag[1:])
if err == nil {
out.WriteString(st.Set(style.ActionUnset))
}
default:
st, err = a.styles(tag)
if err == nil {
out.WriteString(st.Set(style.ActionSet))
}
}
if err != nil {
cur = idx[0]
} else {
cur = idx[1]
}
}
if len(msg) > cur {
out.WriteString(msg[cur:])
}
return out.String()
}

View File

@@ -0,0 +1,26 @@
package formatter_test
import (
"context"
"testing"
"gitoa.ru/go-4devs/console/output/formatter"
)
func TestFormatter(t *testing.T) {
ctx := context.Background()
formatter := formatter.New()
cases := map[string]string{
"<info>info message</info>": "\x1b[32minfo message\x1b[39m",
"<info><command></info>": "\x1b[32m<command>\x1b[39m",
"<html>...</html>": "<html>...</html>",
}
for msg, ex := range cases {
got := formatter.Format(ctx, msg)
if ex != got {
t.Errorf("ivalid expected:%#v, got: %#v", ex, got)
}
}
}

14
output/formatter/none.go Normal file
View File

@@ -0,0 +1,14 @@
package formatter
import "gitoa.ru/go-4devs/console/output/style"
func None() *Formatter {
return New(
WithStyle(func(name string) (style.Style, error) {
if _, err := style.Find(name); err != nil {
return style.Empty(), err
}
return style.Empty(), nil
}))
}

View File

@@ -0,0 +1,27 @@
package formatter_test
import (
"context"
"testing"
"gitoa.ru/go-4devs/console/output/formatter"
)
func TestNone(t *testing.T) {
ctx := context.Background()
none := formatter.None()
cases := map[string]string{
"<info>message info</info>": "message info",
"<error>message error</error>": "message error",
"<comment><scheme></comment>": "<scheme>",
"<body>body</body>": "<body>body</body>",
}
for msg, ex := range cases {
got := none.Format(ctx, msg)
if ex != got {
t.Errorf("expect:%#v, got:%#v", ex, got)
}
}
}

59
output/key.go Normal file
View File

@@ -0,0 +1,59 @@
package output
type Key string
func (k Key) Any(v interface{}) KeyValue {
return KeyValue{
Key: k,
Value: AnyValue(v),
}
}
func (k Key) Bool(v bool) KeyValue {
return KeyValue{
Key: k,
Value: BoolValue(v),
}
}
func (k Key) Int(v int) KeyValue {
return KeyValue{
Key: k,
Value: IntValue(v),
}
}
func (k Key) Int64(v int64) KeyValue {
return KeyValue{
Key: k,
Value: Int64Value(v),
}
}
func (k Key) Uint(v uint) KeyValue {
return KeyValue{
Key: k,
Value: UintValue(v),
}
}
func (k Key) Uint64(v uint64) KeyValue {
return KeyValue{
Key: k,
Value: Uint64Value(v),
}
}
func (k Key) Float64(v float64) KeyValue {
return KeyValue{
Key: k,
Value: Float64Value(v),
}
}
func (k Key) String(v string) KeyValue {
return KeyValue{
Key: k,
Value: StringValue(v),
}
}

63
output/kv.go Normal file
View File

@@ -0,0 +1,63 @@
package output
import (
"fmt"
"strings"
)
var (
_ fmt.Stringer = KeyValue{}
_ fmt.Stringer = KeyValues{}
)
type KeyValues []KeyValue
func (kv KeyValues) String() string {
s := make([]string, len(kv))
for i, v := range kv {
s[i] = v.String()
}
return strings.Join(s, ", ")
}
type KeyValue struct {
Key Key
Value Value
}
func (k KeyValue) String() string {
return string(k.Key) + "=\"" + k.Value.String() + "\""
}
func Any(k string, v interface{}) KeyValue {
return Key(k).Any(v)
}
func Bool(k string, v bool) KeyValue {
return Key(k).Bool(v)
}
func Int(k string, v int) KeyValue {
return Key(k).Int(v)
}
func Int64(k string, v int64) KeyValue {
return Key(k).Int64(v)
}
func Uint(k string, v uint) KeyValue {
return Key(k).Uint(v)
}
func Uint64(k string, v uint64) KeyValue {
return Key(k).Uint64(v)
}
func Float64(k string, v float64) KeyValue {
return Key(k).Float64(v)
}
func String(k string, v string) KeyValue {
return Key(k).String(v)
}

77
output/output.go Normal file
View File

@@ -0,0 +1,77 @@
package output
import (
"context"
"fmt"
"io"
)
type Verbosity int
const (
VerbosityQuiet Verbosity = iota - 1
VerbosityNorm
VerbosityInfo
VerbosityDebug
VerbosityTrace
)
type Output func(ctx context.Context, verb Verbosity, msg string, args ...KeyValue) (int, error)
func (o Output) Print(ctx context.Context, args ...interface{}) {
o(ctx, VerbosityNorm, fmt.Sprint(args...))
}
func (o Output) PrintKV(ctx context.Context, msg string, kv ...KeyValue) {
o(ctx, VerbosityNorm, msg, kv...)
}
func (o Output) Printf(ctx context.Context, format string, args ...interface{}) {
o(ctx, VerbosityNorm, fmt.Sprintf(format, args...))
}
func (o Output) Println(ctx context.Context, args ...interface{}) {
o(ctx, VerbosityNorm, fmt.Sprintln(args...))
}
func (o Output) Info(ctx context.Context, args ...interface{}) {
o(ctx, VerbosityInfo, fmt.Sprint(args...))
}
func (o Output) InfoKV(ctx context.Context, msg string, kv ...KeyValue) {
o(ctx, VerbosityInfo, msg, kv...)
}
func (o Output) Debug(ctx context.Context, args ...interface{}) {
o(ctx, VerbosityDebug, fmt.Sprint(args...))
}
func (o Output) DebugKV(ctx context.Context, msg string, kv ...KeyValue) {
o(ctx, VerbosityDebug, msg, kv...)
}
func (o Output) Trace(ctx context.Context, args ...interface{}) {
o(ctx, VerbosityTrace, fmt.Sprint(args...))
}
func (o Output) TraceKV(ctx context.Context, msg string, kv ...KeyValue) {
o(ctx, VerbosityTrace, msg, kv...)
}
func (o Output) Write(b []byte) (int, error) {
return o(context.Background(), VerbosityNorm, string(b))
}
func (o Output) Writer(ctx context.Context, verb Verbosity) io.Writer {
return verbosityWriter{ctx, o, verb}
}
type verbosityWriter struct {
ctx context.Context
out Output
verb Verbosity
}
func (w verbosityWriter) Write(b []byte) (int, error) {
return w.out(w.ctx, w.verb, string(b))
}

51
output/style/color.go Normal file
View File

@@ -0,0 +1,51 @@
package style
const (
Black Color = "0"
Red Color = "1"
Green Color = "2"
Yellow Color = "3"
Blue Color = "4"
Magenta Color = "5"
Cyan Color = "6"
White Color = "7"
Default Color = "9"
)
const (
Bold Option = "122"
Underscore Option = "424"
Blink Option = "525"
Reverse Option = "727"
Conseal Option = "828"
)
const (
ActionSet = 1
ActionUnset = 2
)
type Option string
func (o Option) Apply(action int) string {
v := string(o)
switch action {
case ActionSet:
return v[0:1]
case ActionUnset:
return v[1:]
}
return ""
}
type Color string
func (c Color) Apply(action int) string {
if action == ActionSet {
return string(c)
}
return string(Default)
}

88
output/style/style.go Normal file
View File

@@ -0,0 +1,88 @@
package style
import (
"errors"
"fmt"
"strings"
"sync"
)
//nolint: gochecknoglobals
var (
styles = map[string]Style{
"error": {Foreground: White, Background: Red},
"info": {Foreground: Green},
"comment": {Foreground: Yellow},
"question": {Foreground: Black, Background: Cyan},
}
stylesMu sync.Mutex
empty = Style{}
)
var (
ErrNotFound = errors.New("console: style not found")
ErrDuplicateStyle = errors.New("console: Register called twice")
)
func Empty() Style {
return empty
}
func Find(name string) (Style, error) {
if st, has := styles[name]; has {
return st, nil
}
return empty, ErrNotFound
}
func Register(name string, style Style) error {
stylesMu.Lock()
defer stylesMu.Unlock()
if _, has := styles[name]; has {
return fmt.Errorf("%w for style %s", ErrDuplicateStyle, name)
}
styles[name] = style
return nil
}
func MustRegister(name string, style Style) {
if err := Register(name, style); err != nil {
panic(err)
}
}
type Style struct {
Background Color
Foreground Color
Options []Option
}
func (s Style) Apply(msg string) string {
return s.Set(ActionSet) + msg + s.Set(ActionUnset)
}
func (s Style) Set(action int) string {
style := make([]string, 0, len(s.Options))
if s.Foreground != "" {
style = append(style, "3"+s.Foreground.Apply(action))
}
if s.Background != "" {
style = append(style, "4"+s.Background.Apply(action))
}
for _, opt := range s.Options {
style = append(style, opt.Apply(action))
}
if len(style) == 0 {
return ""
}
return "\033[" + strings.Join(style, ";") + "m"
}

57
output/value.go Normal file
View File

@@ -0,0 +1,57 @@
package output
import "fmt"
type Type int
const (
TypeAny Type = iota
TypeBool
TypeInt
TypeInt64
TypeUint
TypeUint64
TypeFloat64
TypeString
)
type Value struct {
vtype Type
value interface{}
}
func (v Value) String() string {
return fmt.Sprint(v.value)
}
func AnyValue(v interface{}) Value {
return Value{vtype: TypeAny, value: v}
}
func BoolValue(v bool) Value {
return Value{vtype: TypeBool, value: v}
}
func IntValue(v int) Value {
return Value{vtype: TypeInt, value: v}
}
func Int64Value(v int64) Value {
return Value{vtype: TypeInt64, value: v}
}
func UintValue(v uint) Value {
return Value{vtype: TypeUint, value: v}
}
func Uint64Value(v uint64) Value {
return Value{vtype: TypeUint64, value: v}
}
func Float64Value(v float64) Value {
return Value{vtype: TypeFloat64, value: v}
}
func StringValue(v string) Value {
return Value{vtype: TypeString, value: v}
}

23
output/verbosity/norm.go Normal file
View File

@@ -0,0 +1,23 @@
package verbosity
import (
"context"
"gitoa.ru/go-4devs/console/output"
)
func Verb(out output.Output, verb output.Verbosity) output.Output {
return func(ctx context.Context, v output.Verbosity, msg string, kv ...output.KeyValue) (int, error) {
if verb >= v {
return out(ctx, v, msg, kv...)
}
return 0, nil
}
}
func Quiet() output.Output {
return func(context.Context, output.Verbosity, string, ...output.KeyValue) (int, error) {
return 0, nil
}
}

22
output/wrap/formatter.go Normal file
View File

@@ -0,0 +1,22 @@
package wrap
import (
"context"
"gitoa.ru/go-4devs/console/output"
"gitoa.ru/go-4devs/console/output/formatter"
)
func Format(out output.Output, format *formatter.Formatter) output.Output {
return func(ctx context.Context, v output.Verbosity, msg string, kv ...output.KeyValue) (int, error) {
return out(ctx, v, format.Format(ctx, msg), kv...)
}
}
func Ansi(out output.Output) output.Output {
return Format(out, formatter.Ansi())
}
func None(out output.Output) output.Output {
return Format(out, formatter.None())
}

44
output/writer/output.go Normal file
View File

@@ -0,0 +1,44 @@
package writer
import (
"bytes"
"context"
"fmt"
"io"
"os"
"strings"
"gitoa.ru/go-4devs/console/output"
)
func Stderr() output.Output {
return New(os.Stderr, String)
}
func Stdout() output.Output {
return New(os.Stdout, String)
}
func Buffer(buf *bytes.Buffer) output.Output {
return New(buf, String)
}
func String(_ output.Verbosity, msg string, kv ...output.KeyValue) string {
if len(kv) > 0 {
newline := ""
if msg[len(msg)-1:] == "\n" {
newline = "\n"
}
return "msg=\"" + strings.TrimSpace(msg) + "\", " + output.KeyValues(kv).String() + newline
}
return msg
}
func New(w io.Writer, format func(verb output.Verbosity, msg string, kv ...output.KeyValue) string) output.Output {
return func(ctx context.Context, verb output.Verbosity, msg string, kv ...output.KeyValue) (int, error) {
return fmt.Fprint(w, format(verb, msg, kv...))
}
}

View File

@@ -0,0 +1,49 @@
package writer_test
import (
"bytes"
"context"
"testing"
"gitoa.ru/go-4devs/console/output"
"gitoa.ru/go-4devs/console/output/writer"
)
func TestNew(t *testing.T) {
ctx := context.Background()
buf := bytes.Buffer{}
wr := writer.New(&buf, writer.String)
cases := map[string]struct {
ex string
kv []output.KeyValue
}{
"message": {
ex: "message",
},
"msg with kv": {
ex: "msg=\"msg with kv\", string key=\"string value\", bool key=\"false\", int key=\"42\"",
kv: []output.KeyValue{
output.String("string key", "string value"),
output.Bool("bool key", false),
output.Int("int key", 42),
},
},
"msg with newline \n": {
ex: "msg=\"msg with newline\", int=\"42\"\n",
kv: []output.KeyValue{
output.Int("int", 42),
},
},
}
for msg, data := range cases {
wr.InfoKV(ctx, msg, data.kv...)
if data.ex != buf.String() {
t.Errorf("message not equals expext:%s, got:%s", data.ex, buf.String())
}
buf.Reset()
}
}