GoPLS Viewer

Home|gopls/present/parse.go
1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package present
6
7import (
8    "bufio"
9    "bytes"
10    "errors"
11    "fmt"
12    "html/template"
13    "io"
14    "io/ioutil"
15    "log"
16    "net/url"
17    "regexp"
18    "strings"
19    "time"
20    "unicode"
21    "unicode/utf8"
22
23    "github.com/yuin/goldmark"
24    "github.com/yuin/goldmark/ast"
25    "github.com/yuin/goldmark/renderer/html"
26    "github.com/yuin/goldmark/text"
27)
28
29var (
30    parsers = make(map[string]ParseFunc)
31    funcs   = template.FuncMap{}
32)
33
34// Template returns an empty template with the action functions in its FuncMap.
35func Template() *template.Template {
36    return template.New("").Funcs(funcs)
37}
38
39// Render renders the doc to the given writer using the provided template.
40func (d *DocRender(w io.Writert *template.Templateerror {
41    data := struct {
42        *Doc
43        Template     *template.Template
44        PlayEnabled  bool
45        NotesEnabled bool
46    }{dtPlayEnabledNotesEnabled}
47    return t.ExecuteTemplate(w"root"data)
48}
49
50// Render renders the section to the given writer using the provided template.
51func (s *SectionRender(w io.Writert *template.Templateerror {
52    data := struct {
53        *Section
54        Template    *template.Template
55        PlayEnabled bool
56    }{stPlayEnabled}
57    return t.ExecuteTemplate(w"section"data)
58}
59
60type ParseFunc func(ctx *ContextfileName stringlineNumber intinputLine string) (Elemerror)
61
62// Register binds the named action, which does not begin with a period, to the
63// specified parser to be invoked when the name, with a period, appears in the
64// present input text.
65func Register(name stringparser ParseFunc) {
66    if len(name) == 0 || name[0] == ';' {
67        panic("bad name in Register: " + name)
68    }
69    parsers["."+name] = parser
70}
71
72// Doc represents an entire document.
73type Doc struct {
74    Title      string
75    Subtitle   string
76    Summary    string
77    Time       time.Time
78    Authors    []Author
79    TitleNotes []string
80    Sections   []Section
81    Tags       []string
82    OldURL     []string
83}
84
85// Author represents the person who wrote and/or is presenting the document.
86type Author struct {
87    Elem []Elem
88}
89
90// TextElem returns the first text elements of the author details.
91// This is used to display the author' name, job title, and company
92// without the contact details.
93func (p *AuthorTextElem() (elems []Elem) {
94    for _el := range p.Elem {
95        if _ok := el.(Text); !ok {
96            break
97        }
98        elems = append(elemsel)
99    }
100    return
101}
102
103// Section represents a section of a document (such as a presentation slide)
104// comprising a title and a list of elements.
105type Section struct {
106    Number  []int
107    Title   string
108    ID      string // HTML anchor ID
109    Elem    []Elem
110    Notes   []string
111    Classes []string
112    Styles  []string
113}
114
115// HTMLAttributes for the section
116func (s SectionHTMLAttributes() template.HTMLAttr {
117    if len(s.Classes) == 0 && len(s.Styles) == 0 {
118        return ""
119    }
120
121    var class string
122    if len(s.Classes) > 0 {
123        class = fmt.Sprintf(`class=%q`strings.Join(s.Classes" "))
124    }
125    var style string
126    if len(s.Styles) > 0 {
127        style = fmt.Sprintf(`style=%q`strings.Join(s.Styles" "))
128    }
129    return template.HTMLAttr(strings.Join([]string{classstyle}, " "))
130}
131
132// Sections contained within the section.
133func (s SectionSections() (sections []Section) {
134    for _e := range s.Elem {
135        if sectionok := e.(Section); ok {
136            sections = append(sectionssection)
137        }
138    }
139    return
140}
141
142// Level returns the level of the given section.
143// The document title is level 1, main section 2, etc.
144func (s SectionLevel() int {
145    return len(s.Number) + 1
146}
147
148// FormattedNumber returns a string containing the concatenation of the
149// numbers identifying a Section.
150func (s SectionFormattedNumber() string {
151    b := &bytes.Buffer{}
152    for _n := range s.Number {
153        fmt.Fprintf(b"%v."n)
154    }
155    return b.String()
156}
157
158func (s SectionTemplateName() string { return "section" }
159
160// Elem defines the interface for a present element. That is, something that
161// can provide the name of the template used to render the element.
162type Elem interface {
163    TemplateName() string
164}
165
166// renderElem implements the elem template function, used to render
167// sub-templates.
168func renderElem(t *template.Templatee Elem) (template.HTMLerror) {
169    var data interface{} = e
170    if sok := e.(Section); ok {
171        data = struct {
172            Section
173            Template *template.Template
174        }{st}
175    }
176    return execTemplate(te.TemplateName(), data)
177}
178
179// pageNum derives a page number from a section.
180func pageNum(s Sectionoffset intint {
181    if len(s.Number) == 0 {
182        return offset
183    }
184    return s.Number[0] + offset
185}
186
187func init() {
188    funcs["elem"] = renderElem
189    funcs["pagenum"] = pageNum
190}
191
192// execTemplate is a helper to execute a template and return the output as a
193// template.HTML value.
194func execTemplate(t *template.Templatename stringdata interface{}) (template.HTMLerror) {
195    b := new(bytes.Buffer)
196    err := t.ExecuteTemplate(bnamedata)
197    if err != nil {
198        return ""err
199    }
200    return template.HTML(b.String()), nil
201}
202
203// Text represents an optionally preformatted paragraph.
204type Text struct {
205    Lines []string
206    Pre   bool
207    Raw   string // original text, for Pre==true
208}
209
210func (t TextTemplateName() string { return "text" }
211
212// List represents a bulleted list.
213type List struct {
214    Bullet []string
215}
216
217func (l ListTemplateName() string { return "list" }
218
219// Lines is a helper for parsing line-based input.
220type Lines struct {
221    line    int // 0 indexed, so has 1-indexed number of last line returned
222    text    []string
223    comment string
224}
225
226func readLines(r io.Reader) (*Lineserror) {
227    var lines []string
228    s := bufio.NewScanner(r)
229    for s.Scan() {
230        lines = append(liness.Text())
231    }
232    if err := s.Err(); err != nil {
233        return nilerr
234    }
235    return &Lines{0lines"#"}, nil
236}
237
238func (l *Linesnext() (text stringok bool) {
239    for {
240        current := l.line
241        l.line++
242        if current >= len(l.text) {
243            return ""false
244        }
245        text = l.text[current]
246        // Lines starting with l.comment are comments.
247        if l.comment == "" || !strings.HasPrefix(textl.comment) {
248            ok = true
249            break
250        }
251    }
252    return
253}
254
255func (l *Linesback() {
256    l.line--
257}
258
259func (l *LinesnextNonEmpty() (text stringok bool) {
260    for {
261        textok = l.next()
262        if !ok {
263            return
264        }
265        if len(text) > 0 {
266            break
267        }
268    }
269    return
270}
271
272// A Context specifies the supporting context for parsing a presentation.
273type Context struct {
274    // ReadFile reads the file named by filename and returns the contents.
275    ReadFile func(filename string) ([]byteerror)
276}
277
278// ParseMode represents flags for the Parse function.
279type ParseMode int
280
281const (
282    // If set, parse only the title and subtitle.
283    TitlesOnly ParseMode = 1
284)
285
286// Parse parses a document from r.
287func (ctx *ContextParse(r io.Readername stringmode ParseMode) (*Docerror) {
288    doc := new(Doc)
289    lineserr := readLines(r)
290    if err != nil {
291        return nilerr
292    }
293
294    // Detect Markdown-enabled vs legacy present file.
295    // Markdown-enabled files have a title line beginning with "# "
296    // (like preprocessed C files of yore).
297    isMarkdown := false
298    for i := lines.linei < len(lines.text); i++ {
299        line := lines.text[i]
300        if line == "" {
301            continue
302        }
303        isMarkdown = strings.HasPrefix(line"# ")
304        break
305    }
306
307    sectionPrefix := "*"
308    if isMarkdown {
309        sectionPrefix = "##"
310        lines.comment = "//"
311    }
312
313    for i := lines.linei < len(lines.text); i++ {
314        if strings.HasPrefix(lines.text[i], sectionPrefix) {
315            break
316        }
317
318        if isSpeakerNote(lines.text[i]) {
319            doc.TitleNotes = append(doc.TitleNotestrimSpeakerNote(lines.text[i]))
320        }
321    }
322
323    err = parseHeader(docisMarkdownlines)
324    if err != nil {
325        return nilerr
326    }
327    if mode&TitlesOnly != 0 {
328        return docnil
329    }
330
331    // Authors
332    if doc.Authorserr = parseAuthors(namesectionPrefixlines); err != nil {
333        return nilerr
334    }
335
336    // Sections
337    if doc.Sectionserr = parseSections(ctxnamesectionPrefixlines, []int{}); err != nil {
338        return nilerr
339    }
340
341    return docnil
342}
343
344// Parse parses a document from r. Parse reads assets used by the presentation
345// from the file system using ioutil.ReadFile.
346func Parse(r io.Readername stringmode ParseMode) (*Docerror) {
347    ctx := Context{ReadFileioutil.ReadFile}
348    return ctx.Parse(rnamemode)
349}
350
351// isHeading matches any section heading.
352var (
353    isHeadingLegacy   = regexp.MustCompile(`^\*+( |$)`)
354    isHeadingMarkdown = regexp.MustCompile(`^\#+( |$)`)
355)
356
357// lesserHeading returns true if text is a heading of a lesser or equal level
358// than that denoted by prefix.
359func lesserHeading(isHeading *regexp.Regexptextprefix stringbool {
360    return isHeading.MatchString(text) && !strings.HasPrefix(textprefix+prefix[:1])
361}
362
363// parseSections parses Sections from lines for the section level indicated by
364// number (a nil number indicates the top level).
365func parseSections(ctx *Contextnameprefix stringlines *Linesnumber []int) ([]Sectionerror) {
366    isMarkdown := prefix[0] == '#'
367    isHeading := isHeadingLegacy
368    if isMarkdown {
369        isHeading = isHeadingMarkdown
370    }
371    var sections []Section
372    for i := 1; ; i++ {
373        // Next non-empty line is title.
374        textok := lines.nextNonEmpty()
375        for ok && text == "" {
376            textok = lines.next()
377        }
378        if !ok {
379            break
380        }
381        if text != prefix && !strings.HasPrefix(textprefix+" ") {
382            lines.back()
383            break
384        }
385        // Markdown sections can end in {#id} to set the HTML anchor for the section.
386        // This is nicer than the default #TOC_1_2-style anchor.
387        title := strings.TrimSpace(text[len(prefix):])
388        id := ""
389        if isMarkdown && strings.HasSuffix(title"}") {
390            j := strings.LastIndex(title"{#")
391            if j >= 0 {
392                id = title[j+2 : len(title)-1]
393                title = strings.TrimSpace(title[:j])
394            }
395        }
396        section := Section{
397            Numberappend(append([]int{}, number...), i),
398            Title:  title,
399            ID:     id,
400        }
401        textok = lines.nextNonEmpty()
402        for ok && !lesserHeading(isHeadingtextprefix) {
403            var e Elem
404            r_ := utf8.DecodeRuneInString(text)
405            switch {
406            case !isMarkdown && unicode.IsSpace(r):
407                i := strings.IndexFunc(text, func(r runebool {
408                    return !unicode.IsSpace(r)
409                })
410                if i < 0 {
411                    break
412                }
413                indent := text[:i]
414                var s []string
415                for ok && (strings.HasPrefix(textindent) || text == "") {
416                    if text != "" {
417                        text = text[i:]
418                    }
419                    s = append(stext)
420                    textok = lines.next()
421                }
422                lines.back()
423                pre := strings.Join(s"\n")
424                raw := pre
425                pre = strings.Replace(pre"\t""    ", -1// browsers treat tabs badly
426                pre = strings.TrimRightFunc(preunicode.IsSpace)
427                e = Text{Lines: []string{pre}, PretrueRawraw}
428            case !isMarkdown && strings.HasPrefix(text"- "):
429                var b []string
430                for {
431                    if strings.HasPrefix(text"- ") {
432                        b = append(btext[2:])
433                    } else if len(b) > 0 && strings.HasPrefix(text" ") {
434                        b[len(b)-1] += "\n" + strings.TrimSpace(text)
435                    } else {
436                        break
437                    }
438                    if textok = lines.next(); !ok {
439                        break
440                    }
441                }
442                lines.back()
443                e = List{Bulletb}
444            case isSpeakerNote(text):
445                section.Notes = append(section.NotestrimSpeakerNote(text))
446            case strings.HasPrefix(textprefix+prefix[:1]+" ") || text == prefix+prefix[:1]:
447                lines.back()
448                subsecserr := parseSections(ctxnameprefix+prefix[:1], linessection.Number)
449                if err != nil {
450                    return nilerr
451                }
452                for _ss := range subsecs {
453                    section.Elem = append(section.Elemss)
454                }
455            case strings.HasPrefix(textprefix+prefix[:1]):
456                return nilfmt.Errorf("%s:%d: badly nested section inside %s: %s"namelines.lineprefixtext)
457            case strings.HasPrefix(text"."):
458                args := strings.Fields(text)
459                if args[0] == ".background" {
460                    section.Classes = append(section.Classes"background")
461                    section.Styles = append(section.Styles"background-image: url('"+args[1]+"')")
462                    break
463                }
464                parser := parsers[args[0]]
465                if parser == nil {
466                    return nilfmt.Errorf("%s:%d: unknown command %q"namelines.linetext)
467                }
468                terr := parser(ctxnamelines.linetext)
469                if err != nil {
470                    return nilerr
471                }
472                e = t
473
474            case isMarkdown:
475                // Collect Markdown lines, including blank lines and indented text.
476                var block []string
477                endLineendBlock := lines.line-1, -1 // end is last non-empty line
478                for ok {
479                    trim := strings.TrimSpace(text)
480                    if trim != "" {
481                        // Command breaks text block.
482                        // Section heading breaks text block in markdown.
483                        if text[0] == '.' || text[0] == '#' || isSpeakerNote(text) {
484                            break
485                        }
486                        if strings.HasPrefix(text`\.`) { // Backslash escapes initial period.
487                            text = text[1:]
488                        }
489                        endLineendBlock = lines.linelen(block)
490                    }
491                    block = append(blocktext)
492                    textok = lines.next()
493                }
494                block = block[:endBlock+1]
495                lines.line = endLine + 1
496                if len(block) == 0 {
497                    break
498                }
499
500                // Replace all leading tabs with 4 spaces,
501                // which render better in code blocks.
502                // CommonMark defines that for parsing the structure of the file
503                // a tab is equivalent to 4 spaces, so this change won't
504                // affect the later parsing at all.
505                // An alternative would be to apply this to code blocks after parsing,
506                // at the same time that we update <a> targets, but that turns out
507                // to be quite difficult to modify in the AST.
508                for iline := range block {
509                    if len(line) > 0 && line[0] == '\t' {
510                        short := strings.TrimLeft(line"\t")
511                        line = strings.Repeat("    "len(line)-len(short)) + short
512                        block[i] = line
513                    }
514                }
515                htmlerr := renderMarkdown([]byte(strings.Join(block"\n")))
516                if err != nil {
517                    return nilerr
518                }
519                e = HTML{HTMLhtml}
520
521            default:
522                // Collect text lines.
523                var block []string
524                for ok && strings.TrimSpace(text) != "" {
525                    // Command breaks text block.
526                    // Section heading breaks text block in markdown.
527                    if text[0] == '.' || isSpeakerNote(text) {
528                        lines.back()
529                        break
530                    }
531                    if strings.HasPrefix(text`\.`) { // Backslash escapes initial period.
532                        text = text[1:]
533                    }
534                    block = append(blocktext)
535                    textok = lines.next()
536                }
537                if len(block) == 0 {
538                    break
539                }
540                e = Text{Linesblock}
541            }
542            if e != nil {
543                section.Elem = append(section.Eleme)
544            }
545            textok = lines.nextNonEmpty()
546        }
547        if isHeading.MatchString(text) {
548            lines.back()
549        }
550        sections = append(sectionssection)
551    }
552
553    if len(sections) == 0 {
554        return nilfmt.Errorf("%s:%d: unexpected line: %s"namelines.line+1lines.text[lines.line])
555    }
556    return sectionsnil
557}
558
559func parseHeader(doc *DocisMarkdown boollines *Lineserror {
560    var ok bool
561    // First non-empty line starts header.
562    doc.Titleok = lines.nextNonEmpty()
563    if !ok {
564        return errors.New("unexpected EOF; expected title")
565    }
566    if isMarkdown {
567        doc.Title = strings.TrimSpace(strings.TrimPrefix(doc.Title"#"))
568    }
569
570    for {
571        textok := lines.next()
572        if !ok {
573            return errors.New("unexpected EOF")
574        }
575        if text == "" {
576            break
577        }
578        if isSpeakerNote(text) {
579            continue
580        }
581        if strings.HasPrefix(text"Tags:") {
582            tags := strings.Split(text[len("Tags:"):], ",")
583            for i := range tags {
584                tags[i] = strings.TrimSpace(tags[i])
585            }
586            doc.Tags = append(doc.Tagstags...)
587        } else if strings.HasPrefix(text"Summary:") {
588            doc.Summary = strings.TrimSpace(text[len("Summary:"):])
589        } else if strings.HasPrefix(text"OldURL:") {
590            doc.OldURL = append(doc.OldURLstrings.TrimSpace(text[len("OldURL:"):]))
591        } else if tok := parseTime(text); ok {
592            doc.Time = t
593        } else if doc.Subtitle == "" {
594            doc.Subtitle = text
595        } else {
596            return fmt.Errorf("unexpected header line: %q"text)
597        }
598    }
599    return nil
600}
601
602func parseAuthors(namesectionPrefix stringlines *Lines) (authors []Authorerr error) {
603    // This grammar demarcates authors with blanks.
604
605    // Skip blank lines.
606    if _ok := lines.nextNonEmpty(); !ok {
607        return nilerrors.New("unexpected EOF")
608    }
609    lines.back()
610
611    var a *Author
612    for {
613        textok := lines.next()
614        if !ok {
615            return nilerrors.New("unexpected EOF")
616        }
617
618        // If we find a section heading, we're done.
619        if strings.HasPrefix(textsectionPrefix) {
620            lines.back()
621            break
622        }
623
624        if isSpeakerNote(text) {
625            continue
626        }
627
628        // If we encounter a blank we're done with this author.
629        if a != nil && len(text) == 0 {
630            authors = append(authors, *a)
631            a = nil
632            continue
633        }
634        if a == nil {
635            a = new(Author)
636        }
637
638        // Parse the line. Those that
639        // - begin with @ are twitter names,
640        // - contain slashes are links, or
641        // - contain an @ symbol are an email address.
642        // The rest is just text.
643        var el Elem
644        switch {
645        case strings.HasPrefix(text"@"):
646            el = parseAuthorURL(name"http://twitter.com/"+text[1:])
647        case strings.Contains(text":"):
648            el = parseAuthorURL(nametext)
649        case strings.Contains(text"@"):
650            el = parseAuthorURL(name"mailto:"+text)
651        }
652        if lok := el.(Link); ok {
653            l.Label = text
654            el = l
655        }
656        if el == nil {
657            el = Text{Lines: []string{text}}
658        }
659        a.Elem = append(a.Elemel)
660    }
661    if a != nil {
662        authors = append(authors, *a)
663    }
664    return authorsnil
665}
666
667func parseAuthorURL(nametext stringElem {
668    uerr := url.Parse(text)
669    if err != nil {
670        log.Printf("parsing %s author block: invalid URL %q: %v"nametexterr)
671        return nil
672    }
673    return Link{URLu}
674}
675
676func parseTime(text string) (t time.Timeok bool) {
677    terr := time.Parse("15:04 2 Jan 2006"text)
678    if err == nil {
679        return ttrue
680    }
681    terr = time.Parse("2 Jan 2006"text)
682    if err == nil {
683        // at 11am UTC it is the same date everywhere
684        t = t.Add(time.Hour * 11)
685        return ttrue
686    }
687    return time.Time{}, false
688}
689
690func isSpeakerNote(s stringbool {
691    return strings.HasPrefix(s": ") || s == ":"
692}
693
694func trimSpeakerNote(s stringstring {
695    if s == ":" {
696        return ""
697    }
698    return strings.TrimPrefix(s": ")
699}
700
701func renderMarkdown(input []byte) (template.HTMLerror) {
702    md := goldmark.New(goldmark.WithRendererOptions(html.WithUnsafe()))
703    reader := text.NewReader(input)
704    doc := md.Parser().Parse(reader)
705    fixupMarkdown(doc)
706    var b strings.Builder
707    if err := md.Renderer().Render(&binputdoc); err != nil {
708        return ""err
709    }
710    return template.HTML(b.String()), nil
711}
712
713func fixupMarkdown(n ast.Node) {
714    ast.Walk(n, func(n ast.Nodeentering bool) (ast.WalkStatuserror) {
715        if entering {
716            switch n := n.(type) {
717            case *ast.Link:
718                n.SetAttributeString("target", []byte("_blank"))
719                // https://developers.google.com/web/tools/lighthouse/audits/noopener
720                n.SetAttributeString("rel", []byte("noopener"))
721            }
722        }
723        return ast.WalkContinuenil
724    })
725}
726
MembersX
List
renderMarkdown.md
Template
Text.Raw
Text.TemplateName.t
parseSections.BlockStmt.BlockStmt._
parseSections.BlockStmt.BlockStmt.BlockStmt.t
parseAuthors.name
trimSpeakerNote
Doc.Sections
Lines.comment
parseHeader.BlockStmt.t
Section.Elem
List.TemplateName.l
parseSections.BlockStmt.BlockStmt.r
Lines.next.l
Context.Parse.ctx
Context.Parse.doc
parseSections.prefix
Author.TextElem
parseSections.BlockStmt.title
Lines.next
Doc.Subtitle
Section.Styles
Doc.Tags
List.Bullet
Context.Parse
ioutil
Section.Sections.RangeStmt_3271.e
Text
renderMarkdown.reader
io
Author.Elem
Section.Classes
ParseFunc
parseHeader.isMarkdown
parseSections.BlockStmt.BlockStmt.e
parseHeader.BlockStmt.text
Text.TemplateName
Context.Parse.isMarkdown
Parse.r
parseAuthors.sectionPrefix
renderElem.e
readLines.r
fixupMarkdown
parseSections.BlockStmt.BlockStmt.BlockStmt.err
renderMarkdown
Author
Context.ReadFile
Parse
parseAuthors.err
Elem
Lines.nextNonEmpty.text
parseSections.BlockStmt.BlockStmt.BlockStmt.s
Section.HTMLAttributes.s
readLines.s
execTemplate
execTemplate.data
Context.Parse.err
parseSections.BlockStmt.BlockStmt.BlockStmt.RangeStmt_13239.line
Section.Sections
Section.TemplateName
parseAuthors
parseSections.ctx
parseSections.BlockStmt.BlockStmt.BlockStmt.subsecs
parseAuthorURL.u
renderElem.t
Text.Lines
parseSections.BlockStmt.BlockStmt.BlockStmt.block
parseHeader.doc
Doc.Title
renderElem
parseHeader
Section.Render.s
Section.Render.w
fixupMarkdown.n
Section.FormattedNumber.RangeStmt_3731.n
execTemplate.t
unicode
Doc.Render.data
parseSections.lines
parseAuthorURL.name
Section.HTMLAttributes.style
Lines.next.BlockStmt.current
parseSections.name
Section
Section.FormattedNumber.b
TitlesOnly
Parse.ctx
lesserHeading.isHeading
parseSections.BlockStmt.BlockStmt.BlockStmt.html
Section.ID
Section.HTMLAttributes.class
parseHeader.lines
parseHeader.BlockStmt.BlockStmt.tags
text
execTemplate.b
parseSections.BlockStmt.BlockStmt.BlockStmt.i
readLines
Lines.nextNonEmpty.l
parseSections.BlockStmt.BlockStmt.BlockStmt.BlockStmt.trim
renderMarkdown.input
Doc.Summary
List.TemplateName
Lines
renderMarkdown.b
renderMarkdown.err
Section.Level
parseSections.BlockStmt.BlockStmt.BlockStmt.b
Section.Sections.sections
Section.Level.s
goldmark
Section.Render
Lines.next.ok
Lines.back.l
Doc.OldURL
isSpeakerNote
Doc.Time
Section.Title
parseSections.BlockStmt.BlockStmt.BlockStmt.args
trimSpeakerNote.s
Doc.Render.w
Section.Sections.s
parseAuthors.BlockStmt.el
Context.Parse.mode
parseAuthors.BlockStmt.ok
Context.Parse.i
parseSections.sections
parseSections.BlockStmt.ok
Lines.line
Doc.Render.t
Section.Number
parseSections.BlockStmt.text
Context.Parse.lines
Section.HTMLAttributes
Section.TemplateName.s
Section.FormattedNumber.s
Text.Pre
Lines.nextNonEmpty
parseSections.number
parseAuthors.lines
Register.parser
parseTime.t
parseTime.err
Context.Parse.r
parseSections.BlockStmt.section
parseSections.BlockStmt.BlockStmt.BlockStmt.RangeStmt_11251.ss
parseSections.BlockStmt.BlockStmt.BlockStmt.RangeStmt_13239.i
parseTime.ok
readLines.err
ParseMode
lesserHeading.text
parseAuthorURL.text
ast
Author.TextElem.p
execTemplate.err
readLines.lines
Context
Doc.Render.d
Section.Render.data
Context.Parse.name
parseSections.BlockStmt.id
parseTime
isSpeakerNote.s
lesserHeading.prefix
lesserHeading
parseSections.BlockStmt.BlockStmt.j
parseAuthors._
pageNum.s
parseAuthors.ok
parseAuthors.BlockStmt.text
parseSections.BlockStmt.BlockStmt.BlockStmt.RangeStmt_13239.BlockStmt.BlockStmt.short
html
parseHeader.ok
Lines.back
Context.Parse.sectionPrefix
parseSections.BlockStmt.BlockStmt.BlockStmt.raw
Doc.Render
Lines.next.text
Register
parseTime.text
Author.TextElem.RangeStmt_2338.el
Section.Render.t
Parse.name
pageNum
pageNum.offset
Parse.mode
Section.Notes
parseAuthorURL
parseSections.i
parseSections.BlockStmt.BlockStmt.BlockStmt.pre
time
Author.TextElem.elems
parseHeader.BlockStmt.ok
Section.FormattedNumber
Lines.text
Register.name
parseAuthors.a
parseAuthorURL.err
renderMarkdown.doc
execTemplate.name
Doc.Authors
Lines.nextNonEmpty.ok
parseSections
parseSections.isHeading
parseAuthors.authors
Doc
Doc.TitleNotes
parseHeader.BlockStmt.BlockStmt.RangeStmt_15051.i
Members
X