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.
mop/markup.go

168 lines
5.1 KiB

// Copyright (c) 2013-2016 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.
11 years ago
11 years ago
package mop
import (
`github.com/nsf/termbox-go`
11 years ago
`regexp`
`strings`
)
// Markup implements some minimalistic text formatting conventions that
// get translated to Termbox colors and attributes. To colorize a string
// wrap it in <color-name>...</> tags. Unlike HTML each tag sets a new
// color whereas the </> tag changes color back to default. For example:
//
// <green>Hello, <red>world!</>
//
// The color tags could be combined with the attributes: <b>...</b> for
// bold, <u>...</u> for underline, and <r>...</r> for reverse. Unlike
// colors the attributes require matching closing tag.
//
// The <right>...</right> tag is used to right align the enclosed string
// (ex. when displaying current time in the upper right corner).
11 years ago
type Markup struct {
10 years ago
Foreground termbox.Attribute // Foreground color.
Background termbox.Attribute // Background color (so far always termbox.ColorDefault).
RightAligned bool // True when the string is right aligned.
tags map[string]termbox.Attribute // Tags to Termbox translation hash.
regex *regexp.Regexp // Regex to identify the supported tag names.
11 years ago
}
10 years ago
// Creates markup to define tag to Termbox translation rules and store default
// colors and column alignments.
func NewMarkup() *Markup {
markup := &Markup{}
10 years ago
markup.Foreground = termbox.ColorDefault
markup.Background = termbox.ColorDefault
markup.RightAligned = false
markup.tags = make(map[string]termbox.Attribute)
10 years ago
markup.tags[`/`] = termbox.ColorDefault
markup.tags[`black`] = termbox.ColorBlack
markup.tags[`red`] = termbox.ColorRed
markup.tags[`green`] = termbox.ColorGreen
markup.tags[`yellow`] = termbox.ColorYellow
markup.tags[`blue`] = termbox.ColorBlue
markup.tags[`magenta`] = termbox.ColorMagenta
10 years ago
markup.tags[`cyan`] = termbox.ColorCyan
markup.tags[`white`] = termbox.ColorWhite
markup.tags[`right`] = termbox.ColorDefault // Termbox can combine attributes and a single color using bitwise OR.
markup.tags[`b`] = termbox.AttrBold // Attribute = 1 << (iota + 4)
markup.tags[`u`] = termbox.AttrUnderline
markup.tags[`r`] = termbox.AttrReverse
markup.regex = markup.supportedTags() // Once we have the hash we could build the regex.
return markup
11 years ago
}
// Tokenize works just like strings.Split() except the resulting array includes
// the delimiters. For example, the "<green>Hello, <red>world!</>" string when
// tokenized by tags produces the following:
//
// [0] "<green>"
// [1] "Hello, "
// [2] "<red>"
// [3] "world!"
// [4] "</>"
//
func (markup *Markup) Tokenize(str string) []string {
matches := markup.regex.FindAllStringIndex(str, -1)
11 years ago
strings := make([]string, 0, len(matches))
head, tail := 0, 0
for _, match := range matches {
tail = match[0]
if match[1] != 0 {
if head != 0 || tail != 0 {
// Apend the text between tags.
strings = append(strings, str[head:tail])
11 years ago
}
// Append the tag itmarkup.
strings = append(strings, str[match[0]:match[1]])
11 years ago
}
head = match[1]
}
if head != len(str) && tail != len(str) {
strings = append(strings, str[head:])
}
return strings
}
// IsTag returns true when the given string looks like markup tag. When the
// tag name matches one of the markup-supported tags it gets translated to
// relevant Termbox attributes and colors.
func (markup *Markup) IsTag(str string) bool {
tag, open := probeForTag(str)
11 years ago
if tag == `` {
return false
}
return markup.process(tag, open)
11 years ago
}
//-----------------------------------------------------------------------------
func (markup *Markup) process(tag string, open bool) bool {
if attribute, ok := markup.tags[tag]; ok {
11 years ago
switch tag {
case `right`:
10 years ago
markup.RightAligned = open // On for <right>, off for </right>.
11 years ago
default:
if open {
if attribute >= termbox.AttrBold {
10 years ago
markup.Foreground |= attribute // Set the Termbox attribute.
11 years ago
} else {
10 years ago
markup.Foreground = attribute // Set the Termbox color.
11 years ago
}
} else {
if attribute >= termbox.AttrBold {
10 years ago
markup.Foreground &= ^attribute // Clear the Termbox attribute.
11 years ago
} else {
markup.Foreground = termbox.ColorDefault
11 years ago
}
}
}
}
return true
}
// supportedTags returns regular expression that matches all possible tags
// supported by the markup, i.e. </?black>|</?red>| ... |<?b>| ... |</?right>
func (markup *Markup) supportedTags() *regexp.Regexp {
11 years ago
arr := []string{}
for tag := range markup.tags {
10 years ago
arr = append(arr, `</?`+tag+`>`)
11 years ago
}
return regexp.MustCompile(strings.Join(arr, `|`))
}
//-----------------------------------------------------------------------------
func probeForTag(str string) (string, bool) {
11 years ago
if len(str) > 2 && str[0:1] == `<` && str[len(str)-1:] == `>` {
return extractTagName(str), str[1:2] != `/`
11 years ago
}
return ``, false
}
// Extract tag name from the given tag, i.e. `<hello>` => `hello`.
func extractTagName(str string) string {
11 years ago
if len(str) < 3 {
return ``
} else if str[1:2] != `/` {
return str[1 : len(str)-1]
} else if len(str) > 3 {
return str[2 : len(str)-1]
}
return `/`
11 years ago
}