diff --git a/pkg/cli/glyph.go b/pkg/cli/glyph.go new file mode 100644 index 0000000..c3af44f --- /dev/null +++ b/pkg/cli/glyph.go @@ -0,0 +1,81 @@ +package cli + +import ( + "bytes" + "unicode" +) + +// GlyphTheme defines which symbols to use. +type GlyphTheme int + +const ( + ThemeUnicode GlyphTheme = iota + ThemeEmoji + ThemeASCII +) + +var currentTheme = ThemeUnicode + +func UseUnicode() { currentTheme = ThemeUnicode } +func UseEmoji() { currentTheme = ThemeEmoji } +func UseASCII() { currentTheme = ThemeASCII } + +func glyphMap() map[string]string { + switch currentTheme { + case ThemeEmoji: + return glyphMapEmoji + case ThemeASCII: + return glyphMapASCII + default: + return glyphMapUnicode + } +} + +// Glyph converts a shortcode to its symbol. +func Glyph(code string) string { + if sym, ok := glyphMap()[code]; ok { + return sym + } + return code +} + +func compileGlyphs(x string) string { + if x == "" { + return "" + } + input := bytes.NewBufferString(x) + output := bytes.NewBufferString("") + + for { + r, _, err := input.ReadRune() + if err != nil { + break + } + if r == ':' { + output.WriteString(replaceGlyph(input)) + } else { + output.WriteRune(r) + } + } + return output.String() +} + +func replaceGlyph(input *bytes.Buffer) string { + code := bytes.NewBufferString(":") + for { + r, _, err := input.ReadRune() + if err != nil { + return code.String() + } + if r == ':' && code.Len() == 1 { + return code.String() + replaceGlyph(input) + } + code.WriteRune(r) + if unicode.IsSpace(r) { + return code.String() + } + if r == ':' { + return Glyph(code.String()) + } + } +} diff --git a/pkg/cli/glyph_maps.go b/pkg/cli/glyph_maps.go new file mode 100644 index 0000000..0aed5b8 --- /dev/null +++ b/pkg/cli/glyph_maps.go @@ -0,0 +1,25 @@ +package cli + +var glyphMapUnicode = map[string]string{ + ":check:": "✓", ":cross:": "✗", ":warn:": "⚠", ":info:": "ℹ", + ":question:": "?", ":skip:": "○", ":dot:": "●", ":circle:": "◯", + ":arrow_right:": "→", ":arrow_left:": "←", ":arrow_up:": "↑", ":arrow_down:": "↓", + ":pointer:": "▶", ":bullet:": "•", ":dash:": "─", ":pipe:": "│", + ":corner:": "└", ":tee:": "├", ":pending:": "…", ":spinner:": "⠋", +} + +var glyphMapEmoji = map[string]string{ + ":check:": "✅", ":cross:": "❌", ":warn:": "⚠️", ":info:": "ℹ️", + ":question:": "❓", ":skip:": "⏭️", ":dot:": "🔵", ":circle:": "⚪", + ":arrow_right:": "➡️", ":arrow_left:": "⬅️", ":arrow_up:": "⬆️", ":arrow_down:": "⬇️", + ":pointer:": "▶️", ":bullet:": "•", ":dash:": "─", ":pipe:": "│", + ":corner:": "└", ":tee:": "├", ":pending:": "⏳", ":spinner:": "🔄", +} + +var glyphMapASCII = map[string]string{ + ":check:": "[OK]", ":cross:": "[FAIL]", ":warn:": "[WARN]", ":info:": "[INFO]", + ":question:": "[?]", ":skip:": "[SKIP]", ":dot:": "[*]", ":circle:": "[ ]", + ":arrow_right:": "->", ":arrow_left:": "<-", ":arrow_up:": "^", ":arrow_down:": "v", + ":pointer:": ">", ":bullet:": "*", ":dash:": "-", ":pipe:": "|", + ":corner:": "`", ":tee:": "+", ":pending:": "...", ":spinner:": "-", +}