From 47563c9f39db7f313d9e47e4f25c219fc6238d3c Mon Sep 17 00:00:00 2001 From: clsr Date: Mon, 4 Sep 2017 10:54:12 +0200 Subject: Update CNMfmt to CNM 0.4 --- cnmfmt/cnmfmt.go | 136 +++++++++++++++++++++++------------------ cnmfmt/cnmfmt_test.go | 166 +++++++++++++++++++++++++++++--------------------- 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) } -- cgit