update whole structure : each syntax feature implements now the 'internal/transform/Transformer' interface ; to apply all transforms on the same input text, use a 'internal/transform/Registry' that dispatches errors | update all transformers' code to the new structure | still pass tests
This commit is contained in:
parent
e553670836
commit
7ee7710e74
33
clifmt.go
33
clifmt.go
|
@ -3,8 +3,12 @@ package clifmt
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/clifmt/internal/color"
|
"git.xdrm.io/go/clifmt/internal/color"
|
||||||
colorTransform "git.xdrm.io/go/clifmt/internal/transform/color"
|
tbold "git.xdrm.io/go/clifmt/internal/syntax/bold"
|
||||||
mdTransform "git.xdrm.io/go/clifmt/internal/transform/markdown"
|
tcolor "git.xdrm.io/go/clifmt/internal/syntax/color"
|
||||||
|
thyperlink "git.xdrm.io/go/clifmt/internal/syntax/hyperlink"
|
||||||
|
titalic "git.xdrm.io/go/clifmt/internal/syntax/italic"
|
||||||
|
tunderline "git.xdrm.io/go/clifmt/internal/syntax/underline"
|
||||||
|
"git.xdrm.io/go/clifmt/internal/transform"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,26 +32,27 @@ func Sprintf(format string, a ...interface{}) (string, error) {
|
||||||
formatted = strings.Replace(formatted, "\\_", underscoreToken, -1)
|
formatted = strings.Replace(formatted, "\\_", underscoreToken, -1)
|
||||||
formatted = strings.Replace(formatted, "\\[", squareBracketToken, -1)
|
formatted = strings.Replace(formatted, "\\[", squareBracketToken, -1)
|
||||||
|
|
||||||
// 3. Colorize
|
// 3. create transformation registry
|
||||||
colorized, err := colorTransform.Transform(formatted, theme)
|
reg := transform.Registry{Transformers: make([]transform.Transformer, 0, 10)}
|
||||||
if err != nil {
|
reg.Transformers = append(reg.Transformers, tcolor.Export)
|
||||||
return "", err
|
reg.Transformers = append(reg.Transformers, tbold.Export)
|
||||||
}
|
reg.Transformers = append(reg.Transformers, titalic.Export)
|
||||||
|
reg.Transformers = append(reg.Transformers, tunderline.Export)
|
||||||
|
reg.Transformers = append(reg.Transformers, thyperlink.Export)
|
||||||
|
|
||||||
// 4. Markdown format
|
transformed, err := reg.Transform(formatted)
|
||||||
markdown, err := mdTransform.Transform(colorized)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Restore token-protected characters
|
// 5. Restore token-protected characters
|
||||||
markdown = strings.Replace(markdown, dollarToken, "$", -1)
|
transformed = strings.Replace(transformed, dollarToken, "$", -1)
|
||||||
markdown = strings.Replace(markdown, asteriskToken, "*", -1)
|
transformed = strings.Replace(transformed, asteriskToken, "*", -1)
|
||||||
markdown = strings.Replace(markdown, underscoreToken, "_", -1)
|
transformed = strings.Replace(transformed, underscoreToken, "_", -1)
|
||||||
markdown = strings.Replace(markdown, squareBracketToken, "[", -1)
|
transformed = strings.Replace(transformed, squareBracketToken, "[", -1)
|
||||||
|
|
||||||
// 6. return final output
|
// 6. return final output
|
||||||
return markdown, nil
|
return transformed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Printf prints a terminal-colorized output following the coloring format
|
// Printf prints a terminal-colorized output following the coloring format
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package bold
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type export string
|
||||||
|
|
||||||
|
var Export = export("bold")
|
||||||
|
|
||||||
|
func (syn export) Regex() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`(?m)\*\*((?:[^\*]+\*?)+)\*\*`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syn export) Transform(args ...string) (string, error) {
|
||||||
|
// no arg, empty -> ignore
|
||||||
|
if len(args) < 1 || len(args[0]) < 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("\x1b[1m%s\x1b[22m", strings.Replace(args[0], "\x1b[0m", "\x1b[0m\x1b[1m", -1)), nil
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package color
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.xdrm.io/go/clifmt/internal/color"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var theme = color.DefaultTheme()
|
||||||
|
|
||||||
|
type export string
|
||||||
|
|
||||||
|
var Export = export("color")
|
||||||
|
|
||||||
|
func (syn export) Regex() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`(?m)\${([^$]+)}\(((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6})))?(?:\:((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6}))))?\)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syn export) Transform(args ...string) (string, error) {
|
||||||
|
// no arg, no color -> error
|
||||||
|
if len(args) < 3 {
|
||||||
|
return "", fmt.Errorf("invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract colors
|
||||||
|
var (
|
||||||
|
fg *color.T = nil
|
||||||
|
bg *color.T = nil
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(args[1]) > 0 {
|
||||||
|
tmp, err := color.Parse(theme, args[1])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fg = &tmp
|
||||||
|
}
|
||||||
|
if len(args[2]) > 0 {
|
||||||
|
tmp, err := color.Parse(theme, args[2])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
bg = &tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorize(args[0], fg, bg), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// colorize returns the terminal-formatted @text colorized with the @fg and @bg colors
|
||||||
|
func colorize(t string, fg *color.T, bg *color.T) string {
|
||||||
|
// no coloring
|
||||||
|
if fg == nil && bg == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// only foreground
|
||||||
|
if bg == nil {
|
||||||
|
return fmt.Sprintf("\x1b[38;2;%d;%d;%dm%s\x1b[0m", fg.Red(), fg.Green(), fg.Blue(), t)
|
||||||
|
}
|
||||||
|
// only background
|
||||||
|
if fg == nil {
|
||||||
|
return fmt.Sprintf("\x1b[48;2;%d;%d;%dm%s\x1b[0m", bg.Red(), bg.Green(), bg.Blue(), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// both colors
|
||||||
|
return fmt.Sprintf("\x1b[38;2;%d;%d;%d;48;2;%d;%d;%dm%s\x1b[0m", fg.Red(), fg.Green(), fg.Blue(), bg.Red(), bg.Green(), bg.Blue(), t)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package hyperlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type export string
|
||||||
|
|
||||||
|
var Export = export("hyperlink")
|
||||||
|
|
||||||
|
func (syn export) Regex() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`(?m)\[([^\[]+)\]\(([^\)]+)\)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syn export) Transform(args ...string) (string, error) {
|
||||||
|
// no arg, empty -> ignore
|
||||||
|
if len(args) < 2 || len(args[0]) < 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", args[1], args[0]), nil
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package italic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type export string
|
||||||
|
|
||||||
|
var Export = export("italic")
|
||||||
|
|
||||||
|
func (syn export) Regex() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`(?m)\*([^\*]+)\*`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syn export) Transform(args ...string) (string, error) {
|
||||||
|
// no arg, empty -> ignore
|
||||||
|
if len(args) < 1 || len(args[0]) < 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("\x1b[3m%s\x1b[23m", strings.Replace(args[0], "\x1b[0m", "\x1b[0m\x1b[3m", -1)), nil
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package underline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type export string
|
||||||
|
|
||||||
|
var Export = export("underline")
|
||||||
|
|
||||||
|
func (syn export) Regex() *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`(?m)_([^_]+)_`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syn export) Transform(args ...string) (string, error) {
|
||||||
|
// no arg, empty -> ignore
|
||||||
|
if len(args) < 1 || len(args[0]) < 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("\x1b[4m%s\x1b[24m", strings.Replace(args[0], "\x1b[0m", "\x1b[0m\x1b[4m", -1)), nil
|
||||||
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
package color
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"git.xdrm.io/go/clifmt/internal/color"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// extractor helps extract features from the coloring format defined as follows :
|
|
||||||
//
|
|
||||||
// - [Color] -> [a-z] # named color
|
|
||||||
// - [Color] -> #[0-9a-f]{3} # hexa color (shortcode)
|
|
||||||
// - [Color] -> #[0-9a-f]{6} # hexa color (full-sized)
|
|
||||||
// - [Text] -> ANY
|
|
||||||
// - [Format] -> ${Text}(Color:Color) # foreground, background colors
|
|
||||||
// - [Format] -> ${Text}(Color) # foreground color only
|
|
||||||
// - [Format] -> ${Text}(:Color) # background color only
|
|
||||||
var extractor = regexp.MustCompile(`(?m)\${([^$]+)}\(((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6})))?(?:\:((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6}))))?\)`)
|
|
||||||
|
|
||||||
// colorize returns the terminal-formatted @text colorized with the @fg and @bg colors
|
|
||||||
func colorize(t string, fg *color.T, bg *color.T) string {
|
|
||||||
// no coloring
|
|
||||||
if fg == nil && bg == nil {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// only foreground
|
|
||||||
if bg == nil {
|
|
||||||
return fmt.Sprintf("\x1b[38;2;%d;%d;%dm%s\x1b[0m", fg.Red(), fg.Green(), fg.Blue(), t)
|
|
||||||
}
|
|
||||||
// only background
|
|
||||||
if fg == nil {
|
|
||||||
return fmt.Sprintf("\x1b[48;2;%d;%d;%dm%s\x1b[0m", bg.Red(), bg.Green(), bg.Blue(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// both colors
|
|
||||||
return fmt.Sprintf("\x1b[38;2;%d;%d;%d;48;2;%d;%d;%dm%s\x1b[0m", fg.Red(), fg.Green(), fg.Blue(), bg.Red(), bg.Green(), bg.Blue(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform the @input text colorized according to the @extractor format
|
|
||||||
func Transform(input string, theme color.Theme) (string, error) {
|
|
||||||
output := ""
|
|
||||||
cursor := int(0)
|
|
||||||
|
|
||||||
// 1. Replace for each match
|
|
||||||
for _, match := range extractor.FindAllStringSubmatchIndex(input, -1) {
|
|
||||||
|
|
||||||
// (1) add gap between input start OR previous match
|
|
||||||
output += input[cursor:match[0]]
|
|
||||||
cursor = match[1]
|
|
||||||
|
|
||||||
// (2) extract features
|
|
||||||
var (
|
|
||||||
text = ""
|
|
||||||
sFg = ""
|
|
||||||
sBg = ""
|
|
||||||
fg *color.T = nil
|
|
||||||
bg *color.T = nil
|
|
||||||
)
|
|
||||||
|
|
||||||
if match[3]-match[2] > 0 {
|
|
||||||
text = input[match[2]:match[3]]
|
|
||||||
}
|
|
||||||
if match[5]-match[4] > 0 {
|
|
||||||
sFg = input[match[4]:match[5]]
|
|
||||||
fgv, err := color.Parse(theme, sFg)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
fg = &fgv
|
|
||||||
}
|
|
||||||
if match[7]-match[6] > 0 {
|
|
||||||
sBg = input[match[6]:match[7]]
|
|
||||||
bgv, err := color.Parse(theme, sBg)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
bg = &bgv
|
|
||||||
}
|
|
||||||
|
|
||||||
// (3) replace text with colorized text
|
|
||||||
output += colorize(text, fg, bg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Add end of input
|
|
||||||
if cursor < len(input) {
|
|
||||||
output += input[cursor:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. print final output
|
|
||||||
return output, nil
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransformerError struct {
|
||||||
|
// Transformer that returned the error
|
||||||
|
Transformer Transformer
|
||||||
|
|
||||||
|
// Err is the actual error
|
||||||
|
Err error
|
||||||
|
|
||||||
|
// Input is the input string to be transformed
|
||||||
|
Input string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *TransformerError) Error() string {
|
||||||
|
return fmt.Sprintf("Transformer <%T> failed on input '%s': %s",
|
||||||
|
err.Transformer,
|
||||||
|
err.Input,
|
||||||
|
err.Err)
|
||||||
|
}
|
|
@ -1,48 +0,0 @@
|
||||||
package markdown
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var boldRe = regexp.MustCompile(`(?m)\*\*((?:[^\*]+\*?)+)\*\*`)
|
|
||||||
|
|
||||||
// boldify returns the terminal-formatted bold text @t
|
|
||||||
func boldify(t string) string {
|
|
||||||
return fmt.Sprintf("\x1b[1m%s\x1b[22m", strings.Replace(t, "\x1b[0m", "\x1b[0m\x1b[1m", -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// boldTransform the @input text using markdown-like syntax :
|
|
||||||
// - "normal **bold** normal"
|
|
||||||
func boldTransform(input string) (string, error) {
|
|
||||||
output := ""
|
|
||||||
cursor := int(0)
|
|
||||||
|
|
||||||
// 1. Replace for each match
|
|
||||||
for _, match := range boldRe.FindAllStringSubmatchIndex(input, -1) {
|
|
||||||
|
|
||||||
// (1) add gap between input start OR previous match
|
|
||||||
output += input[cursor:match[0]]
|
|
||||||
cursor = match[1]
|
|
||||||
|
|
||||||
// (2) extract features
|
|
||||||
text := ""
|
|
||||||
|
|
||||||
if match[3]-match[2] > 0 {
|
|
||||||
text = input[match[2]:match[3]]
|
|
||||||
}
|
|
||||||
|
|
||||||
// (3) replace text with bold text
|
|
||||||
output += boldify(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Add end of input
|
|
||||||
if cursor < len(input) {
|
|
||||||
output += input[cursor:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. print final output
|
|
||||||
return output, nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package markdown
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var hyperlinkRe = regexp.MustCompile(`(?m)\[([^\[]+)\]\(([^\)]+)\)`)
|
|
||||||
|
|
||||||
// linkify returns the terminal-formatted hyperlink for @url with the text : @label
|
|
||||||
func linkify(url, label string) string {
|
|
||||||
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", url, label)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hyperlinkTransform the @input text using markdown-like syntax :
|
|
||||||
// - "normal [link label](link url) normal"
|
|
||||||
func hyperlinkTransform(input string) (string, error) {
|
|
||||||
output := ""
|
|
||||||
cursor := int(0)
|
|
||||||
|
|
||||||
// 1. Replace for each match
|
|
||||||
for _, match := range hyperlinkRe.FindAllStringSubmatchIndex(input, -1) {
|
|
||||||
|
|
||||||
// (1) add gap between input start OR previous match
|
|
||||||
output += input[cursor:match[0]]
|
|
||||||
cursor = match[1]
|
|
||||||
|
|
||||||
// (2) extract features
|
|
||||||
var label, url string
|
|
||||||
|
|
||||||
if match[3]-match[2] > 0 {
|
|
||||||
label = input[match[2]:match[3]]
|
|
||||||
}
|
|
||||||
if match[5]-match[4] > 0 {
|
|
||||||
url = input[match[4]:match[5]]
|
|
||||||
}
|
|
||||||
|
|
||||||
// (3) replace with hyperlink
|
|
||||||
output += linkify(url, label)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Add end of input
|
|
||||||
if cursor < len(input) {
|
|
||||||
output += input[cursor:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. print final output
|
|
||||||
return output, nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package markdown
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var italicRe = regexp.MustCompile(`(?m)\*([^\*]+)\*`)
|
|
||||||
|
|
||||||
// italic returns the terminal-formatted italic text @t
|
|
||||||
func italic(t string) string {
|
|
||||||
return fmt.Sprintf("\x1b[3m%s\x1b[23m", strings.Replace(t, "\x1b[0m", "\x1b[0m\x1b[3m", -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// italicTransform the @input text using markdown-like syntax :
|
|
||||||
// - "normal *italic* normal"
|
|
||||||
func italicTransform(input string) (string, error) {
|
|
||||||
output := ""
|
|
||||||
cursor := int(0)
|
|
||||||
|
|
||||||
// 1. Replace for each match
|
|
||||||
for _, match := range italicRe.FindAllStringSubmatchIndex(input, -1) {
|
|
||||||
|
|
||||||
// (1) add gap between input start OR previous match
|
|
||||||
output += input[cursor:match[0]]
|
|
||||||
cursor = match[1]
|
|
||||||
|
|
||||||
// (2) extract features
|
|
||||||
text := ""
|
|
||||||
|
|
||||||
if match[3]-match[2] > 0 {
|
|
||||||
text = input[match[2]:match[3]]
|
|
||||||
}
|
|
||||||
|
|
||||||
// (3) replace text with bold text
|
|
||||||
output += italic(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Add end of input
|
|
||||||
if cursor < len(input) {
|
|
||||||
output += input[cursor:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. print final output
|
|
||||||
return output, nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package markdown
|
|
||||||
|
|
||||||
func Transform(input string) (string, error) {
|
|
||||||
|
|
||||||
// 1. bold
|
|
||||||
bold, err := boldTransform(input)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. italic
|
|
||||||
italic, err := italicTransform(bold)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. underline
|
|
||||||
underline, err := underlineTransform(italic)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. hyperlink
|
|
||||||
hyperlinked, err := hyperlinkTransform(underline)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return hyperlinked, nil
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package markdown
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var underlineRe = regexp.MustCompile(`(?m)_([^_]+)_`)
|
|
||||||
|
|
||||||
// underline returns the terminal-formatted underline text @t
|
|
||||||
func underline(t string) string {
|
|
||||||
return fmt.Sprintf("\x1b[4m%s\x1b[24m", strings.Replace(t, "\x1b[0m", "\x1b[0m\x1b[4m", -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// underlineTransform the @input text using markdown-like syntax :
|
|
||||||
// - "normal _underline_ normal"
|
|
||||||
func underlineTransform(input string) (string, error) {
|
|
||||||
output := ""
|
|
||||||
cursor := int(0)
|
|
||||||
|
|
||||||
// 1. Replace for each match
|
|
||||||
for _, match := range underlineRe.FindAllStringSubmatchIndex(input, -1) {
|
|
||||||
|
|
||||||
// (1) add gap between input start OR previous match
|
|
||||||
output += input[cursor:match[0]]
|
|
||||||
cursor = match[1]
|
|
||||||
|
|
||||||
// (2) extract features
|
|
||||||
text := ""
|
|
||||||
|
|
||||||
if match[3]-match[2] > 0 {
|
|
||||||
text = input[match[2]:match[3]]
|
|
||||||
}
|
|
||||||
|
|
||||||
// (3) replace text with bold text
|
|
||||||
output += underline(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Add end of input
|
|
||||||
if cursor < len(input) {
|
|
||||||
output += input[cursor:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. print final output
|
|
||||||
return output, nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package transform
|
||||||
|
|
||||||
|
// Registry is used to apply a stack of transformations
|
||||||
|
// over an input string
|
||||||
|
type Registry struct {
|
||||||
|
// cursor is the current transformer
|
||||||
|
cursor uint
|
||||||
|
// Transformers represents the transformer stack
|
||||||
|
// ; each one will be executed in ascending order
|
||||||
|
Transformers []Transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform executes each transformer of the stack in ascending order feeding
|
||||||
|
// each one with the output of its predecessor (@input for the first). Note that if one returns an error
|
||||||
|
// the process stops here and the error is directly returned.
|
||||||
|
func (r *Registry) Transform(input string) (string, error) {
|
||||||
|
in := input
|
||||||
|
|
||||||
|
// execute each transformer by order
|
||||||
|
for _, t := range r.Transformers {
|
||||||
|
|
||||||
|
// 1. execute ; dispatch error on failure
|
||||||
|
out, err := execute(t, in)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. replace next input with current output
|
||||||
|
in = out
|
||||||
|
}
|
||||||
|
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute 1 given transformer @t with its @input string and returns the output,
|
||||||
|
// and the error if one.
|
||||||
|
func execute(t Transformer, input string) (string, error) {
|
||||||
|
var (
|
||||||
|
output string
|
||||||
|
cursor int
|
||||||
|
)
|
||||||
|
|
||||||
|
// apply transformatione for each match
|
||||||
|
for _, match := range t.Regex().FindAllStringSubmatchIndex(input, -1) {
|
||||||
|
|
||||||
|
// (1) append gap between input start OR previous match
|
||||||
|
output += input[cursor:match[0]]
|
||||||
|
cursor = match[1]
|
||||||
|
|
||||||
|
// (2) build transformation arguments
|
||||||
|
args := make([]string, 0, len(match)/2+1)
|
||||||
|
for i, l := 2, len(match); i < l; i += 2 {
|
||||||
|
// match exists (not both -1, nor negative length)
|
||||||
|
if match[i+1]-match[i] > 0 {
|
||||||
|
args = append(args, input[match[i]:match[i+1]])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args = append(args, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// (3) execute transformation
|
||||||
|
transformed, err := t.Transform(args...)
|
||||||
|
if err != nil {
|
||||||
|
return "", &TransformerError{t, err, input[match[0]:match[1]]}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (4) apply transformation
|
||||||
|
output += transformed
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add end of input (if not covered by matches)
|
||||||
|
if cursor < len(input) {
|
||||||
|
output += input[cursor:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// return final output
|
||||||
|
return output, nil
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transformer interface {
|
||||||
|
// Regex returns the regex matching text to replace
|
||||||
|
Regex() *regexp.Regexp
|
||||||
|
|
||||||
|
// Transform is called to replace a match by its transformation
|
||||||
|
// ; it takes as arguments the matched string chunks from the Regex()
|
||||||
|
Transform(...string) (string, error)
|
||||||
|
}
|
Loading…
Reference in New Issue