blob: e0242a2c8a1b8987347aedfd5b2b7cba9227b812 [file] [log] [blame]
// Copyright 2013 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.
//modify 2013-2014 visualfc
package gopresent
import (
"fmt"
"html/template"
"io"
"os"
"path/filepath"
"github.com/visualfc/gotools/command"
"golang.org/x/tools/present"
)
var Command = &command.Command{
Run: runPresent,
UsageLine: "gopresent",
Short: "golang present util",
Long: `golang present util`,
}
var presentVerifyOnly bool
var presentInput string
var presentStdout bool
var presentOutput string
func init() {
Command.Flag.BoolVar(&presentVerifyOnly, "v", false, "verify present only")
Command.Flag.BoolVar(&presentStdout, "stdout", false, "output use std output")
Command.Flag.StringVar(&presentInput, "i", "", "input golang present file")
Command.Flag.StringVar(&presentOutput, "o", "", "output html file name")
}
func runPresent(cmd *command.Command, args []string) error {
if presentInput == "" || !isDoc(presentInput) {
cmd.Usage()
return os.ErrInvalid
}
if presentVerifyOnly {
err := VerifyDoc(presentInput)
if err != nil {
fmt.Fprintf(os.Stderr, "present:%s", err)
command.SetExitStatus(3)
command.Exit()
}
return nil
}
w := os.Stdout
if !presentStdout {
if presentOutput == "" {
presentOutput = presentInput + ".html"
}
ext := filepath.Ext(presentOutput)
if ext != ".htm" && ext != ".html" {
presentOutput += ".html"
}
var err error
w, err = os.Create(presentOutput)
if err != nil {
fmt.Fprintf(os.Stderr, "present:%s", err)
command.SetExitStatus(3)
command.Exit()
}
}
err := RenderDoc(w, presentInput)
if err != nil {
fmt.Fprintf(os.Stderr, "present:%s", err)
command.SetExitStatus(3)
command.Exit()
}
return nil
}
var extensions = map[string]string{
".slide": "slides.tmpl",
".article": "article.tmpl",
}
var extensions_tmpl = map[string]string{
".slide": slides_tmpl,
".article": article_tmpl,
}
func isDoc(path string) bool {
_, ok := extensions[filepath.Ext(path)]
return ok
}
func VerifyDoc(docFile string) error {
doc, err := parse(docFile, 0)
if err != nil {
return err
}
dir := filepath.Dir(docFile)
return verify_doc(dir, doc)
}
// renderDoc reads the present file, builds its template representation,
// and executes the template, sending output to w.
func renderDoc(w io.Writer, base, docFile string) error {
// Read the input and build the doc structure.
doc, err := parse(docFile, 0)
if err != nil {
return err
}
// Find which template should be executed.
ext := filepath.Ext(docFile)
contentTmpl, ok := extensions[ext]
if !ok {
return fmt.Errorf("no template for extension %v", ext)
}
// Locate the template file.
actionTmpl := filepath.Join(base, "templates/action.tmpl")
contentTmpl = filepath.Join(base, "templates", contentTmpl)
// Read and parse the input.
tmpl := present.Template()
tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
if _, err := tmpl.ParseFiles(actionTmpl, contentTmpl); err != nil {
return err
}
// Execute the template.
return doc.Render(w, tmpl)
}
func RenderDoc(w io.Writer, docFile string) error {
// Read the input and build the doc structure.
doc, err := parse(docFile, 0)
if err != nil {
return err
}
// Find which template should be executed.
ext := filepath.Ext(docFile)
contentTmpl, ok := extensions_tmpl[ext]
if !ok {
return fmt.Errorf("no template for extension %v", ext)
}
// Locate the template file.
actionTmpl := action_tmpl //filepath.Join(base, "templates/action.tmpl")
// Read and parse the input.
tmpl := present.Template()
tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
if tmpl, err = tmpl.New("action").Parse(actionTmpl); err != nil {
return err
}
if tmpl, err = tmpl.New("content").Parse(contentTmpl); err != nil {
return err
}
// Execute the template.
return doc.Render(w, tmpl)
}
func parse(name string, mode present.ParseMode) (*present.Doc, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return present.Parse(f, name, 0)
}
func playable(c present.Code) bool {
return present.PlayEnabled && c.Play
}
func isSkipURL(url string) bool {
if filepath.HasPrefix(url, "http://") {
return true
}
if filepath.HasPrefix(url, "https://") {
return true
}
return false
}
func verify_path(root string, url string) error {
if isSkipURL(url) {
return nil
}
path := url
if !filepath.IsAbs(url) {
path = filepath.Join(root, path)
}
_, err := os.Stat(path)
if err != nil {
return err
}
return nil
}
func verify_doc(root string, doc *present.Doc) error {
for _, section := range doc.Sections {
for _, elem := range section.Elem {
switch i := elem.(type) {
case present.Image:
if err := verify_path(root, i.URL); err != nil {
return fmt.Errorf("! .image %s not exist", i.URL)
}
}
}
}
return nil
}
var action_tmpl = `
{/*
This is the action template.
It determines how the formatting actions are rendered.
*/}
{{define "section"}}
<h{{len .Number}} id="TOC_{{.FormattedNumber}}">{{.FormattedNumber}} {{.Title}}</h{{len .Number}}>
{{range .Elem}}{{elem $.Template .}}{{end}}
{{end}}
{{define "list"}}
<ul>
{{range .Bullet}}
<li>{{style .}}</li>
{{end}}
</ul>
{{end}}
{{define "text"}}
{{if .Pre}}
<div class="code"><pre>{{range .Lines}}{{.}}{{end}}</pre></div>
{{else}}
<p>
{{range $i, $l := .Lines}}{{if $i}}{{template "newline"}}
{{end}}{{style $l}}{{end}}
</p>
{{end}}
{{end}}
{{define "code"}}
<div class="code{{if playable .}} playground{{end}}" contenteditable="true" spellcheck="false">{{.Text}}</div>
{{end}}
{{define "image"}}
<div class="image">
<img src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}}>
</div>
{{end}}
{{define "iframe"}}
<iframe src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}}></iframe>
{{end}}
{{define "link"}}<p class="link"><a href="{{.URL}}" target="_blank">{{style .Label}}</a></p>{{end}}
{{define "html"}}{{.HTML}}{{end}}
`
var article_tmpl = `
{/* This is the article template. It defines how articles are formatted. */}
{{define "root"}}
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
<link type="text/css" rel="stylesheet" href="static/article.css">
<meta charset='utf-8'>
</head>
<body>
<div id="topbar" class="wide">
<div class="container">
<div id="heading">{{.Title}}
{{with .Subtitle}}{{.}}{{end}}
</div>
</div>
</div>
<div id="page" class="wide">
<div class="container">
{{with .Sections}}
<div id="toc">
{{template "TOC" .}}
</div>
{{end}}
{{range .Sections}}
{{elem $.Template .}}
{{end}}{{/* of Section block */}}
<h2>Authors</h2>
{{range .Authors}}
<div class="author">
{{range .Elem}}{{elem $.Template .}}{{end}}
</div>
{{end}}
</div>
</div>
<script src='/play.js'></script>
</body>
</html>
{{end}}
{{define "TOC"}}
<ul>
{{range .}}
<li><a href="#TOC_{{.FormattedNumber}}">{{.Title}}</a></li>
{{with .Sections}}{{template "TOC" .}}{{end}}
{{end}}
</ul>
{{end}}
{{define "newline"}}
{{/* No automatic line break. Paragraphs are free-form. */}}
{{end}}
`
var slides_tmpl = `
{/* This is the slide template. It defines how presentations are formatted. */}
{{define "root"}}
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
<meta charset='utf-8'>
<script src='static/slides.js'></script>
</head>
<body style='display: none'>
<section class='slides layout-widescreen'>
<article>
<h1>{{.Title}}</h1>
{{with .Subtitle}}<h3>{{.}}</h3>{{end}}
{{if not .Time.IsZero}}<h3>{{.Time.Format "2 January 2006"}}</h3>{{end}}
{{range .Authors}}
<div class="presenter">
{{range .TextElem}}{{elem $.Template .}}{{end}}
</div>
{{end}}
</article>
{{range $i, $s := .Sections}}
<!-- start of slide {{$s.Number}} -->
<article>
{{if $s.Elem}}
<h3>{{$s.Title}}</h3>
{{range $s.Elem}}{{elem $.Template .}}{{end}}
{{else}}
<h2>{{$s.Title}}</h2>
{{end}}
</article>
<!-- end of slide {{$i}} -->
{{end}}{{/* of Slide block */}}
<article>
<h3>Thank you</h1>
{{range .Authors}}
<div class="presenter">
{{range .Elem}}{{elem $.Template .}}{{end}}
</div>
{{end}}
</article>
</body>
{{if .PlayEnabled}}
<script src='/play.js'></script>
{{end}}
</html>
{{end}}
{{define "newline"}}
<br>
{{end}}
`