diff options
Diffstat (limited to 'cnmfmt')
| -rw-r--r-- | cnmfmt/cnmfmt.go | 136 | ||||
| -rw-r--r-- | 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)  			} |