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, "") }