Add user prompt/confirmation utilities

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

@ -46,8 +46,8 @@ func (f *NoteFinder) Find(opts note.FinderOpts) ([]note.Match, error) {
for _, match := range matches {
fzf.Add([]string{
match.Path,
f.styler.MustStyle(match.Title, style.Rule("yellow")),
f.styler.MustStyle(stringsutil.JoinLines(match.Body), style.Rule("faint")),
f.styler.MustStyle(match.Title, style.RuleYellow),
f.styler.MustStyle(stringsutil.JoinLines(match.Body), style.RuleBlack),
})
}

@ -0,0 +1,84 @@
package tty
import (
"fmt"
"strings"
"github.com/mickael-menu/zk/core/style"
)
// PromptOpt holds metadata about a possible prompt response.
type PromptOpt struct {
// Default value for the response.
Label string
// Short description explaining this response.
Description string
// All recognized values for this response.
AllowedResponses []string
}
// Prompt displays a message and waits for the user to input one of the
// available options.
// Returns the selected option index.
func (t *TTY) Prompt(msg string, defaultOpt int, options []PromptOpt) int {
responses := ""
for i, opt := range options {
if i == len(options)-1 {
responses += " or "
} else if i > 0 {
responses += ", "
}
responses += opt.Label
}
printHelp := func() {
fmt.Println("\nExpected responses:")
for _, opt := range options {
fmt.Printf(" %v\t%v\n", opt.Label, opt.Description)
}
fmt.Println()
}
for {
fmt.Printf("%s\n%s > ", msg, responses)
// Don't prompt when --no-input is on.
if t.NoInput {
fmt.Println(options[defaultOpt].AllowedResponses[0])
return defaultOpt
}
var response string
_, err := fmt.Scan(&response)
if err != nil {
return defaultOpt
}
response = strings.ToLower(response)
for i, opt := range options {
for _, allowedResp := range opt.AllowedResponses {
if response == strings.ToLower(allowedResp) {
return i
}
}
}
printHelp()
}
}
// Confirm is a shortcut to prompt a yes/no question to the user.
func (t *TTY) Confirm(msg string, yesDescription string, noDescription string) bool {
return t.Prompt(msg, 1, []PromptOpt{
{
Label: t.MustStyle("y", style.RuleEmphasis) + "es",
Description: yesDescription,
AllowedResponses: []string{"yes", "y", "ok"},
},
{
Label: t.MustStyle("n", style.RuleEmphasis) + "o",
Description: noDescription,
AllowedResponses: []string{"no", "n"},
},
}) == 0
}

@ -7,18 +7,12 @@ import (
"github.com/mickael-menu/zk/core/style"
)
// Styler is a text styler using ANSI escape codes to be used with a TTY.
type Styler struct{}
func NewStyler() *Styler {
return &Styler{}
}
func (s *Styler) Style(text string, rules ...style.Rule) (string, error) {
// Style implements style.Styler using ANSI escape codes to be used with a TTY.
func (t *TTY) Style(text string, rules ...style.Rule) (string, error) {
if text == "" {
return text, nil
}
attrs, err := s.attributes(expandThemeAliases(rules))
attrs, err := attributes(expandThemeAliases(rules))
if err != nil {
return "", err
}
@ -28,8 +22,8 @@ func (s *Styler) Style(text string, rules ...style.Rule) (string, error) {
return color.New(attrs...).Sprint(text), nil
}
func (s *Styler) MustStyle(text string, rules ...style.Rule) string {
text, err := s.Style(text, rules...)
func (t *TTY) MustStyle(text string, rules ...style.Rule) string {
text, err := t.Style(text, rules...)
if err != nil {
panic(err.Error())
}
@ -38,9 +32,10 @@ func (s *Styler) MustStyle(text string, rules ...style.Rule) string {
// FIXME: User config
var themeAliases = map[style.Rule][]style.Rule{
"title": {"bold", "yellow"},
"path": {"underline", "cyan"},
"term": {"red"},
"title": {"bold", "yellow"},
"path": {"underline", "cyan"},
"term": {"red"},
"emphasis": {"bold", "cyan"},
}
func expandThemeAliases(rules []style.Rule) []style.Rule {
@ -108,7 +103,7 @@ var attrsMapping = map[style.Rule]color.Attribute{
style.RuleBrightWhiteBg: color.BgHiWhite,
}
func (s *Styler) attributes(rules []style.Rule) ([]color.Attribute, error) {
func attributes(rules []style.Rule) ([]color.Attribute, error) {
attrs := make([]color.Attribute, 0)
for _, rule := range rules {

@ -8,42 +8,42 @@ import (
"github.com/mickael-menu/zk/util/test/assert"
)
func createStyler() *Styler {
func createTTY() *TTY {
color.NoColor = false // Otherwise the color codes are not injected during tests
return &Styler{}
return New()
}
func TestStyleNoRule(t *testing.T) {
res, err := createStyler().Style("Hello")
res, err := createTTY().Style("Hello")
assert.Nil(t, err)
assert.Equal(t, res, "Hello")
}
func TestStyleOneRule(t *testing.T) {
res, err := createStyler().Style("Hello", style.Rule("red"))
res, err := createTTY().Style("Hello", style.Rule("red"))
assert.Nil(t, err)
assert.Equal(t, res, "\033[31mHello\033[0m")
}
func TestStyleMultipleRule(t *testing.T) {
res, err := createStyler().Style("Hello", style.Rule("red"), style.Rule("bold"))
res, err := createTTY().Style("Hello", style.Rule("red"), style.Rule("bold"))
assert.Nil(t, err)
assert.Equal(t, res, "\033[31;1mHello\033[0m")
}
func TestStyleUnknownRule(t *testing.T) {
_, err := createStyler().Style("Hello", style.Rule("unknown"))
_, err := createTTY().Style("Hello", style.Rule("unknown"))
assert.Err(t, err, "unknown styling rule: unknown")
}
func TestStyleEmptyString(t *testing.T) {
res, err := createStyler().Style("", style.Rule("bold"))
res, err := createTTY().Style("", style.Rule("bold"))
assert.Nil(t, err)
assert.Equal(t, res, "")
}
func TestStyleAllRules(t *testing.T) {
styler := createStyler()
styler := createTTY()
test := func(rule string, expected string) {
res, err := styler.Style("Hello", style.Rule(rule))
assert.Nil(t, err)
@ -53,6 +53,7 @@ func TestStyleAllRules(t *testing.T) {
test("title", "1;33m")
test("path", "4;36m")
test("term", "31m")
test("emphasis", "1;36m")
test("bold", "1m")
test("faint", "2m")

@ -0,0 +1,9 @@
package tty
type TTY struct {
NoInput bool
}
func New() *TTY {
return &TTY{}
}

@ -18,6 +18,7 @@ import (
type Container struct {
Date date.Provider
Logger util.Logger
TTY *tty.TTY
templateLoader *handlebars.Loader
}
@ -29,28 +30,25 @@ func NewContainer() *Container {
// zk is short-lived, so we freeze the current date to use the same
// date for any rendering during the execution.
Date: &date,
TTY: tty.New(),
}
}
func (c *Container) TemplateLoader(lang string) *handlebars.Loader {
if c.templateLoader == nil {
handlebars.Init(lang, c.Logger, tty.NewStyler())
handlebars.Init(lang, c.Logger, c.TTY)
c.templateLoader = handlebars.NewLoader()
}
return c.templateLoader
}
func (c *Container) Styler() *tty.Styler {
return tty.NewStyler()
}
func (c *Container) Parser() *markdown.Parser {
return markdown.NewParser()
}
func (c *Container) NoteFinder(tx sqlite.Transaction) note.Finder {
notes := sqlite.NewNoteDAO(tx, c.Logger)
return fzf.NewNoteFinder(notes, c.Styler())
return fzf.NewNoteFinder(notes, c.TTY)
}
// Database returns the DB instance for the given slip box, after executing any

@ -43,7 +43,7 @@ func (cmd *List) Run(container *Container) error {
}
templates := container.TemplateLoader(zk.Config.Lang)
styler := container.Styler()
styler := container.TTY
format := opt.NewNotEmptyString(cmd.Format)
formatter, err := note.NewFormatter(zk.Path, wd, format, templates, styler)
if err != nil {

@ -13,9 +13,14 @@ type Rule string
// Predefined styling rules.
var (
// Title of a note.
RuleTitle = Rule("title")
RulePath = Rule("path")
RuleTerm = Rule("term")
// Path to slip box file.
RulePath = Rule("path")
// Searched for term in a note.
RuleTerm = Rule("term")
// Element to emphasize, for example the short version of a prompt response: [y]es.
RuleEmphasis = Rule("emphasis")
RuleBold = Rule("bold")
RuleItalic = Rule("italic")

@ -13,6 +13,7 @@ var cli struct {
Init cmd.Init `cmd help:"Create a slip box in the given directory"`
List cmd.List `cmd help:"List notes matching given criteria"`
New cmd.New `cmd help:"Create a new note in the given slip box directory"`
NoInput NoInput `help:"Never prompt or ask for confirmation"`
Version kong.VersionFlag `help:"Print zk version"`
}
@ -21,6 +22,7 @@ func main() {
container := cmd.NewContainer()
ctx := kong.Parse(&cli,
kong.Bind(container),
kong.Name("zk"),
kong.Vars{
"version": Version,
@ -29,3 +31,11 @@ func main() {
err := ctx.Run(container)
ctx.FatalIfErrorf(err)
}
// NoInput is a flag preventing any user prompt when enabled.
type NoInput bool
func (f NoInput) BeforeApply(container *cmd.Container) error {
container.TTY.NoInput = true
return nil
}

Loading…
Cancel
Save