Update to go-org 1.3.2 (#12728)
* Update to go-org 1.3.2 Fix #12727 Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix unit test Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		
							parent
							
								
									e80eda7d01
								
							
						
					
					
						commit
						9fdb4f887b
					
				
							
								
								
									
										5
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										5
									
								
								go.mod
								
								
								
								
							|  | @ -27,6 +27,7 @@ require ( | ||||||
| 	github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect | 	github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect | ||||||
| 	github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc | 	github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
|  | 	github.com/dlclark/regexp2 v1.2.1 // indirect | ||||||
| 	github.com/dustin/go-humanize v1.0.0 | 	github.com/dustin/go-humanize v1.0.0 | ||||||
| 	github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | 	github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | ||||||
| 	github.com/emirpasic/gods v1.12.0 | 	github.com/emirpasic/gods v1.12.0 | ||||||
|  | @ -73,7 +74,7 @@ require ( | ||||||
| 	github.com/mitchellh/go-homedir v1.1.0 | 	github.com/mitchellh/go-homedir v1.1.0 | ||||||
| 	github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc | 	github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc | ||||||
| 	github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | 	github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | ||||||
| 	github.com/niklasfasching/go-org v0.1.9 | 	github.com/niklasfasching/go-org v1.3.2 | ||||||
| 	github.com/oliamb/cutter v0.2.2 | 	github.com/oliamb/cutter v0.2.2 | ||||||
| 	github.com/olivere/elastic/v7 v7.0.9 | 	github.com/olivere/elastic/v7 v7.0.9 | ||||||
| 	github.com/pkg/errors v0.9.1 | 	github.com/pkg/errors v0.9.1 | ||||||
|  | @ -99,7 +100,7 @@ require ( | ||||||
| 	github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 | 	github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 | ||||||
| 	github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | 	github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | ||||||
| 	golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a | 	golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a | ||||||
| 	golang.org/x/net v0.0.0-20200707034311-ab3426394381 | 	golang.org/x/net v0.0.0-20200904194848-62affa334b73 | ||||||
| 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | ||||||
| 	golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae | 	golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae | ||||||
| 	golang.org/x/text v0.3.3 | 	golang.org/x/text v0.3.3 | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										8
									
								
								go.sum
								
								
								
								
							|  | @ -194,6 +194,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 | ||||||
| github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | ||||||
| github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= | github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= | ||||||
| github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | ||||||
|  | github.com/dlclark/regexp2 v1.2.1 h1:Ff/S0snjr1oZHUNOkvA/gP6KUaMg5vDDl3Qnhjnwgm8= | ||||||
|  | github.com/dlclark/regexp2 v1.2.1/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | ||||||
| github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | ||||||
| github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | ||||||
| github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= | ||||||
|  | @ -705,8 +707,8 @@ github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/R | ||||||
| github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= | github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= | ||||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= | ||||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||||
| github.com/niklasfasching/go-org v0.1.9 h1:Toz8WMIt+qJb52uYEk1YD/muLuOOmRt1CfkV+bKVMkI= | github.com/niklasfasching/go-org v1.3.2 h1:ZKTSd+GdJYkoZl1pBXLR/k7DRiRXnmB96TRiHmHdzwI= | ||||||
| github.com/niklasfasching/go-org v0.1.9/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU= | github.com/niklasfasching/go-org v1.3.2/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU= | ||||||
| github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= | github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= | ||||||
| github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= | github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= | ||||||
| github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= | github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= | ||||||
|  | @ -1010,6 +1012,8 @@ golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/ | ||||||
| golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||||
| golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= | golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= | ||||||
| golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||||
|  | golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= | ||||||
|  | golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||||
| golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
| golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||||
|  |  | ||||||
|  | @ -27,12 +27,12 @@ func TestRender_StandardLinks(t *testing.T) { | ||||||
| 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) | 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	googleRendered := "<p>\n<a href=\"https://google.com/\" title=\"https://google.com/\">https://google.com/</a>\n</p>" | 	googleRendered := "<p><a href=\"https://google.com/\" title=\"https://google.com/\">https://google.com/</a></p>" | ||||||
| 	test("[[https://google.com/]]", googleRendered) | 	test("[[https://google.com/]]", googleRendered) | ||||||
| 
 | 
 | ||||||
| 	lnk := util.URLJoin(AppSubURL, "WikiPage") | 	lnk := util.URLJoin(AppSubURL, "WikiPage") | ||||||
| 	test("[[WikiPage][WikiPage]]", | 	test("[[WikiPage][WikiPage]]", | ||||||
| 		"<p>\n<a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a>\n</p>") | 		"<p><a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a></p>") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRender_Images(t *testing.T) { | func TestRender_Images(t *testing.T) { | ||||||
|  | @ -48,5 +48,5 @@ func TestRender_Images(t *testing.T) { | ||||||
| 	result := util.URLJoin(AppSubURL, url) | 	result := util.URLJoin(AppSubURL, url) | ||||||
| 
 | 
 | ||||||
| 	test("[[file:"+url+"]]", | 	test("[[file:"+url+"]]", | ||||||
| 		"<p>\n<img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" />\n</p>") | 		"<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| language: go | language: go | ||||||
| 
 | 
 | ||||||
| go: | go: | ||||||
|   - 1.5 |   - 1.9 | ||||||
|   - tip |   - tip | ||||||
|  | @ -235,17 +235,14 @@ func (re *Regexp) getRunesAndStart(s string, startAt int) ([]rune, int) { | ||||||
| 		ret[i] = r | 		ret[i] = r | ||||||
| 		i++ | 		i++ | ||||||
| 	} | 	} | ||||||
|  | 	if startAt == len(s) { | ||||||
|  | 		runeIdx = i | ||||||
|  | 	} | ||||||
| 	return ret[:i], runeIdx | 	return ret[:i], runeIdx | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getRunes(s string) []rune { | func getRunes(s string) []rune { | ||||||
| 	ret := make([]rune, len(s)) | 	return []rune(s) | ||||||
| 	i := 0 |  | ||||||
| 	for _, r := range s { |  | ||||||
| 		ret[i] = r |  | ||||||
| 		i++ |  | ||||||
| 	} |  | ||||||
| 	return ret[:i] |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MatchRunes return true if the runes matches the regex
 | // MatchRunes return true if the runes matches the regex
 | ||||||
|  |  | ||||||
|  | @ -1250,10 +1250,10 @@ func (p *parser) scanBasicBackslash(scanOnly bool) (*regexNode, error) { | ||||||
| 			return nil, nil | 			return nil, nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if p.useOptionE() || p.isCaptureSlot(capnum) { | 		if p.isCaptureSlot(capnum) { | ||||||
| 			return newRegexNodeM(ntRef, p.options, capnum), nil | 			return newRegexNodeM(ntRef, p.options, capnum), nil | ||||||
| 		} | 		} | ||||||
| 		if capnum <= 9 { | 		if capnum <= 9 && !p.useOptionE() { | ||||||
| 			return nil, p.getErr(ErrUndefinedBackRef, capnum) | 			return nil, p.getErr(ErrUndefinedBackRef, capnum) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -1808,11 +1808,11 @@ func (p *parser) scanOctal() rune { | ||||||
| 	i := 0 | 	i := 0 | ||||||
| 	d := int(p.rightChar(0) - '0') | 	d := int(p.rightChar(0) - '0') | ||||||
| 	for c > 0 && d <= 7 { | 	for c > 0 && d <= 7 { | ||||||
| 		i *= 8 | 		if i >= 0x20 && p.useOptionE() { | ||||||
| 		i += d |  | ||||||
| 		if p.useOptionE() && i >= 0x20 { |  | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  | 		i *= 8 | ||||||
|  | 		i += d | ||||||
| 		c-- | 		c-- | ||||||
| 
 | 
 | ||||||
| 		p.moveRight(1) | 		p.moveRight(1) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,11 @@ type Block struct { | ||||||
| 	Name       string | 	Name       string | ||||||
| 	Parameters []string | 	Parameters []string | ||||||
| 	Children   []Node | 	Children   []Node | ||||||
|  | 	Result     Node | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Result struct { | ||||||
|  | 	Node Node | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Example struct { | type Example struct { | ||||||
|  | @ -19,6 +24,8 @@ type Example struct { | ||||||
| var exampleLineRegexp = regexp.MustCompile(`^(\s*):(\s(.*)|\s*$)`) | var exampleLineRegexp = regexp.MustCompile(`^(\s*):(\s(.*)|\s*$)`) | ||||||
| var beginBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+BEGIN_(\w+)(.*)`) | var beginBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+BEGIN_(\w+)(.*)`) | ||||||
| var endBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+END_(\w+)`) | var endBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+END_(\w+)`) | ||||||
|  | var resultRegexp = regexp.MustCompile(`(?i)^(\s*)#\+RESULTS:`) | ||||||
|  | var exampleBlockEscapeRegexp = regexp.MustCompile(`(^|\n)([ \t]*),([ \t]*)(\*|,\*|#\+|,#\+)`) | ||||||
| 
 | 
 | ||||||
| func lexBlock(line string) (token, bool) { | func lexBlock(line string) (token, bool) { | ||||||
| 	if m := beginBlockRegexp.FindStringSubmatch(line); m != nil { | 	if m := beginBlockRegexp.FindStringSubmatch(line); m != nil { | ||||||
|  | @ -29,6 +36,13 @@ func lexBlock(line string) (token, bool) { | ||||||
| 	return nilToken, false | 	return nilToken, false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func lexResult(line string) (token, bool) { | ||||||
|  | 	if m := resultRegexp.FindStringSubmatch(line); m != nil { | ||||||
|  | 		return token{"result", len(m[1]), "", m}, true | ||||||
|  | 	} | ||||||
|  | 	return nilToken, false | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func lexExample(line string) (token, bool) { | func lexExample(line string) (token, bool) { | ||||||
| 	if m := exampleLineRegexp.FindStringSubmatch(line); m != nil { | 	if m := exampleLineRegexp.FindStringSubmatch(line); m != nil { | ||||||
| 		return token{"example", len(m[1]), m[3], m}, true | 		return token{"example", len(m[1]), m[3], m}, true | ||||||
|  | @ -45,22 +59,41 @@ func (d *Document) parseBlock(i int, parentStop stopFn) (int, Node) { | ||||||
| 	stop := func(d *Document, i int) bool { | 	stop := func(d *Document, i int) bool { | ||||||
| 		return i >= len(d.tokens) || (d.tokens[i].kind == "endBlock" && d.tokens[i].content == name) | 		return i >= len(d.tokens) || (d.tokens[i].kind == "endBlock" && d.tokens[i].content == name) | ||||||
| 	} | 	} | ||||||
| 	block, i := Block{name, parameters, nil}, i+1 | 	block, i := Block{name, parameters, nil, nil}, i+1 | ||||||
| 	if isRawTextBlock(name) { | 	if isRawTextBlock(name) { | ||||||
| 		rawText := "" | 		rawText := "" | ||||||
| 		for ; !stop(d, i); i++ { | 		for ; !stop(d, i); i++ { | ||||||
| 			rawText += trim(d.tokens[i].matches[0]) + "\n" | 			rawText += trim(d.tokens[i].matches[0]) + "\n" | ||||||
| 		} | 		} | ||||||
|  | 		if name == "EXAMPLE" || (name == "SRC" && len(parameters) >= 1 && parameters[0] == "org") { | ||||||
|  | 			rawText = exampleBlockEscapeRegexp.ReplaceAllString(rawText, "$1$2$3$4") | ||||||
|  | 		} | ||||||
| 		block.Children = d.parseRawInline(rawText) | 		block.Children = d.parseRawInline(rawText) | ||||||
| 	} else { | 	} else { | ||||||
| 		consumed, nodes := d.parseMany(i, stop) | 		consumed, nodes := d.parseMany(i, stop) | ||||||
| 		block.Children = nodes | 		block.Children = nodes | ||||||
| 		i += consumed | 		i += consumed | ||||||
| 	} | 	} | ||||||
| 	if i < len(d.tokens) && d.tokens[i].kind == "endBlock" && d.tokens[i].content == name { | 	if i >= len(d.tokens) || d.tokens[i].kind != "endBlock" || d.tokens[i].content != name { | ||||||
| 		return i + 1 - start, block |  | ||||||
| 	} |  | ||||||
| 		return 0, nil | 		return 0, nil | ||||||
|  | 	} | ||||||
|  | 	if name == "SRC" { | ||||||
|  | 		consumed, result := d.parseSrcBlockResult(i+1, parentStop) | ||||||
|  | 		block.Result = result | ||||||
|  | 		i += consumed | ||||||
|  | 	} | ||||||
|  | 	return i + 1 - start, block | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (d *Document) parseSrcBlockResult(i int, parentStop stopFn) (int, Node) { | ||||||
|  | 	start := i | ||||||
|  | 	for ; !parentStop(d, i) && d.tokens[i].kind == "text" && d.tokens[i].content == ""; i++ { | ||||||
|  | 	} | ||||||
|  | 	if parentStop(d, i) || d.tokens[i].kind != "result" { | ||||||
|  | 		return 0, nil | ||||||
|  | 	} | ||||||
|  | 	consumed, result := d.parseResult(i, parentStop) | ||||||
|  | 	return (i - start) + consumed, result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseExample(i int, parentStop stopFn) (int, Node) { | func (d *Document) parseExample(i int, parentStop stopFn) (int, Node) { | ||||||
|  | @ -71,6 +104,14 @@ func (d *Document) parseExample(i int, parentStop stopFn) (int, Node) { | ||||||
| 	return i - start, example | 	return i - start, example | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (d *Document) parseResult(i int, parentStop stopFn) (int, Node) { | ||||||
|  | 	if i+1 >= len(d.tokens) { | ||||||
|  | 		return 0, nil | ||||||
|  | 	} | ||||||
|  | 	consumed, node := d.parseOne(i+1, parentStop) | ||||||
|  | 	return consumed + 1, Result{node} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func trimIndentUpTo(max int) func(string) string { | func trimIndentUpTo(max int) func(string) string { | ||||||
| 	return func(line string) string { | 	return func(line string) string { | ||||||
| 		i := 0 | 		i := 0 | ||||||
|  | @ -80,5 +121,17 @@ func trimIndentUpTo(max int) func(string) string { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (b Block) ParameterMap() map[string]string { | ||||||
|  | 	if len(b.Parameters) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	m := map[string]string{":lang": b.Parameters[0]} | ||||||
|  | 	for i := 1; i+1 < len(b.Parameters); i += 2 { | ||||||
|  | 		m[b.Parameters[i]] = b.Parameters[i+1] | ||||||
|  | 	} | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (n Example) String() string { return orgWriter.WriteNodesAsString(n) } | func (n Example) String() string { return orgWriter.WriteNodesAsString(n) } | ||||||
| func (n Block) String() string   { return orgWriter.WriteNodesAsString(n) } | func (n Block) String() string   { return orgWriter.WriteNodesAsString(n) } | ||||||
|  | func (n Result) String() string  { return orgWriter.WriteNodesAsString(n) } | ||||||
|  |  | ||||||
|  | @ -36,6 +36,8 @@ type Document struct { | ||||||
| 	Path           string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE).
 | 	Path           string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE).
 | ||||||
| 	tokens         []token | 	tokens         []token | ||||||
| 	baseLvl        int | 	baseLvl        int | ||||||
|  | 	Macros         map[string]string | ||||||
|  | 	Links          map[string]string | ||||||
| 	Nodes          []Node | 	Nodes          []Node | ||||||
| 	NamedNodes     map[string]Node | 	NamedNodes     map[string]Node | ||||||
| 	Outline        Outline           // Outline is a Table Of Contents for the document and contains all sections (headline + content).
 | 	Outline        Outline           // Outline is a Table Of Contents for the document and contains all sections (headline + content).
 | ||||||
|  | @ -63,6 +65,7 @@ var lexFns = []lexFn{ | ||||||
| 	lexHeadline, | 	lexHeadline, | ||||||
| 	lexDrawer, | 	lexDrawer, | ||||||
| 	lexBlock, | 	lexBlock, | ||||||
|  | 	lexResult, | ||||||
| 	lexList, | 	lexList, | ||||||
| 	lexTable, | 	lexTable, | ||||||
| 	lexHorizontalRule, | 	lexHorizontalRule, | ||||||
|  | @ -83,7 +86,7 @@ func New() *Configuration { | ||||||
| 		DefaultSettings: map[string]string{ | 		DefaultSettings: map[string]string{ | ||||||
| 			"TODO":         "TODO | DONE", | 			"TODO":         "TODO | DONE", | ||||||
| 			"EXCLUDE_TAGS": "noexport", | 			"EXCLUDE_TAGS": "noexport", | ||||||
| 			"OPTIONS":      "toc:t <:t e:t f:t pri:t todo:t tags:t", | 			"OPTIONS":      "toc:t <:t e:t f:t pri:t todo:t tags:t title:t", | ||||||
| 		}, | 		}, | ||||||
| 		Log:      log.New(os.Stderr, "go-org: ", 0), | 		Log:      log.New(os.Stderr, "go-org: ", 0), | ||||||
| 		ReadFile: ioutil.ReadFile, | 		ReadFile: ioutil.ReadFile, | ||||||
|  | @ -120,6 +123,8 @@ func (c *Configuration) Parse(input io.Reader, path string) (d *Document) { | ||||||
| 		Outline:        Outline{outlineSection, outlineSection, 0}, | 		Outline:        Outline{outlineSection, outlineSection, 0}, | ||||||
| 		BufferSettings: map[string]string{}, | 		BufferSettings: map[string]string{}, | ||||||
| 		NamedNodes:     map[string]Node{}, | 		NamedNodes:     map[string]Node{}, | ||||||
|  | 		Links:          map[string]string{}, | ||||||
|  | 		Macros:         map[string]string{}, | ||||||
| 		Path:           path, | 		Path:           path, | ||||||
| 	} | 	} | ||||||
| 	defer func() { | 	defer func() { | ||||||
|  | @ -169,12 +174,13 @@ func (d *Document) Get(key string) string { | ||||||
| // - < (export timestamps)
 | // - < (export timestamps)
 | ||||||
| // - e (export org entities)
 | // - e (export org entities)
 | ||||||
| // - f (export footnotes)
 | // - f (export footnotes)
 | ||||||
| // - toc (export table of content)
 | // - title (export title)
 | ||||||
|  | // - toc (export table of content. an int limits the included org headline lvl)
 | ||||||
| // - todo (export headline todo status)
 | // - todo (export headline todo status)
 | ||||||
| // - pri (export headline priority)
 | // - pri (export headline priority)
 | ||||||
| // - tags (export headline tags)
 | // - tags (export headline tags)
 | ||||||
| // see https://orgmode.org/manual/Export-settings.html for more information
 | // see https://orgmode.org/manual/Export-settings.html for more information
 | ||||||
| func (d *Document) GetOption(key string) bool { | func (d *Document) GetOption(key string) string { | ||||||
| 	get := func(settings map[string]string) string { | 	get := func(settings map[string]string) string { | ||||||
| 		for _, field := range strings.Fields(settings["OPTIONS"]) { | 		for _, field := range strings.Fields(settings["OPTIONS"]) { | ||||||
| 			if strings.HasPrefix(field, key+":") { | 			if strings.HasPrefix(field, key+":") { | ||||||
|  | @ -187,15 +193,11 @@ func (d *Document) GetOption(key string) bool { | ||||||
| 	if value == "" { | 	if value == "" { | ||||||
| 		value = get(d.DefaultSettings) | 		value = get(d.DefaultSettings) | ||||||
| 	} | 	} | ||||||
| 	switch value { | 	if value == "" { | ||||||
| 	case "t": | 		value = "nil" | ||||||
| 		return true | 		d.Log.Printf("Missing value for export option %s", key) | ||||||
| 	case "nil": |  | ||||||
| 		return false |  | ||||||
| 	default: |  | ||||||
| 		d.Log.Printf("Bad value for export option %s (%s)", key, value) |  | ||||||
| 		return false |  | ||||||
| 	} | 	} | ||||||
|  | 	return value | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseOne(i int, stop stopFn) (consumed int, node Node) { | func (d *Document) parseOne(i int, stop stopFn) (consumed int, node Node) { | ||||||
|  | @ -206,6 +208,8 @@ func (d *Document) parseOne(i int, stop stopFn) (consumed int, node Node) { | ||||||
| 		consumed, node = d.parseTable(i, stop) | 		consumed, node = d.parseTable(i, stop) | ||||||
| 	case "beginBlock": | 	case "beginBlock": | ||||||
| 		consumed, node = d.parseBlock(i, stop) | 		consumed, node = d.parseBlock(i, stop) | ||||||
|  | 	case "result": | ||||||
|  | 		consumed, node = d.parseResult(i, stop) | ||||||
| 	case "beginDrawer": | 	case "beginDrawer": | ||||||
| 		consumed, node = d.parseDrawer(i, stop) | 		consumed, node = d.parseDrawer(i, stop) | ||||||
| 	case "text": | 	case "text": | ||||||
|  |  | ||||||
|  | @ -35,14 +35,14 @@ var tagRegexp = regexp.MustCompile(`(.*?)\s+(:[A-Za-z0-9_@#%:]+:\s*$)`) | ||||||
| 
 | 
 | ||||||
| func lexHeadline(line string) (token, bool) { | func lexHeadline(line string) (token, bool) { | ||||||
| 	if m := headlineRegexp.FindStringSubmatch(line); m != nil { | 	if m := headlineRegexp.FindStringSubmatch(line); m != nil { | ||||||
| 		return token{"headline", len(m[1]), m[2], m}, true | 		return token{"headline", 0, m[2], m}, true | ||||||
| 	} | 	} | ||||||
| 	return nilToken, false | 	return nilToken, false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) { | func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) { | ||||||
| 	t, headline := d.tokens[i], Headline{} | 	t, headline := d.tokens[i], Headline{} | ||||||
| 	headline.Lvl = t.lvl | 	headline.Lvl = len(t.matches[1]) | ||||||
| 
 | 
 | ||||||
| 	headline.Index = d.addHeadline(&headline) | 	headline.Index = d.addHeadline(&headline) | ||||||
| 
 | 
 | ||||||
|  | @ -69,7 +69,7 @@ func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) { | ||||||
| 	headline.Title = d.parseInline(text) | 	headline.Title = d.parseInline(text) | ||||||
| 
 | 
 | ||||||
| 	stop := func(d *Document, i int) bool { | 	stop := func(d *Document, i int) bool { | ||||||
| 		return parentStop(d, i) || d.tokens[i].kind == "headline" && d.tokens[i].lvl <= headline.Lvl | 		return parentStop(d, i) || d.tokens[i].kind == "headline" && len(d.tokens[i].matches[1]) <= headline.Lvl | ||||||
| 	} | 	} | ||||||
| 	consumed, nodes := d.parseMany(i+1, stop) | 	consumed, nodes := d.parseMany(i+1, stop) | ||||||
| 	if len(nodes) > 0 { | 	if len(nodes) > 0 { | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import ( | ||||||
| 	"html" | 	"html" | ||||||
| 	"log" | 	"log" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"unicode" | 	"unicode" | ||||||
| 
 | 
 | ||||||
|  | @ -15,7 +16,7 @@ import ( | ||||||
| // HTMLWriter exports an org document into a html document.
 | // HTMLWriter exports an org document into a html document.
 | ||||||
| type HTMLWriter struct { | type HTMLWriter struct { | ||||||
| 	ExtendingWriter    Writer | 	ExtendingWriter    Writer | ||||||
| 	HighlightCodeBlock func(source, lang string) string | 	HighlightCodeBlock func(source, lang string, inline bool) string | ||||||
| 
 | 
 | ||||||
| 	strings.Builder | 	strings.Builder | ||||||
| 	document   *Document | 	document   *Document | ||||||
|  | @ -60,7 +61,10 @@ func NewHTMLWriter() *HTMLWriter { | ||||||
| 		document:   &Document{Configuration: defaultConfig}, | 		document:   &Document{Configuration: defaultConfig}, | ||||||
| 		log:        defaultConfig.Log, | 		log:        defaultConfig.Log, | ||||||
| 		htmlEscape: true, | 		htmlEscape: true, | ||||||
| 		HighlightCodeBlock: func(source, lang string) string { | 		HighlightCodeBlock: func(source, lang string, inline bool) string { | ||||||
|  | 			if inline { | ||||||
|  | 				return fmt.Sprintf("<div class=\"highlight-inline\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source)) | ||||||
|  | 			} | ||||||
| 			return fmt.Sprintf("<div class=\"highlight\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source)) | 			return fmt.Sprintf("<div class=\"highlight\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source)) | ||||||
| 		}, | 		}, | ||||||
| 		footnotes: &footnotes{ | 		footnotes: &footnotes{ | ||||||
|  | @ -88,7 +92,11 @@ func (w *HTMLWriter) WriterWithExtensions() Writer { | ||||||
| func (w *HTMLWriter) Before(d *Document) { | func (w *HTMLWriter) Before(d *Document) { | ||||||
| 	w.document = d | 	w.document = d | ||||||
| 	w.log = d.Log | 	w.log = d.Log | ||||||
| 	if title := d.Get("TITLE"); title != "" { | 	if title := d.Get("TITLE"); title != "" && w.document.GetOption("title") != "nil" { | ||||||
|  | 		titleDocument := d.Parse(strings.NewReader(title), d.Path) | ||||||
|  | 		if titleDocument.Error == nil { | ||||||
|  | 			title = w.WriteNodesAsString(titleDocument.Nodes...) | ||||||
|  | 		} | ||||||
| 		w.WriteString(fmt.Sprintf(`<h1 class="title">%s</h1>`+"\n", title)) | 		w.WriteString(fmt.Sprintf(`<h1 class="title">%s</h1>`+"\n", title)) | ||||||
| 	} | 	} | ||||||
| 	w.WriteOutline(d) | 	w.WriteOutline(d) | ||||||
|  | @ -102,38 +110,54 @@ func (w *HTMLWriter) WriteComment(Comment)               {} | ||||||
| func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {} | func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {} | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteBlock(b Block) { | func (w *HTMLWriter) WriteBlock(b Block) { | ||||||
| 	content := "" | 	content, params := w.blockContent(b.Name, b.Children), b.ParameterMap() | ||||||
| 	if isRawTextBlock(b.Name) { | 
 | ||||||
| 		builder, htmlEscape := w.Builder, w.htmlEscape | 	switch b.Name { | ||||||
| 		w.Builder, w.htmlEscape = strings.Builder{}, false | 	case "SRC": | ||||||
| 		WriteNodes(w, b.Children...) | 		if params[":exports"] == "results" || params[":exports"] == "none" { | ||||||
| 		out := w.String() | 			break | ||||||
| 		w.Builder, w.htmlEscape = builder, htmlEscape |  | ||||||
| 		content = strings.TrimRightFunc(out, unicode.IsSpace) |  | ||||||
| 	} else { |  | ||||||
| 		content = w.WriteNodesAsString(b.Children...) |  | ||||||
| 		} | 		} | ||||||
| 	switch name := b.Name; { |  | ||||||
| 	case name == "SRC": |  | ||||||
| 		lang := "text" | 		lang := "text" | ||||||
| 		if len(b.Parameters) >= 1 { | 		if len(b.Parameters) >= 1 { | ||||||
| 			lang = strings.ToLower(b.Parameters[0]) | 			lang = strings.ToLower(b.Parameters[0]) | ||||||
| 		} | 		} | ||||||
| 		content = w.HighlightCodeBlock(content, lang) | 		content = w.HighlightCodeBlock(content, lang, false) | ||||||
| 		w.WriteString(fmt.Sprintf("<div class=\"src src-%s\">\n%s\n</div>\n", lang, content)) | 		w.WriteString(fmt.Sprintf("<div class=\"src src-%s\">\n%s\n</div>\n", lang, content)) | ||||||
| 	case name == "EXAMPLE": | 	case "EXAMPLE": | ||||||
| 		w.WriteString(`<pre class="example">` + "\n" + content + "\n</pre>\n") | 		w.WriteString(`<pre class="example">` + "\n" + html.EscapeString(content) + "\n</pre>\n") | ||||||
| 	case name == "EXPORT" && len(b.Parameters) >= 1 && strings.ToLower(b.Parameters[0]) == "html": | 	case "EXPORT": | ||||||
|  | 		if len(b.Parameters) >= 1 && strings.ToLower(b.Parameters[0]) == "html" { | ||||||
| 			w.WriteString(content + "\n") | 			w.WriteString(content + "\n") | ||||||
| 	case name == "QUOTE": | 		} | ||||||
|  | 	case "QUOTE": | ||||||
| 		w.WriteString("<blockquote>\n" + content + "</blockquote>\n") | 		w.WriteString("<blockquote>\n" + content + "</blockquote>\n") | ||||||
| 	case name == "CENTER": | 	case "CENTER": | ||||||
| 		w.WriteString(`<div class="center-block" style="text-align: center; margin-left: auto; margin-right: auto;">` + "\n") | 		w.WriteString(`<div class="center-block" style="text-align: center; margin-left: auto; margin-right: auto;">` + "\n") | ||||||
| 		w.WriteString(content + "</div>\n") | 		w.WriteString(content + "</div>\n") | ||||||
| 	default: | 	default: | ||||||
| 		w.WriteString(fmt.Sprintf(`<div class="%s-block">`, strings.ToLower(b.Name)) + "\n") | 		w.WriteString(fmt.Sprintf(`<div class="%s-block">`, strings.ToLower(b.Name)) + "\n") | ||||||
| 		w.WriteString(content + "</div>\n") | 		w.WriteString(content + "</div>\n") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if b.Result != nil && params[":exports"] != "code" && params[":exports"] != "none" { | ||||||
|  | 		WriteNodes(w, b.Result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *HTMLWriter) WriteResult(r Result) { WriteNodes(w, r.Node) } | ||||||
|  | 
 | ||||||
|  | func (w *HTMLWriter) WriteInlineBlock(b InlineBlock) { | ||||||
|  | 	content := w.blockContent(strings.ToUpper(b.Name), b.Children) | ||||||
|  | 	switch b.Name { | ||||||
|  | 	case "src": | ||||||
|  | 		lang := strings.ToLower(b.Parameters[0]) | ||||||
|  | 		content = w.HighlightCodeBlock(content, lang, true) | ||||||
|  | 		w.WriteString(fmt.Sprintf("<div class=\"src src-inline src-%s\">\n%s\n</div>", lang, content)) | ||||||
|  | 	case "export": | ||||||
|  | 		if strings.ToLower(b.Parameters[0]) == "html" { | ||||||
|  | 			w.WriteString(content) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteDrawer(d Drawer) { | func (w *HTMLWriter) WriteDrawer(d Drawer) { | ||||||
|  | @ -155,7 +179,7 @@ func (w *HTMLWriter) WriteFootnoteDefinition(f FootnoteDefinition) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteFootnotes(d *Document) { | func (w *HTMLWriter) WriteFootnotes(d *Document) { | ||||||
| 	if !w.document.GetOption("f") || len(w.footnotes.list) == 0 { | 	if w.document.GetOption("f") == "nil" || len(w.footnotes.list) == 0 { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	w.WriteString(`<div class="footnotes">` + "\n") | 	w.WriteString(`<div class="footnotes">` + "\n") | ||||||
|  | @ -183,25 +207,33 @@ func (w *HTMLWriter) WriteFootnotes(d *Document) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteOutline(d *Document) { | func (w *HTMLWriter) WriteOutline(d *Document) { | ||||||
| 	if w.document.GetOption("toc") && len(d.Outline.Children) != 0 { | 	if w.document.GetOption("toc") != "nil" && len(d.Outline.Children) != 0 { | ||||||
|  | 		maxLvl, _ := strconv.Atoi(w.document.GetOption("toc")) | ||||||
| 		w.WriteString("<nav>\n<ul>\n") | 		w.WriteString("<nav>\n<ul>\n") | ||||||
| 		for _, section := range d.Outline.Children { | 		for _, section := range d.Outline.Children { | ||||||
| 			w.writeSection(section) | 			w.writeSection(section, maxLvl) | ||||||
| 		} | 		} | ||||||
| 		w.WriteString("</ul>\n</nav>\n") | 		w.WriteString("</ul>\n</nav>\n") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) writeSection(section *Section) { | func (w *HTMLWriter) writeSection(section *Section, maxLvl int) { | ||||||
|  | 	if maxLvl != 0 && section.Headline.Lvl > maxLvl { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	// NOTE: To satisfy hugo ExtractTOC() check we cannot use `<li>\n` here. Doesn't really matter, just a note.
 | 	// NOTE: To satisfy hugo ExtractTOC() check we cannot use `<li>\n` here. Doesn't really matter, just a note.
 | ||||||
| 	w.WriteString("<li>") | 	w.WriteString("<li>") | ||||||
| 	h := section.Headline | 	h := section.Headline | ||||||
| 	title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.WriteNodesAsString(h.Title...), "") | 	title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.WriteNodesAsString(h.Title...), "") | ||||||
| 	w.WriteString(fmt.Sprintf("<a href=\"#%s\">%s</a>\n", h.ID(), title)) | 	w.WriteString(fmt.Sprintf("<a href=\"#%s\">%s</a>\n", h.ID(), title)) | ||||||
| 	if len(section.Children) != 0 { | 	hasChildren := false | ||||||
|  | 	for _, section := range section.Children { | ||||||
|  | 		hasChildren = hasChildren || maxLvl == 0 || section.Headline.Lvl <= maxLvl | ||||||
|  | 	} | ||||||
|  | 	if hasChildren { | ||||||
| 		w.WriteString("<ul>\n") | 		w.WriteString("<ul>\n") | ||||||
| 		for _, section := range section.Children { | 		for _, section := range section.Children { | ||||||
| 			w.writeSection(section) | 			w.writeSection(section, maxLvl) | ||||||
| 		} | 		} | ||||||
| 		w.WriteString("</ul>\n") | 		w.WriteString("</ul>\n") | ||||||
| 	} | 	} | ||||||
|  | @ -217,16 +249,17 @@ func (w *HTMLWriter) WriteHeadline(h Headline) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	w.WriteString(fmt.Sprintf(`<div id="outline-container-%s" class="outline-%d">`, h.ID(), h.Lvl+1) + "\n") | ||||||
| 	w.WriteString(fmt.Sprintf(`<h%d id="%s">`, h.Lvl+1, h.ID()) + "\n") | 	w.WriteString(fmt.Sprintf(`<h%d id="%s">`, h.Lvl+1, h.ID()) + "\n") | ||||||
| 	if w.document.GetOption("todo") && h.Status != "" { | 	if w.document.GetOption("todo") != "nil" && h.Status != "" { | ||||||
| 		w.WriteString(fmt.Sprintf(`<span class="todo">%s</span>`, h.Status) + "\n") | 		w.WriteString(fmt.Sprintf(`<span class="todo">%s</span>`, h.Status) + "\n") | ||||||
| 	} | 	} | ||||||
| 	if w.document.GetOption("pri") && h.Priority != "" { | 	if w.document.GetOption("pri") != "nil" && h.Priority != "" { | ||||||
| 		w.WriteString(fmt.Sprintf(`<span class="priority">[%s]</span>`, h.Priority) + "\n") | 		w.WriteString(fmt.Sprintf(`<span class="priority">[%s]</span>`, h.Priority) + "\n") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	WriteNodes(w, h.Title...) | 	WriteNodes(w, h.Title...) | ||||||
| 	if w.document.GetOption("tags") && len(h.Tags) != 0 { | 	if w.document.GetOption("tags") != "nil" && len(h.Tags) != 0 { | ||||||
| 		tags := make([]string, len(h.Tags)) | 		tags := make([]string, len(h.Tags)) | ||||||
| 		for i, tag := range h.Tags { | 		for i, tag := range h.Tags { | ||||||
| 			tags[i] = fmt.Sprintf(`<span>%s</span>`, tag) | 			tags[i] = fmt.Sprintf(`<span>%s</span>`, tag) | ||||||
|  | @ -235,13 +268,16 @@ func (w *HTMLWriter) WriteHeadline(h Headline) { | ||||||
| 		w.WriteString(fmt.Sprintf(`<span class="tags">%s</span>`, strings.Join(tags, " "))) | 		w.WriteString(fmt.Sprintf(`<span class="tags">%s</span>`, strings.Join(tags, " "))) | ||||||
| 	} | 	} | ||||||
| 	w.WriteString(fmt.Sprintf("\n</h%d>\n", h.Lvl+1)) | 	w.WriteString(fmt.Sprintf("\n</h%d>\n", h.Lvl+1)) | ||||||
| 	WriteNodes(w, h.Children...) | 	if content := w.WriteNodesAsString(h.Children...); content != "" { | ||||||
|  | 		w.WriteString(fmt.Sprintf(`<div id="outline-text-%s" class="outline-text-%d">`, h.ID(), h.Lvl+1) + "\n" + content + "</div>\n") | ||||||
|  | 	} | ||||||
|  | 	w.WriteString("</div>\n") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteText(t Text) { | func (w *HTMLWriter) WriteText(t Text) { | ||||||
| 	if !w.htmlEscape { | 	if !w.htmlEscape { | ||||||
| 		w.WriteString(t.Content) | 		w.WriteString(t.Content) | ||||||
| 	} else if !w.document.GetOption("e") || t.IsRaw { | 	} else if w.document.GetOption("e") == "nil" || t.IsRaw { | ||||||
| 		w.WriteString(html.EscapeString(t.Content)) | 		w.WriteString(html.EscapeString(t.Content)) | ||||||
| 	} else { | 	} else { | ||||||
| 		w.WriteString(html.EscapeString(htmlEntityReplacer.Replace(t.Content))) | 		w.WriteString(html.EscapeString(htmlEntityReplacer.Replace(t.Content))) | ||||||
|  | @ -277,7 +313,7 @@ func (w *HTMLWriter) WriteExplicitLineBreak(l ExplicitLineBreak) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) { | func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) { | ||||||
| 	if !w.document.GetOption("f") { | 	if w.document.GetOption("f") == "nil" { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	i := w.footnotes.add(l) | 	i := w.footnotes.add(l) | ||||||
|  | @ -286,7 +322,7 @@ func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteTimestamp(t Timestamp) { | func (w *HTMLWriter) WriteTimestamp(t Timestamp) { | ||||||
| 	if !w.document.GetOption("<") { | 	if w.document.GetOption("<") == "nil" { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	w.WriteString(`<span class="timestamp"><`) | 	w.WriteString(`<span class="timestamp"><`) | ||||||
|  | @ -306,20 +342,46 @@ func (w *HTMLWriter) WriteRegularLink(l RegularLink) { | ||||||
| 	if l.Protocol == "file" { | 	if l.Protocol == "file" { | ||||||
| 		url = url[len("file:"):] | 		url = url[len("file:"):] | ||||||
| 	} | 	} | ||||||
|  | 	if prefix := w.document.Links[l.Protocol]; prefix != "" { | ||||||
|  | 		url = html.EscapeString(prefix) + strings.TrimPrefix(url, l.Protocol+":") | ||||||
|  | 	} | ||||||
|  | 	switch l.Kind() { | ||||||
|  | 	case "image": | ||||||
|  | 		if l.Description == nil { | ||||||
|  | 			w.WriteString(fmt.Sprintf(`<img src="%s" alt="%s" title="%s" />`, url, url, url)) | ||||||
|  | 		} else { | ||||||
|  | 			description := strings.TrimPrefix(String(l.Description), "file:") | ||||||
|  | 			w.WriteString(fmt.Sprintf(`<a href="%s"><img src="%s" alt="%s" /></a>`, url, description, description)) | ||||||
|  | 		} | ||||||
|  | 	case "video": | ||||||
|  | 		if l.Description == nil { | ||||||
|  | 			w.WriteString(fmt.Sprintf(`<video src="%s" title="%s">%s</video>`, url, url, url)) | ||||||
|  | 		} else { | ||||||
|  | 			description := strings.TrimPrefix(String(l.Description), "file:") | ||||||
|  | 			w.WriteString(fmt.Sprintf(`<a href="%s"><video src="%s" title="%s"></video></a>`, url, description, description)) | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
| 		description := url | 		description := url | ||||||
| 		if l.Description != nil { | 		if l.Description != nil { | ||||||
| 			description = w.WriteNodesAsString(l.Description...) | 			description = w.WriteNodesAsString(l.Description...) | ||||||
| 		} | 		} | ||||||
| 	switch l.Kind() { |  | ||||||
| 	case "image": |  | ||||||
| 		w.WriteString(fmt.Sprintf(`<img src="%s" alt="%s" title="%s" />`, url, description, description)) |  | ||||||
| 	case "video": |  | ||||||
| 		w.WriteString(fmt.Sprintf(`<video src="%s" title="%s">%s</video>`, url, description, description)) |  | ||||||
| 	default: |  | ||||||
| 		w.WriteString(fmt.Sprintf(`<a href="%s">%s</a>`, url, description)) | 		w.WriteString(fmt.Sprintf(`<a href="%s">%s</a>`, url, description)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (w *HTMLWriter) WriteMacro(m Macro) { | ||||||
|  | 	if macro := w.document.Macros[m.Name]; macro != "" { | ||||||
|  | 		for i, param := range m.Parameters { | ||||||
|  | 			macro = strings.Replace(macro, fmt.Sprintf("$%d", i+1), param, -1) | ||||||
|  | 		} | ||||||
|  | 		macroDocument := w.document.Parse(strings.NewReader(macro), w.document.Path) | ||||||
|  | 		if macroDocument.Error != nil { | ||||||
|  | 			w.log.Printf("bad macro: %s -> %s: %v", m.Name, macro, macroDocument.Error) | ||||||
|  | 		} | ||||||
|  | 		WriteNodes(w, macroDocument.Nodes...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (w *HTMLWriter) WriteList(l List) { | func (w *HTMLWriter) WriteList(l List) { | ||||||
| 	tags, ok := listTags[l.Kind] | 	tags, ok := listTags[l.Kind] | ||||||
| 	if !ok { | 	if !ok { | ||||||
|  | @ -363,11 +425,8 @@ func (w *HTMLWriter) WriteParagraph(p Paragraph) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	w.WriteString("<p>") | 	w.WriteString("<p>") | ||||||
| 	if _, ok := p.Children[0].(LineBreak); !ok { |  | ||||||
| 		w.WriteString("\n") |  | ||||||
| 	} |  | ||||||
| 	WriteNodes(w, p.Children...) | 	WriteNodes(w, p.Children...) | ||||||
| 	w.WriteString("\n</p>\n") | 	w.WriteString("</p>\n") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteExample(e Example) { | func (w *HTMLWriter) WriteExample(e Example) { | ||||||
|  | @ -414,24 +473,32 @@ func (w *HTMLWriter) WriteNodeWithName(n NodeWithName) { | ||||||
| 
 | 
 | ||||||
| func (w *HTMLWriter) WriteTable(t Table) { | func (w *HTMLWriter) WriteTable(t Table) { | ||||||
| 	w.WriteString("<table>\n") | 	w.WriteString("<table>\n") | ||||||
| 	beforeFirstContentRow := true | 	inHead := len(t.SeparatorIndices) > 0 && | ||||||
| 	for i, row := range t.Rows { | 		t.SeparatorIndices[0] != len(t.Rows)-1 && | ||||||
| 		if row.IsSpecial || len(row.Columns) == 0 { | 		(t.SeparatorIndices[0] != 0 || len(t.SeparatorIndices) > 1 && t.SeparatorIndices[len(t.SeparatorIndices)-1] != len(t.Rows)-1) | ||||||
| 			continue | 	if inHead { | ||||||
| 		} |  | ||||||
| 		if beforeFirstContentRow { |  | ||||||
| 			beforeFirstContentRow = false |  | ||||||
| 			if i+1 < len(t.Rows) && len(t.Rows[i+1].Columns) == 0 { |  | ||||||
| 		w.WriteString("<thead>\n") | 		w.WriteString("<thead>\n") | ||||||
| 				w.writeTableColumns(row.Columns, "th") |  | ||||||
| 				w.WriteString("</thead>\n<tbody>\n") |  | ||||||
| 				continue |  | ||||||
| 	} else { | 	} else { | ||||||
| 		w.WriteString("<tbody>\n") | 		w.WriteString("<tbody>\n") | ||||||
| 	} | 	} | ||||||
|  | 	for i, row := range t.Rows { | ||||||
|  | 		if len(row.Columns) == 0 && i != 0 && i != len(t.Rows)-1 { | ||||||
|  | 			if inHead { | ||||||
|  | 				w.WriteString("</thead>\n<tbody>\n") | ||||||
|  | 				inHead = false | ||||||
|  | 			} else { | ||||||
|  | 				w.WriteString("</tbody>\n<tbody>\n") | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  | 		if row.IsSpecial { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if inHead { | ||||||
|  | 			w.writeTableColumns(row.Columns, "th") | ||||||
|  | 		} else { | ||||||
| 			w.writeTableColumns(row.Columns, "td") | 			w.writeTableColumns(row.Columns, "td") | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	w.WriteString("</tbody>\n</table>\n") | 	w.WriteString("</tbody>\n</table>\n") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -472,6 +539,19 @@ func (w *HTMLWriter) withHTMLAttributes(input string, kvs ...string) string { | ||||||
| 	return out.String() | 	return out.String() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (w *HTMLWriter) blockContent(name string, children []Node) string { | ||||||
|  | 	if isRawTextBlock(name) { | ||||||
|  | 		builder, htmlEscape := w.Builder, w.htmlEscape | ||||||
|  | 		w.Builder, w.htmlEscape = strings.Builder{}, false | ||||||
|  | 		WriteNodes(w, children...) | ||||||
|  | 		out := w.String() | ||||||
|  | 		w.Builder, w.htmlEscape = builder, htmlEscape | ||||||
|  | 		return strings.TrimRightFunc(out, unicode.IsSpace) | ||||||
|  | 	} else { | ||||||
|  | 		return w.WriteNodesAsString(children...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute { | func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute { | ||||||
| 	for i, a := range attributes { | 	for i, a := range attributes { | ||||||
| 		if strings.ToLower(a.Key) == strings.ToLower(k) { | 		if strings.ToLower(a.Key) == strings.ToLower(k) { | ||||||
|  |  | ||||||
|  | @ -30,6 +30,12 @@ type Emphasis struct { | ||||||
| 	Content []Node | 	Content []Node | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type InlineBlock struct { | ||||||
|  | 	Name       string | ||||||
|  | 	Parameters []string | ||||||
|  | 	Children   []Node | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type LatexFragment struct { | type LatexFragment struct { | ||||||
| 	OpeningPair string | 	OpeningPair string | ||||||
| 	ClosingPair string | 	ClosingPair string | ||||||
|  | @ -48,6 +54,11 @@ type RegularLink struct { | ||||||
| 	AutoLink    bool | 	AutoLink    bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type Macro struct { | ||||||
|  | 	Name       string | ||||||
|  | 	Parameters []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var validURLCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=" | var validURLCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=" | ||||||
| var autolinkProtocols = regexp.MustCompile(`^(https?|ftp|file)$`) | var autolinkProtocols = regexp.MustCompile(`^(https?|ftp|file)$`) | ||||||
| var imageExtensionRegexp = regexp.MustCompile(`^[.](png|gif|jpe?g|svg|tiff?)$`) | var imageExtensionRegexp = regexp.MustCompile(`^[.](png|gif|jpe?g|svg|tiff?)$`) | ||||||
|  | @ -58,6 +69,9 @@ var timestampRegexp = regexp.MustCompile(`^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d | ||||||
| var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`) | var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`) | ||||||
| var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`) | var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`) | ||||||
| var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`) | var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`) | ||||||
|  | var inlineBlockRegexp = regexp.MustCompile(`src_(\w+)(\[(.*)\])?{(.*)}`) | ||||||
|  | var inlineExportBlockRegexp = regexp.MustCompile(`@@(\w+):(.*?)@@`) | ||||||
|  | var macroRegexp = regexp.MustCompile(`{{{(.*)\((.*)\)}}}`) | ||||||
| 
 | 
 | ||||||
| var timestampFormat = "2006-01-02 Mon 15:04" | var timestampFormat = "2006-01-02 Mon 15:04" | ||||||
| var datestampFormat = "2006-01-02 Mon" | var datestampFormat = "2006-01-02 Mon" | ||||||
|  | @ -66,6 +80,7 @@ var latexFragmentPairs = map[string]string{ | ||||||
| 	`\(`: `\)`, | 	`\(`: `\)`, | ||||||
| 	`\[`: `\]`, | 	`\[`: `\]`, | ||||||
| 	`$$`: `$$`, | 	`$$`: `$$`, | ||||||
|  | 	`$`:  `$`, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseInline(input string) (nodes []Node) { | func (d *Document) parseInline(input string) (nodes []Node) { | ||||||
|  | @ -76,25 +91,29 @@ func (d *Document) parseInline(input string) (nodes []Node) { | ||||||
| 		case '^': | 		case '^': | ||||||
| 			consumed, node = d.parseSubOrSuperScript(input, current) | 			consumed, node = d.parseSubOrSuperScript(input, current) | ||||||
| 		case '_': | 		case '_': | ||||||
| 			consumed, node = d.parseSubScriptOrEmphasis(input, current) | 			rewind, consumed, node = d.parseSubScriptOrEmphasisOrInlineBlock(input, current) | ||||||
|  | 		case '@': | ||||||
|  | 			consumed, node = d.parseInlineExportBlock(input, current) | ||||||
| 		case '*', '/', '+': | 		case '*', '/', '+': | ||||||
| 			consumed, node = d.parseEmphasis(input, current, false) | 			consumed, node = d.parseEmphasis(input, current, false) | ||||||
| 		case '=', '~': | 		case '=', '~': | ||||||
| 			consumed, node = d.parseEmphasis(input, current, true) | 			consumed, node = d.parseEmphasis(input, current, true) | ||||||
| 		case '[': | 		case '[': | ||||||
| 			consumed, node = d.parseOpeningBracket(input, current) | 			consumed, node = d.parseOpeningBracket(input, current) | ||||||
|  | 		case '{': | ||||||
|  | 			consumed, node = d.parseMacro(input, current) | ||||||
| 		case '<': | 		case '<': | ||||||
| 			consumed, node = d.parseTimestamp(input, current) | 			consumed, node = d.parseTimestamp(input, current) | ||||||
| 		case '\\': | 		case '\\': | ||||||
| 			consumed, node = d.parseExplicitLineBreakOrLatexFragment(input, current) | 			consumed, node = d.parseExplicitLineBreakOrLatexFragment(input, current) | ||||||
| 		case '$': | 		case '$': | ||||||
| 			consumed, node = d.parseLatexFragment(input, current) | 			consumed, node = d.parseLatexFragment(input, current, 1) | ||||||
| 		case '\n': | 		case '\n': | ||||||
| 			consumed, node = d.parseLineBreak(input, current) | 			consumed, node = d.parseLineBreak(input, current) | ||||||
| 		case ':': | 		case ':': | ||||||
| 			rewind, consumed, node = d.parseAutoLink(input, current) | 			rewind, consumed, node = d.parseAutoLink(input, current) | ||||||
| 			current -= rewind |  | ||||||
| 		} | 		} | ||||||
|  | 		current -= rewind | ||||||
| 		if consumed != 0 { | 		if consumed != 0 { | ||||||
| 			if current > previous { | 			if current > previous { | ||||||
| 				nodes = append(nodes, Text{input[previous:current], false}) | 				nodes = append(nodes, Text{input[previous:current], false}) | ||||||
|  | @ -143,6 +162,23 @@ func (d *Document) parseLineBreak(input string, start int) (int, Node) { | ||||||
| 	return i - start, LineBreak{i - start} | 	return i - start, LineBreak{i - start} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (d *Document) parseInlineBlock(input string, start int) (int, int, Node) { | ||||||
|  | 	if !(strings.HasSuffix(input[:start], "src") && (start-4 < 0 || unicode.IsSpace(rune(input[start-4])))) { | ||||||
|  | 		return 0, 0, nil | ||||||
|  | 	} | ||||||
|  | 	if m := inlineBlockRegexp.FindStringSubmatch(input[start-3:]); m != nil { | ||||||
|  | 		return 3, len(m[0]), InlineBlock{"src", strings.Fields(m[1] + " " + m[3]), d.parseRawInline(m[4])} | ||||||
|  | 	} | ||||||
|  | 	return 0, 0, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (d *Document) parseInlineExportBlock(input string, start int) (int, Node) { | ||||||
|  | 	if m := inlineExportBlockRegexp.FindStringSubmatch(input[start:]); m != nil { | ||||||
|  | 		return len(m[0]), InlineBlock{"export", m[1:2], d.parseRawInline(m[2])} | ||||||
|  | 	} | ||||||
|  | 	return 0, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) { | func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) { | ||||||
| 	switch { | 	switch { | ||||||
| 	case start+2 >= len(input): | 	case start+2 >= len(input): | ||||||
|  | @ -153,7 +189,7 @@ func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case input[start+1] == '(' || input[start+1] == '[': | 	case input[start+1] == '(' || input[start+1] == '[': | ||||||
| 		return d.parseLatexFragment(input, start) | 		return d.parseLatexFragment(input, start, 2) | ||||||
| 	case strings.Index(input[start:], `\begin{`) == 0: | 	case strings.Index(input[start:], `\begin{`) == 0: | ||||||
| 		if m := latexFragmentRegexp.FindStringSubmatch(input[start:]); m != nil { | 		if m := latexFragmentRegexp.FindStringSubmatch(input[start:]); m != nil { | ||||||
| 			if open, content, close := m[1], m[2], m[3]; open == close { | 			if open, content, close := m[1], m[2], m[3]; open == close { | ||||||
|  | @ -166,15 +202,18 @@ func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int | ||||||
| 	return 0, nil | 	return 0, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseLatexFragment(input string, start int) (int, Node) { | func (d *Document) parseLatexFragment(input string, start int, pairLength int) (int, Node) { | ||||||
| 	if start+2 >= len(input) { | 	if start+2 >= len(input) { | ||||||
| 		return 0, nil | 		return 0, nil | ||||||
| 	} | 	} | ||||||
| 	openingPair := input[start : start+2] | 	if pairLength == 1 && input[start:start+2] == "$$" { | ||||||
|  | 		pairLength = 2 | ||||||
|  | 	} | ||||||
|  | 	openingPair := input[start : start+pairLength] | ||||||
| 	closingPair := latexFragmentPairs[openingPair] | 	closingPair := latexFragmentPairs[openingPair] | ||||||
| 	if i := strings.Index(input[start+2:], closingPair); i != -1 { | 	if i := strings.Index(input[start+pairLength:], closingPair); i != -1 { | ||||||
| 		content := d.parseRawInline(input[start+2 : start+2+i]) | 		content := d.parseRawInline(input[start+pairLength : start+pairLength+i]) | ||||||
| 		return i + 2 + 2, LatexFragment{openingPair, closingPair, content} | 		return i + pairLength + pairLength, LatexFragment{openingPair, closingPair, content} | ||||||
| 	} | 	} | ||||||
| 	return 0, nil | 	return 0, nil | ||||||
| } | } | ||||||
|  | @ -186,11 +225,14 @@ func (d *Document) parseSubOrSuperScript(input string, start int) (int, Node) { | ||||||
| 	return 0, nil | 	return 0, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseSubScriptOrEmphasis(input string, start int) (int, Node) { | func (d *Document) parseSubScriptOrEmphasisOrInlineBlock(input string, start int) (int, int, Node) { | ||||||
| 	if consumed, node := d.parseSubOrSuperScript(input, start); consumed != 0 { | 	if rewind, consumed, node := d.parseInlineBlock(input, start); consumed != 0 { | ||||||
| 		return consumed, node | 		return rewind, consumed, node | ||||||
|  | 	} else if consumed, node := d.parseSubOrSuperScript(input, start); consumed != 0 { | ||||||
|  | 		return 0, consumed, node | ||||||
| 	} | 	} | ||||||
| 	return d.parseEmphasis(input, start, false) | 	consumed, node := d.parseEmphasis(input, start, false) | ||||||
|  | 	return 0, consumed, node | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseOpeningBracket(input string, start int) (int, Node) { | func (d *Document) parseOpeningBracket(input string, start int) (int, Node) { | ||||||
|  | @ -204,6 +246,13 @@ func (d *Document) parseOpeningBracket(input string, start int) (int, Node) { | ||||||
| 	return 0, nil | 	return 0, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (d *Document) parseMacro(input string, start int) (int, Node) { | ||||||
|  | 	if m := macroRegexp.FindStringSubmatch(input[start:]); m != nil { | ||||||
|  | 		return len(m[0]), Macro{m[1], strings.Split(m[2], ",")} | ||||||
|  | 	} | ||||||
|  | 	return 0, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (d *Document) parseFootnoteReference(input string, start int) (int, Node) { | func (d *Document) parseFootnoteReference(input string, start int) (int, Node) { | ||||||
| 	if m := footnoteRegexp.FindStringSubmatch(input[start:]); m != nil { | 	if m := footnoteRegexp.FindStringSubmatch(input[start:]); m != nil { | ||||||
| 		name, definition := m[1], m[3] | 		name, definition := m[1], m[3] | ||||||
|  | @ -334,6 +383,14 @@ func isValidPostChar(r rune) bool { | ||||||
| func isValidBorderChar(r rune) bool { return !unicode.IsSpace(r) } | func isValidBorderChar(r rune) bool { return !unicode.IsSpace(r) } | ||||||
| 
 | 
 | ||||||
| func (l RegularLink) Kind() string { | func (l RegularLink) Kind() string { | ||||||
|  | 	description := String(l.Description) | ||||||
|  | 	descProtocol, descExt := strings.SplitN(description, ":", 2)[0], path.Ext(description) | ||||||
|  | 	if ok := descProtocol == "file" || descProtocol == "http" || descProtocol == "https"; ok && imageExtensionRegexp.MatchString(descExt) { | ||||||
|  | 		return "image" | ||||||
|  | 	} else if ok && videoExtensionRegexp.MatchString(descExt) { | ||||||
|  | 		return "video" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if p := l.Protocol; l.Description != nil || (p != "" && p != "file" && p != "http" && p != "https") { | 	if p := l.Protocol; l.Description != nil || (p != "" && p != "file" && p != "http" && p != "https") { | ||||||
| 		return "regular" | 		return "regular" | ||||||
| 	} | 	} | ||||||
|  | @ -351,7 +408,9 @@ func (n LineBreak) String() string         { return orgWriter.WriteNodesAsString | ||||||
| func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) } | func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) } | ||||||
| func (n StatisticToken) String() string    { return orgWriter.WriteNodesAsString(n) } | func (n StatisticToken) String() string    { return orgWriter.WriteNodesAsString(n) } | ||||||
| func (n Emphasis) String() string          { return orgWriter.WriteNodesAsString(n) } | func (n Emphasis) String() string          { return orgWriter.WriteNodesAsString(n) } | ||||||
|  | func (n InlineBlock) String() string       { return orgWriter.WriteNodesAsString(n) } | ||||||
| func (n LatexFragment) String() string     { return orgWriter.WriteNodesAsString(n) } | func (n LatexFragment) String() string     { return orgWriter.WriteNodesAsString(n) } | ||||||
| func (n FootnoteLink) String() string      { return orgWriter.WriteNodesAsString(n) } | func (n FootnoteLink) String() string      { return orgWriter.WriteNodesAsString(n) } | ||||||
| func (n RegularLink) String() string       { return orgWriter.WriteNodesAsString(n) } | func (n RegularLink) String() string       { return orgWriter.WriteNodesAsString(n) } | ||||||
|  | func (n Macro) String() string             { return orgWriter.WriteNodesAsString(n) } | ||||||
| func (n Timestamp) String() string         { return orgWriter.WriteNodesAsString(n) } | func (n Timestamp) String() string         { return orgWriter.WriteNodesAsString(n) } | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ type Include struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):(\s+(.*)|$)`) | var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):(\s+(.*)|$)`) | ||||||
| var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`) | var commentRegexp = regexp.MustCompile(`^(\s*)#\s(.*)`) | ||||||
| 
 | 
 | ||||||
| var includeFileRegexp = regexp.MustCompile(`(?i)^"([^"]+)" (src|example|export) (\w+)$`) | var includeFileRegexp = regexp.MustCompile(`(?i)^"([^"]+)" (src|example|export) (\w+)$`) | ||||||
| var attributeRegexp = regexp.MustCompile(`(?:^|\s+)(:[-\w]+)\s+(.*)$`) | var attributeRegexp = regexp.MustCompile(`(?:^|\s+)(:[-\w]+)\s+(.*)$`) | ||||||
|  | @ -62,6 +62,16 @@ func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) { | ||||||
| 		return d.loadSetupFile(k) | 		return d.loadSetupFile(k) | ||||||
| 	case "INCLUDE": | 	case "INCLUDE": | ||||||
| 		return d.parseInclude(k) | 		return d.parseInclude(k) | ||||||
|  | 	case "LINK": | ||||||
|  | 		if parts := strings.Split(k.Value, " "); len(parts) >= 2 { | ||||||
|  | 			d.Links[parts[0]] = parts[1] | ||||||
|  | 		} | ||||||
|  | 		return 1, k | ||||||
|  | 	case "MACRO": | ||||||
|  | 		if parts := strings.Split(k.Value, " "); len(parts) >= 2 { | ||||||
|  | 			d.Macros[parts[0]] = parts[1] | ||||||
|  | 		} | ||||||
|  | 		return 1, k | ||||||
| 	case "CAPTION", "ATTR_HTML": | 	case "CAPTION", "ATTR_HTML": | ||||||
| 		consumed, node := d.parseAffiliated(i, stop) | 		consumed, node := d.parseAffiliated(i, stop) | ||||||
| 		if consumed != 0 { | 		if consumed != 0 { | ||||||
|  | @ -150,7 +160,7 @@ func (d *Document) parseInclude(k Keyword) (int, Node) { | ||||||
| 				d.Log.Printf("Bad include %#v: %s", k, err) | 				d.Log.Printf("Bad include %#v: %s", k, err) | ||||||
| 				return k | 				return k | ||||||
| 			} | 			} | ||||||
| 			return Block{strings.ToUpper(kind), []string{lang}, d.parseRawInline(string(bs))} | 			return Block{strings.ToUpper(kind), []string{lang}, d.parseRawInline(string(bs)), nil} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return 1, Include{k, resolve} | 	return 1, Include{k, resolve} | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package org | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"unicode" | 	"unicode" | ||||||
| 	"unicode/utf8" | 	"unicode/utf8" | ||||||
|  | @ -16,6 +17,8 @@ type OrgWriter struct { | ||||||
| 	indent string | 	indent string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var exampleBlockUnescapeRegexp = regexp.MustCompile(`(^|\n)([ \t]*)(\*|,\*|#\+|,#\+)`) | ||||||
|  | 
 | ||||||
| var emphasisOrgBorders = map[string][]string{ | var emphasisOrgBorders = map[string][]string{ | ||||||
| 	"_":   []string{"_", "_"}, | 	"_":   []string{"_", "_"}, | ||||||
| 	"*":   []string{"*", "*"}, | 	"*":   []string{"*", "*"}, | ||||||
|  | @ -90,11 +93,42 @@ func (w *OrgWriter) WriteBlock(b Block) { | ||||||
| 	if isRawTextBlock(b.Name) { | 	if isRawTextBlock(b.Name) { | ||||||
| 		w.WriteString(w.indent) | 		w.WriteString(w.indent) | ||||||
| 	} | 	} | ||||||
| 	WriteNodes(w, b.Children...) | 	content := w.WriteNodesAsString(b.Children...) | ||||||
|  | 	if b.Name == "EXAMPLE" || (b.Name == "SRC" && len(b.Parameters) >= 1 && b.Parameters[0] == "org") { | ||||||
|  | 		content = exampleBlockUnescapeRegexp.ReplaceAllString(content, "$1$2,$3") | ||||||
|  | 	} | ||||||
|  | 	w.WriteString(content) | ||||||
| 	if !isRawTextBlock(b.Name) { | 	if !isRawTextBlock(b.Name) { | ||||||
| 		w.WriteString(w.indent) | 		w.WriteString(w.indent) | ||||||
| 	} | 	} | ||||||
| 	w.WriteString("#+END_" + b.Name + "\n") | 	w.WriteString("#+END_" + b.Name + "\n") | ||||||
|  | 
 | ||||||
|  | 	if b.Result != nil { | ||||||
|  | 		w.WriteString("\n") | ||||||
|  | 		WriteNodes(w, b.Result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *OrgWriter) WriteResult(r Result) { | ||||||
|  | 	w.WriteString("#+RESULTS:\n") | ||||||
|  | 	WriteNodes(w, r.Node) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *OrgWriter) WriteInlineBlock(b InlineBlock) { | ||||||
|  | 	switch b.Name { | ||||||
|  | 	case "src": | ||||||
|  | 		w.WriteString(b.Name + "_" + b.Parameters[0]) | ||||||
|  | 		if len(b.Parameters) > 1 { | ||||||
|  | 			w.WriteString("[" + strings.Join(b.Parameters[1:], " ") + "]") | ||||||
|  | 		} | ||||||
|  | 		w.WriteString("{") | ||||||
|  | 		WriteNodes(w, b.Children...) | ||||||
|  | 		w.WriteString("}") | ||||||
|  | 	case "export": | ||||||
|  | 		w.WriteString("@@" + b.Parameters[0] + ":") | ||||||
|  | 		WriteNodes(w, b.Children...) | ||||||
|  | 		w.WriteString("@@") | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *OrgWriter) WriteDrawer(d Drawer) { | func (w *OrgWriter) WriteDrawer(d Drawer) { | ||||||
|  | @ -173,7 +207,7 @@ func (w *OrgWriter) WriteNodeWithName(n NodeWithName) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *OrgWriter) WriteComment(c Comment) { | func (w *OrgWriter) WriteComment(c Comment) { | ||||||
| 	w.WriteString(w.indent + "#" + c.Content + "\n") | 	w.WriteString(w.indent + "# " + c.Content + "\n") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) } | func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) } | ||||||
|  | @ -326,3 +360,7 @@ func (w *OrgWriter) WriteRegularLink(l RegularLink) { | ||||||
| 		w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...))) | 		w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...))) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (w *OrgWriter) WriteMacro(m Macro) { | ||||||
|  | 	w.WriteString(fmt.Sprintf("{{{%s(%s)}}}", m.Name, strings.Join(m.Parameters, ","))) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| type Table struct { | type Table struct { | ||||||
| 	Rows             []Row | 	Rows             []Row | ||||||
| 	ColumnInfos      []ColumnInfo | 	ColumnInfos      []ColumnInfo | ||||||
|  | 	SeparatorIndices []int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Row struct { | type Row struct { | ||||||
|  | @ -25,12 +26,13 @@ type Column struct { | ||||||
| type ColumnInfo struct { | type ColumnInfo struct { | ||||||
| 	Align      string | 	Align      string | ||||||
| 	Len        int | 	Len        int | ||||||
|  | 	DisplayLen int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var tableSeparatorRegexp = regexp.MustCompile(`^(\s*)(\|[+-|]*)\s*$`) | var tableSeparatorRegexp = regexp.MustCompile(`^(\s*)(\|[+-|]*)\s*$`) | ||||||
| var tableRowRegexp = regexp.MustCompile(`^(\s*)(\|.*)`) | var tableRowRegexp = regexp.MustCompile(`^(\s*)(\|.*)`) | ||||||
| 
 | 
 | ||||||
| var columnAlignRegexp = regexp.MustCompile(`^<(l|c|r)>$`) | var columnAlignAndLengthRegexp = regexp.MustCompile(`^<(l|c|r)?(\d+)?>$`) | ||||||
| 
 | 
 | ||||||
| func lexTable(line string) (token, bool) { | func lexTable(line string) (token, bool) { | ||||||
| 	if m := tableSeparatorRegexp.FindStringSubmatch(line); m != nil { | 	if m := tableSeparatorRegexp.FindStringSubmatch(line); m != nil { | ||||||
|  | @ -42,7 +44,7 @@ func lexTable(line string) (token, bool) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) { | func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) { | ||||||
| 	rawRows, start := [][]string{}, i | 	rawRows, separatorIndices, start := [][]string{}, []int{}, i | ||||||
| 	for ; !parentStop(d, i); i++ { | 	for ; !parentStop(d, i); i++ { | ||||||
| 		if t := d.tokens[i]; t.kind == "tableRow" { | 		if t := d.tokens[i]; t.kind == "tableRow" { | ||||||
| 			rawRow := strings.FieldsFunc(d.tokens[i].content, func(r rune) bool { return r == '|' }) | 			rawRow := strings.FieldsFunc(d.tokens[i].content, func(r rune) bool { return r == '|' }) | ||||||
|  | @ -51,13 +53,14 @@ func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) { | ||||||
| 			} | 			} | ||||||
| 			rawRows = append(rawRows, rawRow) | 			rawRows = append(rawRows, rawRow) | ||||||
| 		} else if t.kind == "tableSeparator" { | 		} else if t.kind == "tableSeparator" { | ||||||
|  | 			separatorIndices = append(separatorIndices, i-start) | ||||||
| 			rawRows = append(rawRows, nil) | 			rawRows = append(rawRows, nil) | ||||||
| 		} else { | 		} else { | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	table := Table{nil, getColumnInfos(rawRows)} | 	table := Table{nil, getColumnInfos(rawRows), separatorIndices} | ||||||
| 	for _, rawColumns := range rawRows { | 	for _, rawColumns := range rawRows { | ||||||
| 		row := Row{nil, isSpecialRow(rawColumns)} | 		row := Row{nil, isSpecialRow(rawColumns)} | ||||||
| 		if len(rawColumns) != 0 { | 		if len(rawColumns) != 0 { | ||||||
|  | @ -94,7 +97,7 @@ func getColumnInfos(rows [][]string) []ColumnInfo { | ||||||
| 				columnInfos[i].Len = n | 				columnInfos[i].Len = n | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if m := columnAlignRegexp.FindStringSubmatch(columns[i]); m != nil && isSpecialRow(columns) { | 			if m := columnAlignAndLengthRegexp.FindStringSubmatch(columns[i]); m != nil && isSpecialRow(columns) { | ||||||
| 				switch m[1] { | 				switch m[1] { | ||||||
| 				case "l": | 				case "l": | ||||||
| 					columnInfos[i].Align = "left" | 					columnInfos[i].Align = "left" | ||||||
|  | @ -103,6 +106,10 @@ func getColumnInfos(rows [][]string) []ColumnInfo { | ||||||
| 				case "r": | 				case "r": | ||||||
| 					columnInfos[i].Align = "right" | 					columnInfos[i].Align = "right" | ||||||
| 				} | 				} | ||||||
|  | 				if m[2] != "" { | ||||||
|  | 					l, _ := strconv.Atoi(m[2]) | ||||||
|  | 					columnInfos[i].DisplayLen = l | ||||||
|  | 				} | ||||||
| 			} else if _, err := strconv.ParseFloat(columns[i], 32); err == nil { | 			} else if _, err := strconv.ParseFloat(columns[i], 32); err == nil { | ||||||
| 				countNumeric++ | 				countNumeric++ | ||||||
| 			} else if strings.TrimSpace(columns[i]) != "" { | 			} else if strings.TrimSpace(columns[i]) != "" { | ||||||
|  | @ -120,7 +127,7 @@ func getColumnInfos(rows [][]string) []ColumnInfo { | ||||||
| func isSpecialRow(rawColumns []string) bool { | func isSpecialRow(rawColumns []string) bool { | ||||||
| 	isAlignRow := true | 	isAlignRow := true | ||||||
| 	for _, rawColumn := range rawColumns { | 	for _, rawColumn := range rawColumns { | ||||||
| 		if !columnAlignRegexp.MatchString(rawColumn) && rawColumn != "" { | 		if !columnAlignAndLengthRegexp.MatchString(rawColumn) && rawColumn != "" { | ||||||
| 			isAlignRow = false | 			isAlignRow = false | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -18,6 +18,8 @@ type Writer interface { | ||||||
| 	WriteNodeWithName(NodeWithName) | 	WriteNodeWithName(NodeWithName) | ||||||
| 	WriteHeadline(Headline) | 	WriteHeadline(Headline) | ||||||
| 	WriteBlock(Block) | 	WriteBlock(Block) | ||||||
|  | 	WriteResult(Result) | ||||||
|  | 	WriteInlineBlock(InlineBlock) | ||||||
| 	WriteExample(Example) | 	WriteExample(Example) | ||||||
| 	WriteDrawer(Drawer) | 	WriteDrawer(Drawer) | ||||||
| 	WritePropertyDrawer(PropertyDrawer) | 	WritePropertyDrawer(PropertyDrawer) | ||||||
|  | @ -34,6 +36,7 @@ type Writer interface { | ||||||
| 	WriteExplicitLineBreak(ExplicitLineBreak) | 	WriteExplicitLineBreak(ExplicitLineBreak) | ||||||
| 	WriteLineBreak(LineBreak) | 	WriteLineBreak(LineBreak) | ||||||
| 	WriteRegularLink(RegularLink) | 	WriteRegularLink(RegularLink) | ||||||
|  | 	WriteMacro(Macro) | ||||||
| 	WriteTimestamp(Timestamp) | 	WriteTimestamp(Timestamp) | ||||||
| 	WriteFootnoteLink(FootnoteLink) | 	WriteFootnoteLink(FootnoteLink) | ||||||
| 	WriteFootnoteDefinition(FootnoteDefinition) | 	WriteFootnoteDefinition(FootnoteDefinition) | ||||||
|  | @ -57,6 +60,10 @@ func WriteNodes(w Writer, nodes ...Node) { | ||||||
| 			w.WriteHeadline(n) | 			w.WriteHeadline(n) | ||||||
| 		case Block: | 		case Block: | ||||||
| 			w.WriteBlock(n) | 			w.WriteBlock(n) | ||||||
|  | 		case Result: | ||||||
|  | 			w.WriteResult(n) | ||||||
|  | 		case InlineBlock: | ||||||
|  | 			w.WriteInlineBlock(n) | ||||||
| 		case Example: | 		case Example: | ||||||
| 			w.WriteExample(n) | 			w.WriteExample(n) | ||||||
| 		case Drawer: | 		case Drawer: | ||||||
|  | @ -89,6 +96,8 @@ func WriteNodes(w Writer, nodes ...Node) { | ||||||
| 			w.WriteLineBreak(n) | 			w.WriteLineBreak(n) | ||||||
| 		case RegularLink: | 		case RegularLink: | ||||||
| 			w.WriteRegularLink(n) | 			w.WriteRegularLink(n) | ||||||
|  | 		case Macro: | ||||||
|  | 			w.WriteMacro(n) | ||||||
| 		case Timestamp: | 		case Timestamp: | ||||||
| 			w.WriteTimestamp(n) | 			w.WriteTimestamp(n) | ||||||
| 		case FootnoteLink: | 		case FootnoteLink: | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -201,7 +201,8 @@ github.com/denisenkom/go-mssqldb/internal/querytext | ||||||
| # github.com/dgrijalva/jwt-go v3.2.0+incompatible | # github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
| ## explicit | ## explicit | ||||||
| github.com/dgrijalva/jwt-go | github.com/dgrijalva/jwt-go | ||||||
| # github.com/dlclark/regexp2 v1.2.0 | # github.com/dlclark/regexp2 v1.2.1 | ||||||
|  | ## explicit | ||||||
| github.com/dlclark/regexp2 | github.com/dlclark/regexp2 | ||||||
| github.com/dlclark/regexp2/syntax | github.com/dlclark/regexp2/syntax | ||||||
| # github.com/dsnet/compress v0.0.1 | # github.com/dsnet/compress v0.0.1 | ||||||
|  | @ -599,7 +600,7 @@ github.com/msteinert/pam | ||||||
| # github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | # github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | ||||||
| ## explicit | ## explicit | ||||||
| github.com/nfnt/resize | github.com/nfnt/resize | ||||||
| # github.com/niklasfasching/go-org v0.1.9 | # github.com/niklasfasching/go-org v1.3.2 | ||||||
| ## explicit | ## explicit | ||||||
| github.com/niklasfasching/go-org/org | github.com/niklasfasching/go-org/org | ||||||
| # github.com/nwaples/rardecode v1.0.0 | # github.com/nwaples/rardecode v1.0.0 | ||||||
|  | @ -795,7 +796,7 @@ golang.org/x/crypto/ssh/knownhosts | ||||||
| # golang.org/x/mod v0.3.0 | # golang.org/x/mod v0.3.0 | ||||||
| golang.org/x/mod/module | golang.org/x/mod/module | ||||||
| golang.org/x/mod/semver | golang.org/x/mod/semver | ||||||
| # golang.org/x/net v0.0.0-20200707034311-ab3426394381 | # golang.org/x/net v0.0.0-20200904194848-62affa334b73 | ||||||
| ## explicit | ## explicit | ||||||
| golang.org/x/net/context | golang.org/x/net/context | ||||||
| golang.org/x/net/context/ctxhttp | golang.org/x/net/context/ctxhttp | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue