package text
import (
"bytes"
"io"
"regexp"
"unicode/utf8"
"github.com/yuin/goldmark/util"
)
const invalidValue = -1
const EOF = byte (0xff )
type Reader interface {
io .RuneReader
Source () []byte
ResetPosition ()
Peek () byte
PeekLine () ([]byte , Segment )
PrecendingCharacter () rune
Value (Segment ) []byte
LineOffset () int
Position () (int , Segment )
SetPosition (int , Segment )
SetPadding (int )
Advance (int )
AdvanceAndSetPadding (int , int )
AdvanceLine ()
SkipSpaces () (Segment , int , bool )
SkipBlankLines () (Segment , int , bool )
Match (reg *regexp .Regexp ) bool
FindSubMatch (reg *regexp .Regexp ) [][]byte
FindClosure (opener, closer byte , options FindClosureOptions ) (*Segments , bool )
}
type FindClosureOptions struct {
CodeSpan bool
Nesting bool
Newline bool
Advance bool
}
type reader struct {
source []byte
sourceLength int
line int
peekedLine []byte
pos Segment
head int
lineOffset int
}
func NewReader (source []byte ) Reader {
r := &reader {
source : source ,
sourceLength : len (source ),
}
r .ResetPosition ()
return r
}
func (r *reader ) FindClosure (opener , closer byte , options FindClosureOptions ) (*Segments , bool ) {
return findClosureReader (r , opener , closer , options )
}
func (r *reader ) ResetPosition () {
r .line = -1
r .head = 0
r .lineOffset = -1
r .AdvanceLine ()
}
func (r *reader ) Source () []byte {
return r .source
}
func (r *reader ) Value (seg Segment ) []byte {
return seg .Value (r .source )
}
func (r *reader ) Peek () byte {
if r .pos .Start >= 0 && r .pos .Start < r .sourceLength {
if r .pos .Padding != 0 {
return space [0 ]
}
return r .source [r .pos .Start ]
}
return EOF
}
func (r *reader ) PeekLine () ([]byte , Segment ) {
if r .pos .Start >= 0 && r .pos .Start < r .sourceLength {
if r .peekedLine == nil {
r .peekedLine = r .pos .Value (r .Source ())
}
return r .peekedLine , r .pos
}
return nil , r .pos
}
func (r *reader ) ReadRune () (rune , int , error ) {
return readRuneReader (r )
}
func (r *reader ) LineOffset () int {
if r .lineOffset < 0 {
v := 0
for i := r .head ; i < r .pos .Start ; i ++ {
if r .source [i ] == '\t' {
v += util .TabWidth (v )
} else {
v ++
}
}
r .lineOffset = v - r .pos .Padding
}
return r .lineOffset
}
func (r *reader ) PrecendingCharacter () rune {
if r .pos .Start <= 0 {
if r .pos .Padding != 0 {
return rune (' ' )
}
return rune ('\n' )
}
i := r .pos .Start - 1
for ; i >= 0 ; i -- {
if utf8 .RuneStart (r .source [i ]) {
break
}
}
rn , _ := utf8 .DecodeRune (r .source [i :])
return rn
}
func (r *reader ) Advance (n int ) {
r .lineOffset = -1
if n < len (r .peekedLine ) && r .pos .Padding == 0 {
r .pos .Start += n
r .peekedLine = nil
return
}
r .peekedLine = nil
l := r .sourceLength
for ; n > 0 && r .pos .Start < l ; n -- {
if r .pos .Padding != 0 {
r .pos .Padding --
continue
}
if r .source [r .pos .Start ] == '\n' {
r .AdvanceLine ()
continue
}
r .pos .Start ++
}
}
func (r *reader ) AdvanceAndSetPadding (n , padding int ) {
r .Advance (n )
if padding > r .pos .Padding {
r .SetPadding (padding )
}
}
func (r *reader ) AdvanceLine () {
r .lineOffset = -1
r .peekedLine = nil
r .pos .Start = r .pos .Stop
r .head = r .pos .Start
if r .pos .Start < 0 {
return
}
r .pos .Stop = r .sourceLength
for i := r .pos .Start ; i < r .sourceLength ; i ++ {
c := r .source [i ]
if c == '\n' {
r .pos .Stop = i + 1
break
}
}
r .line ++
r .pos .Padding = 0
}
func (r *reader ) Position () (int , Segment ) {
return r .line , r .pos
}
func (r *reader ) SetPosition (line int , pos Segment ) {
r .lineOffset = -1
r .line = line
r .pos = pos
}
func (r *reader ) SetPadding (v int ) {
r .pos .Padding = v
}
func (r *reader ) SkipSpaces () (Segment , int , bool ) {
return skipSpacesReader (r )
}
func (r *reader ) SkipBlankLines () (Segment , int , bool ) {
return skipBlankLinesReader (r )
}
func (r *reader ) Match (reg *regexp .Regexp ) bool {
return matchReader (r , reg )
}
func (r *reader ) FindSubMatch (reg *regexp .Regexp ) [][]byte {
return findSubMatchReader (r , reg )
}
type BlockReader interface {
Reader
Reset (segment *Segments )
}
type blockReader struct {
source []byte
segments *Segments
segmentsLength int
line int
pos Segment
head int
last int
lineOffset int
}
func NewBlockReader (source []byte , segments *Segments ) BlockReader {
r := &blockReader {
source : source ,
}
if segments != nil {
r .Reset (segments )
}
return r
}
func (r *blockReader ) FindClosure (opener , closer byte , options FindClosureOptions ) (*Segments , bool ) {
return findClosureReader (r , opener , closer , options )
}
func (r *blockReader ) ResetPosition () {
r .line = -1
r .head = 0
r .last = 0
r .lineOffset = -1
r .pos .Start = -1
r .pos .Stop = -1
r .pos .Padding = 0
if r .segmentsLength > 0 {
last := r .segments .At (r .segmentsLength - 1 )
r .last = last .Stop
}
r .AdvanceLine ()
}
func (r *blockReader ) Reset (segments *Segments ) {
r .segments = segments
r .segmentsLength = segments .Len ()
r .ResetPosition ()
}
func (r *blockReader ) Source () []byte {
return r .source
}
func (r *blockReader ) Value (seg Segment ) []byte {
line := r .segmentsLength - 1
ret := make ([]byte , 0 , seg .Stop -seg .Start +1 )
for ; line >= 0 ; line -- {
if seg .Start >= r .segments .At (line ).Start {
break
}
}
i := seg .Start
for ; line < r .segmentsLength ; line ++ {
s := r .segments .At (line )
if i < 0 {
i = s .Start
}
ret = s .ConcatPadding (ret )
for ; i < seg .Stop && i < s .Stop ; i ++ {
ret = append (ret , r .source [i ])
}
i = -1
if s .Stop > seg .Stop {
break
}
}
return ret
}
func (r *blockReader ) ReadRune () (rune , int , error ) {
return readRuneReader (r )
}
func (r *blockReader ) PrecendingCharacter () rune {
if r .pos .Padding != 0 {
return rune (' ' )
}
if r .segments .Len () < 1 {
return rune ('\n' )
}
firstSegment := r .segments .At (0 )
if r .line == 0 && r .pos .Start <= firstSegment .Start {
return rune ('\n' )
}
l := len (r .source )
i := r .pos .Start - 1
for ; i < l && i >= 0 ; i -- {
if utf8 .RuneStart (r .source [i ]) {
break
}
}
if i < 0 || i >= l {
return rune ('\n' )
}
rn , _ := utf8 .DecodeRune (r .source [i :])
return rn
}
func (r *blockReader ) LineOffset () int {
if r .lineOffset < 0 {
v := 0
for i := r .head ; i < r .pos .Start ; i ++ {
if r .source [i ] == '\t' {
v += util .TabWidth (v )
} else {
v ++
}
}
r .lineOffset = v - r .pos .Padding
}
return r .lineOffset
}
func (r *blockReader ) Peek () byte {
if r .line < r .segmentsLength && r .pos .Start >= 0 && r .pos .Start < r .last {
if r .pos .Padding != 0 {
return space [0 ]
}
return r .source [r .pos .Start ]
}
return EOF
}
func (r *blockReader ) PeekLine () ([]byte , Segment ) {
if r .line < r .segmentsLength && r .pos .Start >= 0 && r .pos .Start < r .last {
return r .pos .Value (r .source ), r .pos
}
return nil , r .pos
}
func (r *blockReader ) Advance (n int ) {
r .lineOffset = -1
if n < r .pos .Stop -r .pos .Start && r .pos .Padding == 0 {
r .pos .Start += n
return
}
for ; n > 0 ; n -- {
if r .pos .Padding != 0 {
r .pos .Padding --
continue
}
if r .pos .Start >= r .pos .Stop -1 && r .pos .Stop < r .last {
r .AdvanceLine ()
continue
}
r .pos .Start ++
}
}
func (r *blockReader ) AdvanceAndSetPadding (n , padding int ) {
r .Advance (n )
if padding > r .pos .Padding {
r .SetPadding (padding )
}
}
func (r *blockReader ) AdvanceLine () {
r .SetPosition (r .line +1 , NewSegment (invalidValue , invalidValue ))
r .head = r .pos .Start
}
func (r *blockReader ) Position () (int , Segment ) {
return r .line , r .pos
}
func (r *blockReader ) SetPosition (line int , pos Segment ) {
r .lineOffset = -1
r .line = line
if pos .Start == invalidValue {
if r .line < r .segmentsLength {
s := r .segments .At (line )
r .head = s .Start
r .pos = s
}
} else {
r .pos = pos
if r .line < r .segmentsLength {
s := r .segments .At (line )
r .head = s .Start
}
}
}
func (r *blockReader ) SetPadding (v int ) {
r .lineOffset = -1
r .pos .Padding = v
}
func (r *blockReader ) SkipSpaces () (Segment , int , bool ) {
return skipSpacesReader (r )
}
func (r *blockReader ) SkipBlankLines () (Segment , int , bool ) {
return skipBlankLinesReader (r )
}
func (r *blockReader ) Match (reg *regexp .Regexp ) bool {
return matchReader (r , reg )
}
func (r *blockReader ) FindSubMatch (reg *regexp .Regexp ) [][]byte {
return findSubMatchReader (r , reg )
}
func skipBlankLinesReader(r Reader ) (Segment , int , bool ) {
lines := 0
for {
line , seg := r .PeekLine ()
if line == nil {
return seg , lines , false
}
if util .IsBlank (line ) {
lines ++
r .AdvanceLine ()
} else {
return seg , lines , true
}
}
}
func skipSpacesReader(r Reader ) (Segment , int , bool ) {
chars := 0
for {
line , segment := r .PeekLine ()
if line == nil {
return segment , chars , false
}
for i , c := range line {
if util .IsSpace (c ) {
chars ++
r .Advance (1 )
continue
}
return segment .WithStart (segment .Start + i + 1 ), chars , true
}
}
}
func matchReader(r Reader , reg *regexp .Regexp ) bool {
oldline , oldseg := r .Position ()
match := reg .FindReaderSubmatchIndex (r )
r .SetPosition (oldline , oldseg )
if match == nil {
return false
}
r .Advance (match [1 ] - match [0 ])
return true
}
func findSubMatchReader(r Reader , reg *regexp .Regexp ) [][]byte {
oldLine , oldSeg := r .Position ()
match := reg .FindReaderSubmatchIndex (r )
r .SetPosition (oldLine , oldSeg )
if match == nil {
return nil
}
var bb bytes .Buffer
bb .Grow (match [1 ] - match [0 ])
for i := 0 ; i < match [1 ]; {
r , size , _ := readRuneReader (r )
i += size
bb .WriteRune (r )
}
bs := bb .Bytes ()
var result [][]byte
for i := 0 ; i < len (match ); i += 2 {
if match [i ] < 0 {
result = append (result , []byte {})
continue
}
result = append (result , bs [match [i ]:match [i +1 ]])
}
r .SetPosition (oldLine , oldSeg )
r .Advance (match [1 ] - match [0 ])
return result
}
func readRuneReader(r Reader ) (rune , int , error ) {
line , _ := r .PeekLine ()
if line == nil {
return 0 , 0 , io .EOF
}
rn , size := utf8 .DecodeRune (line )
if rn == utf8 .RuneError {
return 0 , 0 , io .EOF
}
r .Advance (size )
return rn , size , nil
}
func findClosureReader(r Reader , opener , closer byte , opts FindClosureOptions ) (*Segments , bool ) {
opened := 1
codeSpanOpener := 0
closed := false
orgline , orgpos := r .Position ()
var ret *Segments
for {
bs , seg := r .PeekLine ()
if bs == nil {
goto end
}
i := 0
for i < len (bs ) {
c := bs [i ]
if opts .CodeSpan && codeSpanOpener != 0 && c == '`' {
codeSpanCloser := 0
for ; i < len (bs ); i ++ {
if bs [i ] == '`' {
codeSpanCloser ++
} else {
i --
break
}
}
if codeSpanCloser == codeSpanOpener {
codeSpanOpener = 0
}
} else if codeSpanOpener == 0 && c == '\\' && i < len (bs )-1 && util .IsPunct (bs [i +1 ]) {
i += 2
continue
} else if opts .CodeSpan && codeSpanOpener == 0 && c == '`' {
for ; i < len (bs ); i ++ {
if bs [i ] == '`' {
codeSpanOpener ++
} else {
i --
break
}
}
} else if (opts .CodeSpan && codeSpanOpener == 0 ) || !opts .CodeSpan {
if c == closer {
opened --
if opened == 0 {
if ret == nil {
ret = NewSegments ()
}
ret .Append (seg .WithStop (seg .Start + i ))
r .Advance (i + 1 )
closed = true
goto end
}
} else if c == opener {
if !opts .Nesting {
goto end
}
opened ++
}
}
i ++
}
if !opts .Newline {
goto end
}
r .AdvanceLine ()
if ret == nil {
ret = NewSegments ()
}
ret .Append (seg )
}
end :
if !opts .Advance {
r .SetPosition (orgline , orgpos )
}
if closed {
return ret , true
}
return nil , false
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .