Add the core Styler, a default TTY implementation and a {{style}} template helper

pull/6/head
Mickaël Menu 3 years ago
parent 8ed6667743
commit e47eabf421
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -6,16 +6,18 @@ import (
"github.com/aymerick/raymond"
"github.com/mickael-menu/zk/adapter/handlebars/helpers"
"github.com/mickael-menu/zk/core"
"github.com/mickael-menu/zk/core/templ"
"github.com/mickael-menu/zk/util"
"github.com/mickael-menu/zk/util/errors"
)
func Init(lang string, logger util.Logger) {
func Init(lang string, logger util.Logger, styler core.Styler) {
helpers.RegisterDate(logger)
helpers.RegisterPrepend(logger)
helpers.RegisterShell(logger)
helpers.RegisterSlug(logger, lang)
helpers.RegisterSlug(lang, logger)
helpers.RegisterStyle(styler, logger)
}
// Template renders a parsed handlebars template.

@ -1,16 +1,29 @@
package handlebars
import (
"fmt"
"testing"
"time"
"github.com/mickael-menu/zk/core"
"github.com/mickael-menu/zk/util"
"github.com/mickael-menu/zk/util/assert"
"github.com/mickael-menu/zk/util/fixtures"
)
func init() {
Init("en", &util.NullLogger)
Init("en", &util.NullLogger, &styler{})
}
// styler is a test double for core.Styler
// "hello", "red" -> "red(hello)"
type styler struct{}
func (s *styler) Style(text string, rules ...core.StyleRule) (string, error) {
for _, rule := range rules {
text = fmt.Sprintf("%s(%s)", rule, text)
}
return text, nil
}
func testString(t *testing.T, template string, context interface{}, expected string) {
@ -126,3 +139,12 @@ func TestShellHelper(t *testing.T) {
"Hello, world!\n",
)
}
func TestStyleHelper(t *testing.T) {
// inline
testString(t, "{{style 'single' 'Some text'}}", nil, "single(Some text)")
testString(t, "{{style 'red bold' 'Another text'}}", nil, "bold(red(Another text))")
// block
testString(t, "{{#style 'single'}}A multiline\ntext{{/style}}", nil, "single(A multiline\ntext)")
}

@ -10,7 +10,7 @@ import (
//
// {{slug "This will be slugified!"}} -> this-will-be-slugified
// {{#slug}}This will be slugified!{{/slug}} -> this-will-be-slugified
func RegisterSlug(logger util.Logger, lang string) {
func RegisterSlug(lang string, logger util.Logger) {
raymond.RegisterHelper("slug", func(opt interface{}) string {
switch arg := opt.(type) {
case *raymond.Options:

@ -0,0 +1,42 @@
package helpers
import (
"strings"
"github.com/aymerick/raymond"
"github.com/mickael-menu/zk/core"
"github.com/mickael-menu/zk/util"
)
// RegisterStyle register the {{style}} template helpers which stylizes the
// text input according to predefined styling rules.
//
// {{style "date" created}}
// {{#style "red"}}Hello, world{{/style}}
func RegisterStyle(styler core.Styler, logger util.Logger) {
style := func(keys string, text string) string {
rules := make([]core.StyleRule, 0)
for _, key := range strings.Fields(keys) {
rules = append(rules, core.StyleRule(key))
}
res, err := styler.Style(text, rules...)
if err != nil {
logger.Err(err)
return text
} else {
return res
}
}
raymond.RegisterHelper("style", func(rules string, opt interface{}) string {
switch arg := opt.(type) {
case *raymond.Options:
return style(rules, arg.Fn())
case string:
return style(rules, arg)
default:
logger.Printf("the {{style}} template helper is expecting a string as input, received: %v", opt)
return ""
}
})
}

@ -0,0 +1,90 @@
package tty
import (
"fmt"
"github.com/fatih/color"
"github.com/mickael-menu/zk/core"
)
// Styler is a text styler using ANSI escape codes to be used with a TTY.
type Styler struct{}
func NewStyler() *Styler {
return &Styler{}
}
// FIXME: Semantic rules
func (s *Styler) Style(text string, rules ...core.StyleRule) (string, error) {
attrs, err := s.attributes(rules)
if err != nil {
return "", err
}
if len(attrs) == 0 {
return text, nil
}
return color.New(attrs...).Sprint(text), nil
}
var attrsMapping = map[core.StyleRule]color.Attribute{
"reset": color.Reset,
"bold": color.Bold,
"faint": color.Faint,
"italic": color.Italic,
"underline": color.Underline,
"blink-slow": color.BlinkSlow,
"blink-fast": color.BlinkRapid,
"hidden": color.Concealed,
"strikethrough": color.CrossedOut,
"black": color.FgBlack,
"red": color.FgRed,
"green": color.FgGreen,
"yellow": color.FgYellow,
"blue": color.FgBlue,
"magenta": color.FgMagenta,
"cyan": color.FgCyan,
"white": color.FgWhite,
"black-bg": color.BgBlack,
"red-bg": color.BgRed,
"green-bg": color.BgGreen,
"yellow-bg": color.BgYellow,
"blue-bg": color.BgBlue,
"magenta-bg": color.BgMagenta,
"cyan-bg": color.BgCyan,
"white-bg": color.BgWhite,
"bright-black": color.FgHiBlack,
"bright-red": color.FgHiRed,
"bright-green": color.FgHiGreen,
"bright-yellow": color.FgHiYellow,
"bright-blue": color.FgHiBlue,
"bright-magenta": color.FgHiMagenta,
"bright-cyan": color.FgHiCyan,
"bright-white": color.FgHiWhite,
"bright-black-bg": color.BgHiBlack,
"bright-red-bg": color.BgHiRed,
"bright-green-bg": color.BgHiGreen,
"bright-yellow-bg": color.BgHiYellow,
"bright-blue-bg": color.BgHiBlue,
"bright-magenta-bg": color.BgHiMagenta,
"bright-cyan-bg": color.BgHiCyan,
"bright-white-bg": color.BgHiWhite,
}
func (s *Styler) attributes(rules []core.StyleRule) ([]color.Attribute, error) {
attrs := make([]color.Attribute, 0)
for _, rule := range rules {
attr, ok := attrsMapping[rule]
if !ok {
return attrs, fmt.Errorf("unknown styling rule: %v", rule)
} else {
attrs = append(attrs, attr)
}
}
return attrs, nil
}

@ -0,0 +1,92 @@
package tty
import (
"testing"
"github.com/fatih/color"
"github.com/mickael-menu/zk/core"
"github.com/mickael-menu/zk/util/assert"
)
func createStyler() *Styler {
color.NoColor = false // Otherwise the color codes are not injected during tests
return &Styler{}
}
func TestStyleNoRule(t *testing.T) {
res, err := createStyler().Style("Hello")
assert.Nil(t, err)
assert.Equal(t, res, "Hello")
}
func TestStyleOneRule(t *testing.T) {
res, err := createStyler().Style("Hello", core.StyleRule("red"))
assert.Nil(t, err)
assert.Equal(t, res, "\033[31mHello\033[0m")
}
func TestStyleMultipleRule(t *testing.T) {
res, err := createStyler().Style("Hello", core.StyleRule("red"), core.StyleRule("bold"))
assert.Nil(t, err)
assert.Equal(t, res, "\033[31;1mHello\033[0m")
}
func TestStyleUnknownRule(t *testing.T) {
_, err := createStyler().Style("Hello", core.StyleRule("unknown"))
assert.Err(t, err, "unknown styling rule: unknown")
}
func TestStyleAllRules(t *testing.T) {
styler := createStyler()
test := func(rule string, expected string) {
res, err := styler.Style("Hello", core.StyleRule(rule))
assert.Nil(t, err)
assert.Equal(t, res, "\033["+expected+"Hello\033[0m")
}
test("reset", "0m")
test("bold", "1m")
test("faint", "2m")
test("italic", "3m")
test("underline", "4m")
test("blink-slow", "5m")
test("blink-fast", "6m")
test("hidden", "8m")
test("strikethrough", "9m")
test("black", "30m")
test("red", "31m")
test("green", "32m")
test("yellow", "33m")
test("blue", "34m")
test("magenta", "35m")
test("cyan", "36m")
test("white", "37m")
test("black-bg", "40m")
test("red-bg", "41m")
test("green-bg", "42m")
test("yellow-bg", "43m")
test("blue-bg", "44m")
test("magenta-bg", "45m")
test("cyan-bg", "46m")
test("white-bg", "47m")
test("bright-black", "90m")
test("bright-red", "91m")
test("bright-green", "92m")
test("bright-yellow", "93m")
test("bright-blue", "94m")
test("bright-magenta", "95m")
test("bright-cyan", "96m")
test("bright-white", "97m")
test("bright-black-bg", "100m")
test("bright-red-bg", "101m")
test("bright-green-bg", "102m")
test("bright-yellow-bg", "103m")
test("bright-blue-bg", "104m")
test("bright-magenta-bg", "105m")
test("bright-cyan-bg", "106m")
test("bright-white-bg", "107m")
}

@ -3,6 +3,7 @@ package cmd
import (
"github.com/mickael-menu/zk/adapter/handlebars"
"github.com/mickael-menu/zk/adapter/sqlite"
"github.com/mickael-menu/zk/adapter/tty"
"github.com/mickael-menu/zk/util"
"github.com/mickael-menu/zk/util/date"
)
@ -26,7 +27,7 @@ func NewContainer() *Container {
func (c *Container) TemplateLoader(lang string) *handlebars.Loader {
if c.templateLoader == nil {
handlebars.Init(lang, c.Logger)
handlebars.Init(lang, c.Logger, tty.NewStyler())
c.templateLoader = handlebars.NewLoader()
}
return c.templateLoader

@ -0,0 +1,20 @@
package core
// Styler stylizes text according to predefined styling rules.
//
// A rule key can be either semantic, e.g. "title" or explicit, e.g. "red".
type Styler interface {
Style(text string, rules ...StyleRule) (string, error)
}
// StyleRule is a key representing a single styling rule.
type StyleRule string
// NullStyler is a Styler with no styling rules.
var NullStyler = nullStyler{}
type nullStyler struct{}
func (s nullStyler) Style(text string, rule ...StyleRule) (string, error) {
return text, nil
}
Loading…
Cancel
Save