package main
import (
"bytes"
"container/list"
"flag"
"github.com/russross/blackfriday"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"text/template"
)
type Section struct {
docsText []byte
codeText []byte
DocsHTML []byte
CodeHTML []byte
}
type TemplateSection struct {
DocsHTML string
CodeHTML string
Index int
}
type Language struct {
name string
symbol string
commentMatcher *regexp.Regexp
dividerText string
dividerHTML *regexp.Regexp
}
type TemplateData struct {
Title string
Sections []*TemplateSection
Sources []string
Multiple bool
}
var languages map[string]*Language
var sources []string
var packageLocation string
const highlightStart = "<div class=\"highlight\"><pre>"
const highlightEnd = "</pre></div>"
func generateDocumentation(source string, wg *sync.WaitGroup) {
code, err := ioutil.ReadFile(source)
if err != nil {
log.Panic(err)
}
sections := parse(source, code)
highlight(source, sections)
generateHTML(source, sections)
wg.Done()
}
func parse(source string, code []byte) *list.List {
lines := bytes.Split(code, []byte("\n"))
sections := new(list.List)
sections.Init()
language := getLanguage(source)
var hasCode bool
var codeText = new(bytes.Buffer)
var docsText = new(bytes.Buffer)
save := func(docs, code []byte) {
docsCopy, codeCopy := make([]byte, len(docs)), make([]byte, len(code))
copy(docsCopy, docs)
copy(codeCopy, code)
sections.PushBack(&Section{docsCopy, codeCopy, nil, nil})
}
for _, line := range lines {
if language.commentMatcher.Match(line) {
if hasCode {
save(docsText.Bytes(), codeText.Bytes())
hasCode = false
codeText.Reset()
docsText.Reset()
}
docsText.Write(language.commentMatcher.ReplaceAll(line, nil))
docsText.WriteString("\n")
} else {
hasCode = true
codeText.Write(line)
codeText.WriteString("\n")
}
}
save(docsText.Bytes(), codeText.Bytes())
return sections
}
func highlight(source string, sections *list.List) {
language := getLanguage(source)
pygments := exec.Command("pygmentize", "-l", language.name, "-f", "html", "-O", "encoding=utf-8")
pygmentsInput, _ := pygments.StdinPipe()
pygmentsOutput, _ := pygments.StdoutPipe()
pygments.Start()
for e := sections.Front(); e != nil; e = e.Next() {
pygmentsInput.Write(e.Value.(*Section).codeText)
if e.Next() != nil {
io.WriteString(pygmentsInput, language.dividerText)
}
}
pygmentsInput.Close()
buf := new(bytes.Buffer)
io.Copy(buf, pygmentsOutput)
output := buf.Bytes()
output = bytes.Replace(output, []byte(highlightStart), nil, -1)
output = bytes.Replace(output, []byte(highlightEnd), nil, -1)
for e := sections.Front(); e != nil; e = e.Next() {
index := language.dividerHTML.FindIndex(output)
if index == nil {
index = []int{len(output), len(output)}
}
fragment := output[0:index[0]]
output = output[index[1]:]
e.Value.(*Section).CodeHTML = bytes.Join([][]byte{[]byte(highlightStart), []byte(highlightEnd)}, fragment)
e.Value.(*Section).DocsHTML = blackfriday.MarkdownCommon(e.Value.(*Section).docsText)
}
}
func destination(source string) string {
base := filepath.Base(source)
return "docs/" + base[0:strings.LastIndex(base, filepath.Ext(base))] + ".html"
}
func generateHTML(source string, sections *list.List) {
title := filepath.Base(source)
dest := destination(source)
sectionsArray := make([]*TemplateSection, sections.Len())
for e, i := sections.Front(), 0; e != nil; e, i = e.Next(), i+1 {
var sec = e.Value.(*Section)
docsBuf := bytes.NewBuffer(sec.DocsHTML)
codeBuf := bytes.NewBuffer(sec.CodeHTML)
sectionsArray[i] = &TemplateSection{docsBuf.String(), codeBuf.String(), i + 1}
}
html := goccoTemplate(TemplateData{title, sectionsArray, sources, len(sources) > 1})
log.Println("gocco: ", source, " -> ", dest)
ioutil.WriteFile(dest, html, 0644)
}
func goccoTemplate(data TemplateData) []byte {
t, err := template.New("gocco").Funcs(
template.FuncMap{
"base": filepath.Base,
"destination": destination,
}).Parse(HTML)
if err != nil {
panic(err)
}
buf := new(bytes.Buffer)
err = t.Execute(buf, data)
if err != nil {
panic(err)
}
return buf.Bytes()
}
func getLanguage(source string) *Language {
return languages[filepath.Ext(source)]
}
func ensureDirectory(name string) {
os.MkdirAll(name, 0755)
}
func setupLanguages() {
languages = make(map[string]*Language)
languages[".go"] = &Language{"go", "//", nil, "", nil}
}
func setup() {
setupLanguages()
for _, lang := range languages {
lang.commentMatcher, _ = regexp.Compile("^\\s*" + lang.symbol + "\\s?")
lang.dividerText = "\n" + lang.symbol + "DIVIDER\n"
lang.dividerHTML, _ = regexp.Compile("\\n*<span class=\"c1?\">" + lang.symbol + "DIVIDER<\\/span>\\n*")
}
}
func main() {
setup()
flag.Parse()
sources = flag.Args()
sort.Strings(sources)
if flag.NArg() <= 0 {
return
}
ensureDirectory("docs")
ioutil.WriteFile("docs/gocco.css", bytes.NewBufferString(Css).Bytes(), 0755)
wg := new(sync.WaitGroup)
wg.Add(flag.NArg())
for _, arg := range flag.Args() {
go generateDocumentation(arg, wg)
}
wg.Wait()
}