summaryrefslogtreecommitdiffstats
path: root/cnmfmt
diff options
context:
space:
mode:
Diffstat (limited to 'cnmfmt')
-rw-r--r--cnmfmt/cnmfmt.go136
-rw-r--r--cnmfmt/cnmfmt_test.go166
2 files changed, 174 insertions, 128 deletions
diff --git a/cnmfmt/cnmfmt.go b/cnmfmt/cnmfmt.go
index fa2ec65..1e5b6ee 100644
--- a/cnmfmt/cnmfmt.go
+++ b/cnmfmt/cnmfmt.go
@@ -9,6 +9,10 @@ import (
"contnet.org/lib/cnm-go"
)
+const (
+ numFormats = 5 // emphasized, alternate, code, quote, link
+)
+
func init() {
cnm.RegisterTextContentParser("fmt", parseTextFmt)
}
@@ -37,7 +41,7 @@ func ParseParagraph(s string) Text {
}
switch r {
- case '*', '/', '_', '`', '@':
+ case '*', '_', '`', '"', '@':
handleTag(r, &last, &t, &format, &buf, &url)
case '\\':
@@ -138,13 +142,13 @@ func handleTag(r rune, last *rune, txt *Text, format *Format, buf *bytes.Buffer,
buf.Reset()
switch r {
case '*':
- format.Bold = !format.Bold
- case '/':
- format.Italic = !format.Italic
+ format.Emphasized = !format.Emphasized
case '_':
- format.Underline = !format.Underline
+ format.Alternate = !format.Alternate
case '`':
- format.Monospace = !format.Monospace
+ format.Code = !format.Code
+ case '"':
+ format.Quote = !format.Quote
case '@':
format.Link = ""
*url = !*url
@@ -152,7 +156,7 @@ func handleTag(r rune, last *rune, txt *Text, format *Format, buf *bytes.Buffer,
*last = -1
} else {
switch *last {
- case '*', '/', '_', '`', '@':
+ case '*', '_', '`', '"', '@':
buf.WriteRune(*last)
}
*last = r
@@ -161,7 +165,7 @@ func handleTag(r rune, last *rune, txt *Text, format *Format, buf *bytes.Buffer,
// WriteIndent writes the formatted text indented by n tabs.
func (t Text) WriteIndent(w io.Writer, n int) error {
- var state [5]byte // bold, italic, underline, monospace, link
+ var state [numFormats]byte
si := 0
format := Format{}
spans := EscapeSpans(t.Spans)
@@ -171,17 +175,17 @@ func (t Text) WriteIndent(w io.Writer, n int) error {
for _, f := range order {
switch f {
case '*':
- format.Bold = !format.Bold
+ format.Emphasized = !format.Emphasized
line = append(line, "**")
- case '/':
- format.Italic = !format.Italic
- line = append(line, "//")
case '_':
- format.Underline = !format.Underline
+ format.Alternate = !format.Alternate
line = append(line, "__")
case '`':
- format.Monospace = !format.Monospace
+ format.Code = !format.Code
line = append(line, "``")
+ case '"':
+ format.Quote = !format.Quote
+ line = append(line, "\"\"")
case '@':
if format.Link != "" {
line = append(line, "@@")
@@ -208,40 +212,41 @@ func tagOrder(state []byte, old, new Format) []byte {
ldiff = "1"
}
diff := Format{
- Bold: old.Bold != new.Bold,
- Italic: old.Italic != new.Italic,
- Underline: old.Underline != new.Underline,
- Monospace: old.Monospace != new.Monospace,
- Link: ldiff,
+ Emphasized: old.Emphasized != new.Emphasized,
+ Alternate: old.Alternate != new.Alternate,
+ Code: old.Code != new.Code,
+ Quote: old.Quote != new.Quote,
+ Link: ldiff,
}
- var order [5]byte
+ // close formats
+ var order [numFormats]byte
oi := 0
for i := len(state) - 1; i >= 0; i-- {
switch state[i] {
case '*':
- if diff.Bold {
+ if diff.Emphasized {
order[oi] = '*'
oi++
- diff.Bold = false
- }
- case '/':
- if diff.Italic {
- order[oi] = '/'
- oi++
- diff.Italic = false
+ diff.Emphasized = false
}
case '_':
- if diff.Underline {
+ if diff.Alternate {
order[oi] = '_'
oi++
- diff.Underline = false
+ diff.Alternate = false
}
case '`':
- if diff.Monospace {
+ if diff.Code {
order[oi] = '`'
oi++
- diff.Monospace = false
+ diff.Code = false
+ }
+ case '"':
+ if diff.Quote {
+ order[oi] = '"'
+ oi++
+ diff.Quote = false
}
case '@':
if diff.Link != "" {
@@ -252,22 +257,23 @@ func tagOrder(state []byte, old, new Format) []byte {
}
}
- if diff.Bold {
+ // open formats
+ if diff.Emphasized {
order[oi] = '*'
oi++
}
- if diff.Italic {
- order[oi] = '/'
- oi++
- }
- if diff.Underline {
+ if diff.Alternate {
order[oi] = '_'
oi++
}
- if diff.Monospace {
+ if diff.Code {
order[oi] = '`'
oi++
}
+ if diff.Quote {
+ order[oi] = '"'
+ oi++
+ }
if diff.Link != "" {
order[oi] = '@'
oi++
@@ -277,25 +283,29 @@ func tagOrder(state []byte, old, new Format) []byte {
}
func cleanupTags(state []byte, order []byte, format Format) int {
- var newState [10]byte
- copy(newState[:5], state)
- copy(newState[5:], order)
+ var newState [2 * numFormats]byte
+
+ // take care of both state and order
+ copy(newState[:numFormats], state)
+ copy(newState[numFormats:], order)
+
+ // remove formats that are off
for i := range newState {
switch newState[i] {
case '*':
- if !format.Bold {
- newState[i] = 0
- }
- case '/':
- if !format.Italic {
+ if !format.Emphasized {
newState[i] = 0
}
case '_':
- if !format.Underline {
+ if !format.Alternate {
newState[i] = 0
}
case '`':
- if !format.Monospace {
+ if !format.Code {
+ newState[i] = 0
+ }
+ case '"':
+ if !format.Quote {
newState[i] = 0
}
case '@':
@@ -304,6 +314,7 @@ func cleanupTags(state []byte, order []byte, format Format) int {
}
}
}
+
si := 0
for _, f := range newState {
if f > 0 {
@@ -311,6 +322,12 @@ func cleanupTags(state []byte, order []byte, format Format) int {
si++
}
}
+
+ // clean up unused state
+ for i := si; i < numFormats; i++ {
+ state[i] = 0
+ }
+
return si
}
@@ -325,17 +342,17 @@ type Span struct {
// Format represents a state of CNMfmt formatting.
type Format struct {
- // Bold text.
- Bold bool
+ // Emphasized text.
+ Emphasized bool
- // Italic text.
- Italic bool
+ // Text in an alternate voice.
+ Alternate bool
- // Underlined text.
- Underline bool
+ // Text is code.
+ Code bool
- // Monospaced text.
- Monospace bool
+ // Quoted text.
+ Quote bool
// Hyperlink URL (if non-empty).
Link string
@@ -385,9 +402,9 @@ func EscapeSpans(spans []Span) []Span {
var escapeReplacer = strings.NewReplacer(
`*`, `\*`,
- `/`, `\/`,
`_`, `\_`,
"`", "\\`",
+ `"`, `\"`,
`@`, `\@`,
)
@@ -402,11 +419,10 @@ func Unescape(s string) string {
}
var unescapeReplacer = strings.NewReplacer(
- `\\`, `\\`,
`\*`, `*`,
- `\/`, `/`,
`\_`, `_`,
"\\`", "`",
+ `\"`, `"`,
`\@`, `@`,
)
diff --git a/cnmfmt/cnmfmt_test.go b/cnmfmt/cnmfmt_test.go
index 89a40a9..5d50b18 100644
--- a/cnmfmt/cnmfmt_test.go
+++ b/cnmfmt/cnmfmt_test.go
@@ -14,16 +14,16 @@ var parseTests = map[string]Text{
Span{Format{}, "\nfoo bar\\"},
}},
"**foo": Text{[]Span{
- Span{Format{Bold: true}, "foo"},
- }},
- "//foo": Text{[]Span{
- Span{Format{Italic: true}, "foo"},
+ Span{Format{Emphasized: true}, "foo"},
}},
"__foo": Text{[]Span{
- Span{Format{Underline: true}, "foo"},
+ Span{Format{Alternate: true}, "foo"},
}},
"``foo": Text{[]Span{
- Span{Format{Monospace: true}, "foo"},
+ Span{Format{Code: true}, "foo"},
+ }},
+ "\"\"foo": Text{[]Span{
+ Span{Format{Quote: true}, "foo"},
}},
"foo*bar": Text{[]Span{
Span{Format{}, "foo*bar"},
@@ -36,7 +36,7 @@ var parseTests = map[string]Text{
}},
"foo***": Text{[]Span{
Span{Format{}, "foo"},
- Span{Format{Bold: true}, "*"},
+ Span{Format{Emphasized: true}, "*"},
}},
"foo****": Text{[]Span{
Span{Format{}, "foo"},
@@ -48,35 +48,35 @@ var parseTests = map[string]Text{
Span{Format{}, "foo"},
}},
"******foo": Text{[]Span{
- Span{Format{Bold: true}, "foo"},
+ Span{Format{Emphasized: true}, "foo"},
}},
"foo ** bar": Text{[]Span{
Span{Format{}, "foo "},
- Span{Format{Bold: true}, " bar"},
+ Span{Format{Emphasized: true}, " bar"},
}},
"foo** bar": Text{[]Span{
Span{Format{}, "foo"},
- Span{Format{Bold: true}, " bar"},
+ Span{Format{Emphasized: true}, " bar"},
}},
"foo **bar": Text{[]Span{
Span{Format{}, "foo "},
- Span{Format{Bold: true}, "bar"},
+ Span{Format{Emphasized: true}, "bar"},
}},
"foo ** bar ** baz": Text{[]Span{
Span{Format{}, "foo "},
- Span{Format{Bold: true}, " bar "},
+ Span{Format{Emphasized: true}, " bar "},
Span{Format{}, " baz"},
}},
"foo ** bar** baz": Text{[]Span{
Span{Format{}, "foo "},
- Span{Format{Bold: true}, " bar"},
+ Span{Format{Emphasized: true}, " bar"},
Span{Format{}, " baz"},
}},
- "**__**foo": Text{[]Span{
- Span{Format{Underline: true}, "foo"},
+ "**\"\"**foo": Text{[]Span{
+ Span{Format{Quote: true}, "foo"},
}},
"***": Text{[]Span{
- Span{Format{Bold: true}, "*"},
+ Span{Format{Emphasized: true}, "*"},
}},
"*\\**": Text{[]Span{
Span{Format{}, "***"},
@@ -99,18 +99,18 @@ var parseTests = map[string]Text{
"\\\\": Text{[]Span{
Span{Format{}, "\\"},
}},
- " ** // `` ": Text{[]Span{
- Span{Format{Bold: true}, " "},
- Span{Format{Bold: true, Italic: true}, " "},
+ " ** __ `` ": Text{[]Span{
+ Span{Format{Emphasized: true}, " "},
+ Span{Format{Emphasized: true, Alternate: true}, " "},
}},
"**": Text{[]Span{}},
- "**``__//foo": Text{[]Span{
- Span{Format{Bold: true, Monospace: true, Underline: true, Italic: true}, "foo"},
+ "**``\"\"__foo": Text{[]Span{
+ Span{Format{Emphasized: true, Code: true, Quote: true, Alternate: true}, "foo"},
}},
- "**foo//bar**baz": Text{[]Span{
- Span{Format{Bold: true}, "foo"},
- Span{Format{Bold: true, Italic: true}, "bar"},
- Span{Format{Italic: true}, "baz"},
+ "**foo__bar**baz": Text{[]Span{
+ Span{Format{Emphasized: true}, "foo"},
+ Span{Format{Emphasized: true, Alternate: true}, "bar"},
+ Span{Format{Alternate: true}, "baz"},
}},
"@@foo": Text{[]Span{
Span{Format{Link: "foo"}, ""},
@@ -147,34 +147,34 @@ var parseTests = map[string]Text{
}},
"@@http://example.com foo **bar @@baz**": Text{[]Span{
Span{Format{Link: "http://example.com"}, "foo "},
- Span{Format{Bold: true, Link: "http://example.com"}, "bar "},
- Span{Format{Bold: true}, "baz"},
+ Span{Format{Emphasized: true, Link: "http://example.com"}, "bar "},
+ Span{Format{Emphasized: true}, "baz"},
}},
- "//@@http://example.com foo //bar @@": Text{[]Span{
- Span{Format{Italic: true, Link: "http://example.com"}, "foo "},
- Span{Format{Link: "http://example.com"}, "bar "},
+ "__@@http://example.com/__/ foo __bar @@": Text{[]Span{
+ Span{Format{Alternate: true, Link: "http://example.com/__/"}, "foo "},
+ Span{Format{Link: "http://example.com/__/"}, "bar "},
}},
- "__\\ asd \\ zxc\\ ": Text{[]Span{
- Span{Format{Underline: true, Monospace: false}, " asd zxc "},
+ "\"\"\\ asd \\ zxc\\ ": Text{[]Span{
+ Span{Format{Quote: true, Code: false}, " asd zxc "},
}},
- "@@/ test/@@": Text{[]Span{
- Span{Format{Link: "/"}, "test/"},
+ "@@_ test_@@": Text{[]Span{
+ Span{Format{Link: "_"}, "test_"},
}},
- "@@/ /test@@": Text{[]Span{
- Span{Format{Link: "/"}, "/test"},
+ "@@_ _test@@": Text{[]Span{
+ Span{Format{Link: "_"}, "_test"},
}},
- "/": Text{[]Span{
- Span{Format{}, "/"},
+ "_": Text{[]Span{
+ Span{Format{}, "_"},
}},
- "test/**": Text{[]Span{
- Span{Format{}, "test/"},
+ "test_**": Text{[]Span{
+ Span{Format{}, "test_"},
}},
- "//test/": Text{[]Span{
- Span{Format{Italic: true}, "test/"},
+ "__test_": Text{[]Span{
+ Span{Format{Alternate: true}, "test_"},
}},
- "/**test": Text{[]Span{
- Span{Format{}, "/"},
- Span{Format{Bold: true}, "test"},
+ "_**test": Text{[]Span{
+ Span{Format{}, "_"},
+ Span{Format{Emphasized: true}, "test"},
}},
}
@@ -213,16 +213,16 @@ func textEqual(a, b Text) bool {
}
var escapeTests = map[string]string{
- "\n\r\t\v\x00": "\\n\\r\\t\v\\x00",
- "@@!!##__//__``**": "\\@\\@!!##\\_\\_\\/\\/\\_\\_\\`\\`\\*\\*",
- `foo\@\@bar`: `foo\\\@\\\@bar`,
+ "\n\r\t\v\x00": "\\n\\r\\t\v\\x00",
+ "@@!!##\"\"//\"\"__``**%%^^&&++==\x01\x01\\": "\\@\\@!!##\\\"\\\"//\\\"\\\"\\_\\_\\`\\`\\*\\*%%^^&&++==\x01\x01\\\\",
+ `foo\@\@bar`: `foo\\\@\\\@bar`,
}
func TestEscape(t *testing.T) {
for k, v := range escapeTests {
t.Run(k, func(t *testing.T) {
if e := Escape(k); e != v {
- t.Errorf("Escape(%q): expected %q, got %q", k, v, e)
+ t.Errorf("Escape(%q):\nexpected: %q\n got: %q", k, v, e)
}
})
}
@@ -232,7 +232,7 @@ var parseTextTests = map[string]TextFmtContents{
"foo ** bar\nbaz\n\n\nquux ** ": TextFmtContents{[]Text{
Text{[]Span{
Span{Format{}, "foo "},
- Span{Format{Bold: true}, " bar baz"},
+ Span{Format{Emphasized: true}, " bar baz"},
}},
Text{[]Span{
Span{Format{}, "quux "},
@@ -261,9 +261,14 @@ var parseTextTests = map[string]TextFmtContents{
`foo**bar\*\*baz\*\*quux**qweasd`: TextFmtContents{[]Text{Text{[]Span{
Span{Format{}, "foo"},
- Span{Format{Bold: true}, "bar**baz**quux"},
+ Span{Format{Emphasized: true}, "bar**baz**quux"},
Span{Format{}, "qweasd"},
}}}},
+
+ "**foo\n\nbar": TextFmtContents{[]Text{
+ Text{[]Span{Span{Format{Emphasized: true}, "foo"}}},
+ Text{[]Span{Span{Format{}, "bar"}}},
+ }},
}
func TestParseTextFmt(t *testing.T) {
@@ -316,14 +321,14 @@ var writeTests = map[string]TextFmtContents{
"**foo\n": TextFmtContents{[]Text{
Text{[]Span{
- Span{Format{Bold: true}, "foo"},
+ Span{Format{Emphasized: true}, "foo"},
}},
}},
"foo **bar baz\n\nquux\n": TextFmtContents{[]Text{
Text{[]Span{
Span{Format{}, "foo "},
- Span{Format{Bold: true}, "bar baz"},
+ Span{Format{Emphasized: true}, "bar baz"},
}},
Text{[]Span{
Span{Format{}, "quux"},
@@ -331,35 +336,62 @@ var writeTests = map[string]TextFmtContents{
}},
"foo**bar``baz**quux\n\n" +
- "\\ __qwe\\ __//\\ asd \\ //``zxc``**\\ \n\n" +
- "//@@http://example.com exa//mple@@ @@href text@@// test\n": TextFmtContents{[]Text{
+ "\\ \"\"qwe\\ \"\"__\\ asd \\ __``zxc``**\\ \n\n" +
+ "__@@http://example.com/__/ exa__mple@@ @@href text@@__ test\n": TextFmtContents{[]Text{
Text{[]Span{
Span{Format{}, "foo"},
- Span{Format{Bold: true}, "bar"},
- Span{Format{Bold: true, Monospace: true}, "baz"},
- Span{Format{Monospace: true}, "quux"},
+ Span{Format{Emphasized: true}, "bar"},
+ Span{Format{Emphasized: true, Code: true}, "baz"},
+ Span{Format{Code: true}, "quux"},
}},
Text{[]Span{
Span{Format{}, " "},
- Span{Format{Underline: true}, "qwe "},
- Span{Format{Italic: true}, " asd "},
- Span{Format{Monospace: true}, "zxc"},
- Span{Format{Bold: true}, " "},
+ Span{Format{Quote: true}, "qwe "},
+ Span{Format{Alternate: true}, " asd "},
+ Span{Format{Code: true}, "zxc"},
+ Span{Format{Emphasized: true}, " "},
}},
Text{[]Span{
- Span{Format{Italic: true, Link: "http://example.com"}, "exa"},
- Span{Format{Link: "http://example.com"}, "mple"},
+ Span{Format{Alternate: true, Link: "http://example.com/__/"}, "exa"},
+ Span{Format{Link: "http://example.com/__/"}, "mple"},
Span{Format{}, " "},
Span{Format{Link: "href"}, "text"},
- Span{Format{Italic: true}, " test"},
+ Span{Format{Alternate: true}, " test"},
}},
}},
"foo**bar\\*\\*baz\\*\\*quux**qweasd\n": TextFmtContents{[]Text{Text{[]Span{
Span{Format{}, "foo"},
- Span{Format{Bold: true}, "bar**baz**quux"},
+ Span{Format{Emphasized: true}, "bar**baz**quux"},
Span{Format{}, "qweasd"},
}}}},
+
+ "**1** **2** **3** **4** **5** **6** **7** **8**" +
+ " **9** **10** **11** **12\n": TextFmtContents{[]Text{Text{[]Span{
+ Span{Format{Emphasized: true}, "1"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "2"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "3"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "4"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "5"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "6"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "7"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "8"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "9"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "10"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "11"},
+ Span{Format{}, " "},
+ Span{Format{Emphasized: true}, "12"},
+ }}}},
}
func TestWriteTextFmt(t *testing.T) {
@@ -371,8 +403,6 @@ func TestWriteTextFmt(t *testing.T) {
t.Fatalf("WriteIndent error: %v", err)
}
w := buf.String()
- t.Log("expected:\n" + k)
- t.Log(" got:\n" + w)
if k != w {
t.Fatalf("WriteIndent: output did not match expected document:\nexpected: %q\n got: %q", k, w)
}