summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/tools/present/parse.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/tools/present/parse.go')
-rw-r--r--vendor/golang.org/x/tools/present/parse.go559
1 files changed, 559 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/present/parse.go b/vendor/golang.org/x/tools/present/parse.go
new file mode 100644
index 0000000..d7289db
--- /dev/null
+++ b/vendor/golang.org/x/tools/present/parse.go
@@ -0,0 +1,559 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package present
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "html/template"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+ "unicode"
+ "unicode/utf8"
+)
+
+var (
+ parsers = make(map[string]ParseFunc)
+ funcs = template.FuncMap{}
+)
+
+// Template returns an empty template with the action functions in its FuncMap.
+func Template() *template.Template {
+ return template.New("").Funcs(funcs)
+}
+
+// Render renders the doc to the given writer using the provided template.
+func (d *Doc) Render(w io.Writer, t *template.Template) error {
+ data := struct {
+ *Doc
+ Template *template.Template
+ PlayEnabled bool
+ NotesEnabled bool
+ }{d, t, PlayEnabled, NotesEnabled}
+ return t.ExecuteTemplate(w, "root", data)
+}
+
+// Render renders the section to the given writer using the provided template.
+func (s *Section) Render(w io.Writer, t *template.Template) error {
+ data := struct {
+ *Section
+ Template *template.Template
+ PlayEnabled bool
+ }{s, t, PlayEnabled}
+ return t.ExecuteTemplate(w, "section", data)
+}
+
+type ParseFunc func(ctx *Context, fileName string, lineNumber int, inputLine string) (Elem, error)
+
+// Register binds the named action, which does not begin with a period, to the
+// specified parser to be invoked when the name, with a period, appears in the
+// present input text.
+func Register(name string, parser ParseFunc) {
+ if len(name) == 0 || name[0] == ';' {
+ panic("bad name in Register: " + name)
+ }
+ parsers["."+name] = parser
+}
+
+// Doc represents an entire document.
+type Doc struct {
+ Title string
+ Subtitle string
+ Time time.Time
+ Authors []Author
+ TitleNotes []string
+ Sections []Section
+ Tags []string
+}
+
+// Author represents the person who wrote and/or is presenting the document.
+type Author struct {
+ Elem []Elem
+}
+
+// TextElem returns the first text elements of the author details.
+// This is used to display the author' name, job title, and company
+// without the contact details.
+func (p *Author) TextElem() (elems []Elem) {
+ for _, el := range p.Elem {
+ if _, ok := el.(Text); !ok {
+ break
+ }
+ elems = append(elems, el)
+ }
+ return
+}
+
+// Section represents a section of a document (such as a presentation slide)
+// comprising a title and a list of elements.
+type Section struct {
+ Number []int
+ Title string
+ Elem []Elem
+ Notes []string
+ Classes []string
+ Styles []string
+}
+
+// HTMLAttributes for the section
+func (s Section) HTMLAttributes() template.HTMLAttr {
+ if len(s.Classes) == 0 && len(s.Styles) == 0 {
+ return ""
+ }
+
+ var class string
+ if len(s.Classes) > 0 {
+ class = fmt.Sprintf(`class=%q`, strings.Join(s.Classes, " "))
+ }
+ var style string
+ if len(s.Styles) > 0 {
+ style = fmt.Sprintf(`style=%q`, strings.Join(s.Styles, " "))
+ }
+ return template.HTMLAttr(strings.Join([]string{class, style}, " "))
+}
+
+// Sections contained within the section.
+func (s Section) Sections() (sections []Section) {
+ for _, e := range s.Elem {
+ if section, ok := e.(Section); ok {
+ sections = append(sections, section)
+ }
+ }
+ return
+}
+
+// Level returns the level of the given section.
+// The document title is level 1, main section 2, etc.
+func (s Section) Level() int {
+ return len(s.Number) + 1
+}
+
+// FormattedNumber returns a string containing the concatenation of the
+// numbers identifying a Section.
+func (s Section) FormattedNumber() string {
+ b := &bytes.Buffer{}
+ for _, n := range s.Number {
+ fmt.Fprintf(b, "%v.", n)
+ }
+ return b.String()
+}
+
+func (s Section) TemplateName() string { return "section" }
+
+// Elem defines the interface for a present element. That is, something that
+// can provide the name of the template used to render the element.
+type Elem interface {
+ TemplateName() string
+}
+
+// renderElem implements the elem template function, used to render
+// sub-templates.
+func renderElem(t *template.Template, e Elem) (template.HTML, error) {
+ var data interface{} = e
+ if s, ok := e.(Section); ok {
+ data = struct {
+ Section
+ Template *template.Template
+ }{s, t}
+ }
+ return execTemplate(t, e.TemplateName(), data)
+}
+
+func init() {
+ funcs["elem"] = renderElem
+}
+
+// execTemplate is a helper to execute a template and return the output as a
+// template.HTML value.
+func execTemplate(t *template.Template, name string, data interface{}) (template.HTML, error) {
+ b := new(bytes.Buffer)
+ err := t.ExecuteTemplate(b, name, data)
+ if err != nil {
+ return "", err
+ }
+ return template.HTML(b.String()), nil
+}
+
+// Text represents an optionally preformatted paragraph.
+type Text struct {
+ Lines []string
+ Pre bool
+}
+
+func (t Text) TemplateName() string { return "text" }
+
+// List represents a bulleted list.
+type List struct {
+ Bullet []string
+}
+
+func (l List) TemplateName() string { return "list" }
+
+// Lines is a helper for parsing line-based input.
+type Lines struct {
+ line int // 0 indexed, so has 1-indexed number of last line returned
+ text []string
+}
+
+func readLines(r io.Reader) (*Lines, error) {
+ var lines []string
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ lines = append(lines, s.Text())
+ }
+ if err := s.Err(); err != nil {
+ return nil, err
+ }
+ return &Lines{0, lines}, nil
+}
+
+func (l *Lines) next() (text string, ok bool) {
+ for {
+ current := l.line
+ l.line++
+ if current >= len(l.text) {
+ return "", false
+ }
+ text = l.text[current]
+ // Lines starting with # are comments.
+ if len(text) == 0 || text[0] != '#' {
+ ok = true
+ break
+ }
+ }
+ return
+}
+
+func (l *Lines) back() {
+ l.line--
+}
+
+func (l *Lines) nextNonEmpty() (text string, ok bool) {
+ for {
+ text, ok = l.next()
+ if !ok {
+ return
+ }
+ if len(text) > 0 {
+ break
+ }
+ }
+ return
+}
+
+// A Context specifies the supporting context for parsing a presentation.
+type Context struct {
+ // ReadFile reads the file named by filename and returns the contents.
+ ReadFile func(filename string) ([]byte, error)
+}
+
+// ParseMode represents flags for the Parse function.
+type ParseMode int
+
+const (
+ // If set, parse only the title and subtitle.
+ TitlesOnly ParseMode = 1
+)
+
+// Parse parses a document from r.
+func (ctx *Context) Parse(r io.Reader, name string, mode ParseMode) (*Doc, error) {
+ doc := new(Doc)
+ lines, err := readLines(r)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := lines.line; i < len(lines.text); i++ {
+ if strings.HasPrefix(lines.text[i], "*") {
+ break
+ }
+
+ if isSpeakerNote(lines.text[i]) {
+ doc.TitleNotes = append(doc.TitleNotes, lines.text[i][2:])
+ }
+ }
+
+ err = parseHeader(doc, lines)
+ if err != nil {
+ return nil, err
+ }
+ if mode&TitlesOnly != 0 {
+ return doc, nil
+ }
+
+ // Authors
+ if doc.Authors, err = parseAuthors(lines); err != nil {
+ return nil, err
+ }
+ // Sections
+ if doc.Sections, err = parseSections(ctx, name, lines, []int{}); err != nil {
+ return nil, err
+ }
+ return doc, nil
+}
+
+// Parse parses a document from r. Parse reads assets used by the presentation
+// from the file system using ioutil.ReadFile.
+func Parse(r io.Reader, name string, mode ParseMode) (*Doc, error) {
+ ctx := Context{ReadFile: ioutil.ReadFile}
+ return ctx.Parse(r, name, mode)
+}
+
+// isHeading matches any section heading.
+var isHeading = regexp.MustCompile(`^\*+ `)
+
+// lesserHeading returns true if text is a heading of a lesser or equal level
+// than that denoted by prefix.
+func lesserHeading(text, prefix string) bool {
+ return isHeading.MatchString(text) && !strings.HasPrefix(text, prefix+"*")
+}
+
+// parseSections parses Sections from lines for the section level indicated by
+// number (a nil number indicates the top level).
+func parseSections(ctx *Context, name string, lines *Lines, number []int) ([]Section, error) {
+ var sections []Section
+ for i := 1; ; i++ {
+ // Next non-empty line is title.
+ text, ok := lines.nextNonEmpty()
+ for ok && text == "" {
+ text, ok = lines.next()
+ }
+ if !ok {
+ break
+ }
+ prefix := strings.Repeat("*", len(number)+1)
+ if !strings.HasPrefix(text, prefix+" ") {
+ lines.back()
+ break
+ }
+ section := Section{
+ Number: append(append([]int{}, number...), i),
+ Title: text[len(prefix)+1:],
+ }
+ text, ok = lines.nextNonEmpty()
+ for ok && !lesserHeading(text, prefix) {
+ var e Elem
+ r, _ := utf8.DecodeRuneInString(text)
+ switch {
+ case unicode.IsSpace(r):
+ i := strings.IndexFunc(text, func(r rune) bool {
+ return !unicode.IsSpace(r)
+ })
+ if i < 0 {
+ break
+ }
+ indent := text[:i]
+ var s []string
+ for ok && (strings.HasPrefix(text, indent) || text == "") {
+ if text != "" {
+ text = text[i:]
+ }
+ s = append(s, text)
+ text, ok = lines.next()
+ }
+ lines.back()
+ pre := strings.Join(s, "\n")
+ pre = strings.Replace(pre, "\t", " ", -1) // browsers treat tabs badly
+ pre = strings.TrimRightFunc(pre, unicode.IsSpace)
+ e = Text{Lines: []string{pre}, Pre: true}
+ case strings.HasPrefix(text, "- "):
+ var b []string
+ for ok && strings.HasPrefix(text, "- ") {
+ b = append(b, text[2:])
+ text, ok = lines.next()
+ }
+ lines.back()
+ e = List{Bullet: b}
+ case isSpeakerNote(text):
+ section.Notes = append(section.Notes, text[2:])
+ case strings.HasPrefix(text, prefix+"* "):
+ lines.back()
+ subsecs, err := parseSections(ctx, name, lines, section.Number)
+ if err != nil {
+ return nil, err
+ }
+ for _, ss := range subsecs {
+ section.Elem = append(section.Elem, ss)
+ }
+ case strings.HasPrefix(text, "."):
+ args := strings.Fields(text)
+ if args[0] == ".background" {
+ section.Classes = append(section.Classes, "background")
+ section.Styles = append(section.Styles, "background-image: url('"+args[1]+"')")
+ break
+ }
+ parser := parsers[args[0]]
+ if parser == nil {
+ return nil, fmt.Errorf("%s:%d: unknown command %q\n", name, lines.line, text)
+ }
+ t, err := parser(ctx, name, lines.line, text)
+ if err != nil {
+ return nil, err
+ }
+ e = t
+ default:
+ var l []string
+ for ok && strings.TrimSpace(text) != "" {
+ if text[0] == '.' { // Command breaks text block.
+ lines.back()
+ break
+ }
+ if strings.HasPrefix(text, `\.`) { // Backslash escapes initial period.
+ text = text[1:]
+ }
+ l = append(l, text)
+ text, ok = lines.next()
+ }
+ if len(l) > 0 {
+ e = Text{Lines: l}
+ }
+ }
+ if e != nil {
+ section.Elem = append(section.Elem, e)
+ }
+ text, ok = lines.nextNonEmpty()
+ }
+ if isHeading.MatchString(text) {
+ lines.back()
+ }
+ sections = append(sections, section)
+ }
+ return sections, nil
+}
+
+func parseHeader(doc *Doc, lines *Lines) error {
+ var ok bool
+ // First non-empty line starts header.
+ doc.Title, ok = lines.nextNonEmpty()
+ if !ok {
+ return errors.New("unexpected EOF; expected title")
+ }
+ for {
+ text, ok := lines.next()
+ if !ok {
+ return errors.New("unexpected EOF")
+ }
+ if text == "" {
+ break
+ }
+ if isSpeakerNote(text) {
+ continue
+ }
+ const tagPrefix = "Tags:"
+ if strings.HasPrefix(text, tagPrefix) {
+ tags := strings.Split(text[len(tagPrefix):], ",")
+ for i := range tags {
+ tags[i] = strings.TrimSpace(tags[i])
+ }
+ doc.Tags = append(doc.Tags, tags...)
+ } else if t, ok := parseTime(text); ok {
+ doc.Time = t
+ } else if doc.Subtitle == "" {
+ doc.Subtitle = text
+ } else {
+ return fmt.Errorf("unexpected header line: %q", text)
+ }
+ }
+ return nil
+}
+
+func parseAuthors(lines *Lines) (authors []Author, err error) {
+ // This grammar demarcates authors with blanks.
+
+ // Skip blank lines.
+ if _, ok := lines.nextNonEmpty(); !ok {
+ return nil, errors.New("unexpected EOF")
+ }
+ lines.back()
+
+ var a *Author
+ for {
+ text, ok := lines.next()
+ if !ok {
+ return nil, errors.New("unexpected EOF")
+ }
+
+ // If we find a section heading, we're done.
+ if strings.HasPrefix(text, "* ") {
+ lines.back()
+ break
+ }
+
+ if isSpeakerNote(text) {
+ continue
+ }
+
+ // If we encounter a blank we're done with this author.
+ if a != nil && len(text) == 0 {
+ authors = append(authors, *a)
+ a = nil
+ continue
+ }
+ if a == nil {
+ a = new(Author)
+ }
+
+ // Parse the line. Those that
+ // - begin with @ are twitter names,
+ // - contain slashes are links, or
+ // - contain an @ symbol are an email address.
+ // The rest is just text.
+ var el Elem
+ switch {
+ case strings.HasPrefix(text, "@"):
+ el = parseURL("http://twitter.com/" + text[1:])
+ case strings.Contains(text, ":"):
+ el = parseURL(text)
+ case strings.Contains(text, "@"):
+ el = parseURL("mailto:" + text)
+ }
+ if l, ok := el.(Link); ok {
+ l.Label = text
+ el = l
+ }
+ if el == nil {
+ el = Text{Lines: []string{text}}
+ }
+ a.Elem = append(a.Elem, el)
+ }
+ if a != nil {
+ authors = append(authors, *a)
+ }
+ return authors, nil
+}
+
+func parseURL(text string) Elem {
+ u, err := url.Parse(text)
+ if err != nil {
+ log.Printf("Parse(%q): %v", text, err)
+ return nil
+ }
+ return Link{URL: u}
+}
+
+func parseTime(text string) (t time.Time, ok bool) {
+ t, err := time.Parse("15:04 2 Jan 2006", text)
+ if err == nil {
+ return t, true
+ }
+ t, err = time.Parse("2 Jan 2006", text)
+ if err == nil {
+ // at 11am UTC it is the same date everywhere
+ t = t.Add(time.Hour * 11)
+ return t, true
+ }
+ return time.Time{}, false
+}
+
+func isSpeakerNote(s string) bool {
+ return strings.HasPrefix(s, ": ")
+}