You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

231 lines
5.4 KiB

package main
import (
"fmt"
"github.com/pelletier/go-toml"
"regexp"
"strings"
)
// Begin Colors
type color int
const (
black color = iota
red
green
yellow
purple
magenta
cyan
grey
normal color = -1
)
func colorFromString(s string) color {
s = strings.ToLower(s)
switch s {
case "black":
return black
case "red":
return red
case "green":
return green
case "yellow":
return yellow
case "purple":
return purple
case "magenta":
return magenta
case "cyan":
return cyan
case "grey":
return grey
case "normal":
return normal
default:
printError(fmt.Sprintf("color `%s` cannot be parsed.", s))
}
return normal
}
// Style struct for specializing the style/color of a stylize
type Style struct {
foregroundColor color
backgroundColor color
bold bool
italic bool // Currently not supported by the UI library
underline bool
strikethrough bool // Currently not supported by the UI library
inverse bool
}
var basicStyle = Style{normal, normal, false, false, false, false, false}
func styleFromConfig(config *toml.Tree, key string) Style {
key = "Colors." + key + "."
style := basicStyle
if config.Has(key + "foreground") {
style = style.withForeground(colorFromString(config.Get(key + "foreground").(string)))
}
if config.Has(key + "background") {
style = style.withForeground(colorFromString(config.Get(key + "background").(string)))
}
if config.GetDefault(key+"bold", false).(bool) {
style = style.withBold()
}
if config.GetDefault(key+"italic", false).(bool) {
style = style.withItalic()
}
if config.GetDefault(key+"underline", false).(bool) {
style = style.withUnderline()
}
if config.GetDefault(key+"strikethrough", false).(bool) {
style = style.withStrikethrough()
}
if config.GetDefault(key+"inverse", false).(bool) {
style = style.withInverse()
}
return style
}
func (s Style) withForeground(f color) Style {
s.foregroundColor = f
return s
}
func (s Style) withBackground(f color) Style {
s.backgroundColor = f
return s
}
func (s Style) withBold() Style {
s.bold = true
return s
}
func (s Style) withInverse() Style {
s.inverse = true
return s
}
func (s Style) withItalic() Style {
s.italic = true
return s
}
func (s Style) withStrikethrough() Style {
s.strikethrough = true
return s
}
func (s Style) withUnderline() Style {
s.underline = true
return s
}
// TODO create both as `reset` (which it is now) as well as `append`
// which essentially just adds on top. that is relevant in the case of
// bold/italic etc - it should add style - not clear.
func (s Style) toANSI() string {
if colorless {
return ""
}
output := "\x1b[0m\x1b[0"
if s.foregroundColor != normal {
output += fmt.Sprintf(";%d", 30+s.foregroundColor)
}
if s.backgroundColor != normal {
output += fmt.Sprintf(";%d", 40+s.backgroundColor)
}
if s.bold {
output += ";1"
}
if s.italic {
output += ";3"
}
if s.underline {
output += ";4"
}
if s.inverse {
output += ";7"
}
if s.strikethrough {
output += ";9"
}
return output + "m"
}
// End Colors
// Begin StyledString
// StyledString is used to save a message with a style, which can then later be rendered to a string
type StyledString struct {
message string
style Style
}
// TODO handle all formatting types
func (s Style) sprintf(base string, parts ...StyledString) StyledString {
text := s.stylize(removeFormatting(base))
//TODO handle posibility to escape
re := regexp.MustCompile(`\$TEXT`)
for len(re.FindAllString(text.message, 1)) > 0 {
part := parts[0]
parts = parts[1:]
text = text.replaceN("$TEXT", part, 1)
}
return text
}
func (s Style) stylize(msg string) StyledString {
return StyledString{msg, s}
}
func (t StyledString) stringFollowedByStyle(style Style) string {
return t.style.toANSI() + t.message + style.toANSI()
}
func (t StyledString) string() string {
return t.stringFollowedByStyle(basicStyle)
}
func (t StyledString) replace(match string, value StyledString) StyledString {
return t.replaceN(match, value, -1)
}
func (t StyledString) replaceN(match string, value StyledString, n int) StyledString {
t.message = strings.Replace(t.message, match, value.stringFollowedByStyle(t.style), n)
return t
}
func (t StyledString) replaceString(match string, value string) StyledString {
t.message = strings.Replace(t.message, match, value, -1)
return t
}
func (t StyledString) replaceRegex(match string, value StyledString) StyledString {
var re = regexp.MustCompile("(" + match + ")")
t.message = re.ReplaceAllString(t.message, value.stringFollowedByStyle(t.style))
return t
}
// Overrides current formatting
func (t StyledString) colorRegex(match string, style Style) StyledString {
re := regexp.MustCompile("(" + match + ")")
subStrings := re.FindAllString(t.message, -1)
for _, element := range subStrings {
cleanSubstring := style.stylize(removeFormatting(element))
t.message = strings.Replace(t.message, element, cleanSubstring.stringFollowedByStyle(t.style), -1)
}
return t
// Old versionreturn t.replaceRegex(match, style.stylize(`$1`))
}
// Appends the other stylize at the end, but retains same style
func (t StyledString) append(other StyledString) StyledString {
t.message = t.message + other.stringFollowedByStyle(t.style)
return t
}
func (t StyledString) appendString(other string) StyledString {
t.message += other
return t
}
// Begin Formatting
func removeFormatting(s string) string {
reFormatting := regexp.MustCompile(`(?m)\x1b\[(\d*;?)*m`)
return reFormatting.ReplaceAllString(s, "")
}