add vendor dependencies
This commit is contained in:
parent
d9e24cd897
commit
9cbb20aea2
|
@ -0,0 +1,15 @@
|
||||||
|
c02ca9a983da5807ddf7d796784928f5be4afd09 github.com/GeertJohan/go.rice
|
||||||
|
c02ca9a983da5807ddf7d796784928f5be4afd09 github.com/GeertJohan/go.rice/embedded
|
||||||
|
a00a8beb369cafd88bb7b32f31fc4ff3219c3565 github.com/Xe/gopreload
|
||||||
|
b685d4edebe855f8edbb4e605c0bf74e1e60b0e9 github.com/Xe/jsonfeed
|
||||||
|
f759b797c0ff6b2c514202198fe5e8ba90094c14 github.com/Xe/ln
|
||||||
|
a5fe2436ffcb3236e175e5149162b41cd28bd27d github.com/daaku/go.zipexe
|
||||||
|
62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/agent
|
||||||
|
62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/internal
|
||||||
|
62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/signal
|
||||||
|
441264de03a8117ed530ae8e049d8f601a33a099 github.com/gorilla/feeds
|
||||||
|
c2c54e542fb797ad986b31721e1baedf214ca413 github.com/kardianos/osext
|
||||||
|
ff09b135c25aae272398c51a07235b90a75aa4f0 github.com/pkg/errors
|
||||||
|
0ba0f2b6ed7c475a92e4df8641825cb7a11d1fa3 github.com/russross/blackfriday
|
||||||
|
739be213b0a1c496dccaf9e5df1514150c9548e4 github.com/tj/front
|
||||||
|
9f9df34309c04878acc86042b16630b0f696e1de gopkg.in/yaml.v1
|
|
@ -0,0 +1,138 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/daaku/go.zipexe"
|
||||||
|
"github.com/kardianos/osext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// appendedBox defines an appended box
|
||||||
|
type appendedBox struct {
|
||||||
|
Name string // box name
|
||||||
|
Files map[string]*appendedFile // appended files (*zip.File) by full path
|
||||||
|
}
|
||||||
|
|
||||||
|
type appendedFile struct {
|
||||||
|
zipFile *zip.File
|
||||||
|
dir bool
|
||||||
|
dirInfo *appendedDirInfo
|
||||||
|
children []*appendedFile
|
||||||
|
content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendedBoxes is a public register of appendes boxes
|
||||||
|
var appendedBoxes = make(map[string]*appendedBox)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// find if exec is appended
|
||||||
|
thisFile, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return // not appended or cant find self executable
|
||||||
|
}
|
||||||
|
closer, rd, err := zipexe.OpenCloser(thisFile)
|
||||||
|
if err != nil {
|
||||||
|
return // not appended
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
for _, f := range rd.File {
|
||||||
|
// get box and file name from f.Name
|
||||||
|
fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2)
|
||||||
|
boxName := fileParts[0]
|
||||||
|
var fileName string
|
||||||
|
if len(fileParts) > 1 {
|
||||||
|
fileName = fileParts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// find box or create new one if doesn't exist
|
||||||
|
box := appendedBoxes[boxName]
|
||||||
|
if box == nil {
|
||||||
|
box = &appendedBox{
|
||||||
|
Name: boxName,
|
||||||
|
Files: make(map[string]*appendedFile),
|
||||||
|
}
|
||||||
|
appendedBoxes[boxName] = box
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and add file to box
|
||||||
|
af := &appendedFile{
|
||||||
|
zipFile: f,
|
||||||
|
}
|
||||||
|
if f.Comment == "dir" {
|
||||||
|
af.dir = true
|
||||||
|
af.dirInfo = &appendedDirInfo{
|
||||||
|
name: filepath.Base(af.zipFile.Name),
|
||||||
|
//++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime()
|
||||||
|
time: time.Now(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this is a file, we need it's contents so we can create a bytes.Reader when the file is opened
|
||||||
|
// make a new byteslice
|
||||||
|
af.content = make([]byte, af.zipFile.FileInfo().Size())
|
||||||
|
// ignore reading empty files from zip (empty file still is a valid file to be read though!)
|
||||||
|
if len(af.content) > 0 {
|
||||||
|
// open io.ReadCloser
|
||||||
|
rc, err := af.zipFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
||||||
|
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
||||||
|
log.Printf("error opening appended file %s: %v", af.zipFile.Name, err)
|
||||||
|
} else {
|
||||||
|
_, err = rc.Read(af.content)
|
||||||
|
rc.Close()
|
||||||
|
if err != nil {
|
||||||
|
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
||||||
|
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
||||||
|
log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add appendedFile to box file list
|
||||||
|
box.Files[fileName] = af
|
||||||
|
|
||||||
|
// add to parent dir (if any)
|
||||||
|
dirName := filepath.Dir(fileName)
|
||||||
|
if dirName == "." {
|
||||||
|
dirName = ""
|
||||||
|
}
|
||||||
|
if fileName != "" { // don't make box root dir a child of itself
|
||||||
|
if dir := box.Files[dirName]; dir != nil {
|
||||||
|
dir.children = append(dir.children, af)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements os.FileInfo.
|
||||||
|
// used for Readdir()
|
||||||
|
type appendedDirInfo struct {
|
||||||
|
name string
|
||||||
|
time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adi *appendedDirInfo) Name() string {
|
||||||
|
return adi.name
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) Size() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) Mode() os.FileMode {
|
||||||
|
return os.ModeDir
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) ModTime() time.Time {
|
||||||
|
return adi.time
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) IsDir() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,337 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GeertJohan/go.rice/embedded"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Box abstracts a directory for resources/files.
|
||||||
|
// It can either load files from disk, or from embedded code (when `rice --embed` was ran).
|
||||||
|
type Box struct {
|
||||||
|
name string
|
||||||
|
absolutePath string
|
||||||
|
embed *embedded.EmbeddedBox
|
||||||
|
appendd *appendedBox
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultLocateOrder = []LocateMethod{LocateEmbedded, LocateAppended, LocateFS}
|
||||||
|
|
||||||
|
func findBox(name string, order []LocateMethod) (*Box, error) {
|
||||||
|
b := &Box{name: name}
|
||||||
|
|
||||||
|
// no support for absolute paths since gopath can be different on different machines.
|
||||||
|
// therefore, required box must be located relative to package requiring it.
|
||||||
|
if filepath.IsAbs(name) {
|
||||||
|
return nil, errors.New("given name/path is absolute")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, method := range order {
|
||||||
|
switch method {
|
||||||
|
case LocateEmbedded:
|
||||||
|
if embed := embedded.EmbeddedBoxes[name]; embed != nil {
|
||||||
|
b.embed = embed
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case LocateAppended:
|
||||||
|
appendedBoxName := strings.Replace(name, `/`, `-`, -1)
|
||||||
|
if appendd := appendedBoxes[appendedBoxName]; appendd != nil {
|
||||||
|
b.appendd = appendd
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case LocateFS:
|
||||||
|
// resolve absolute directory path
|
||||||
|
err := b.resolveAbsolutePathFromCaller()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath exists on filesystem
|
||||||
|
info, err := os.Stat(b.absolutePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath is actually a directory
|
||||||
|
if !info.IsDir() {
|
||||||
|
err = errors.New("given name/path is not a directory")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
case LocateWorkingDirectory:
|
||||||
|
// resolve absolute directory path
|
||||||
|
err := b.resolveAbsolutePathFromWorkingDirectory()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath exists on filesystem
|
||||||
|
info, err := os.Stat(b.absolutePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath is actually a directory
|
||||||
|
if !info.IsDir() {
|
||||||
|
err = errors.New("given name/path is not a directory")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("could not locate box %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindBox returns a Box instance for given name.
|
||||||
|
// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root.
|
||||||
|
// When the given name is absolute, it's absolute. derp.
|
||||||
|
// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded).
|
||||||
|
func FindBox(name string) (*Box, error) {
|
||||||
|
return findBox(name, defaultLocateOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFindBox returns a Box instance for given name, like FindBox does.
|
||||||
|
// It does not return an error, instead it panics when an error occurs.
|
||||||
|
func MustFindBox(name string) *Box {
|
||||||
|
box, err := findBox(name, defaultLocateOrder)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return box
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is injected as a mutable function literal so that we can mock it out in
|
||||||
|
// tests and return a fixed test file.
|
||||||
|
var resolveAbsolutePathFromCaller = func(name string, nStackFrames int) (string, error) {
|
||||||
|
_, callingGoFile, _, ok := runtime.Caller(nStackFrames)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("couldn't find caller on stack")
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve to proper path
|
||||||
|
pkgDir := filepath.Dir(callingGoFile)
|
||||||
|
// fix for go cover
|
||||||
|
const coverPath = "_test/_obj_test"
|
||||||
|
if !filepath.IsAbs(pkgDir) {
|
||||||
|
if i := strings.Index(pkgDir, coverPath); i >= 0 {
|
||||||
|
pkgDir = pkgDir[:i] + pkgDir[i+len(coverPath):] // remove coverPath
|
||||||
|
pkgDir = filepath.Join(os.Getenv("GOPATH"), "src", pkgDir) // make absolute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filepath.Join(pkgDir, name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Box) resolveAbsolutePathFromCaller() error {
|
||||||
|
path, err := resolveAbsolutePathFromCaller(b.name, 4)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.absolutePath = path
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Box) resolveAbsolutePathFromWorkingDirectory() error {
|
||||||
|
path, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.absolutePath = filepath.Join(path, b.name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmbedded indicates wether this box was embedded into the application
|
||||||
|
func (b *Box) IsEmbedded() bool {
|
||||||
|
return b.embed != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAppended indicates wether this box was appended to the application
|
||||||
|
func (b *Box) IsAppended() bool {
|
||||||
|
return b.appendd != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns how actual the box is.
|
||||||
|
// When the box is embedded, it's value is saved in the embedding code.
|
||||||
|
// When the box is live, this methods returns time.Now()
|
||||||
|
func (b *Box) Time() time.Time {
|
||||||
|
if b.IsEmbedded() {
|
||||||
|
return b.embed.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
//++ TODO: return time for appended box
|
||||||
|
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens a File from the box
|
||||||
|
// If there is an error, it will be of type *os.PathError.
|
||||||
|
func (b *Box) Open(name string) (*File, error) {
|
||||||
|
if Debug {
|
||||||
|
fmt.Printf("Open(%s)\n", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.IsEmbedded() {
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Box is embedded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim prefix (paths are relative to box)
|
||||||
|
name = strings.TrimLeft(name, "/")
|
||||||
|
if Debug {
|
||||||
|
fmt.Printf("Trying %s\n", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// search for file
|
||||||
|
ef := b.embed.Files[name]
|
||||||
|
if ef == nil {
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Didn't find file in embed")
|
||||||
|
}
|
||||||
|
// file not found, try dir
|
||||||
|
ed := b.embed.Dirs[name]
|
||||||
|
if ed == nil {
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Didn't find dir in embed")
|
||||||
|
}
|
||||||
|
// dir not found, error out
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: name,
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Found dir. Returning virtual dir")
|
||||||
|
}
|
||||||
|
vd := newVirtualDir(ed)
|
||||||
|
return &File{virtualD: vd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// box is embedded
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Found file. Returning virtual file")
|
||||||
|
}
|
||||||
|
vf := newVirtualFile(ef)
|
||||||
|
return &File{virtualF: vf}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.IsAppended() {
|
||||||
|
// trim prefix (paths are relative to box)
|
||||||
|
name = strings.TrimLeft(name, "/")
|
||||||
|
|
||||||
|
// search for file
|
||||||
|
appendedFile := b.appendd.Files[name]
|
||||||
|
if appendedFile == nil {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: name,
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new file
|
||||||
|
f := &File{
|
||||||
|
appendedF: appendedFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this file is a directory, we want to be able to read and seek
|
||||||
|
if !appendedFile.dir {
|
||||||
|
// looks like malformed data in zip, error now
|
||||||
|
if appendedFile.content == nil {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: "name",
|
||||||
|
Err: errors.New("error reading data from zip file"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create new bytes.Reader
|
||||||
|
f.appendedFileReader = bytes.NewReader(appendedFile.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// all done
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform os open
|
||||||
|
if Debug {
|
||||||
|
fmt.Printf("Using os.Open(%s)", filepath.Join(b.absolutePath, name))
|
||||||
|
}
|
||||||
|
file, err := os.Open(filepath.Join(b.absolutePath, name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &File{realF: file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the content of the file with given name as []byte.
|
||||||
|
func (b *Box) Bytes(name string) ([]byte, error) {
|
||||||
|
file, err := b.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBytes returns the content of the file with given name as []byte.
|
||||||
|
// panic's on error.
|
||||||
|
func (b *Box) MustBytes(name string) []byte {
|
||||||
|
bts, err := b.Bytes(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bts
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the content of the file with given name as string.
|
||||||
|
func (b *Box) String(name string) (string, error) {
|
||||||
|
// check if box is embedded, optimized fast path
|
||||||
|
if b.IsEmbedded() {
|
||||||
|
// find file in embed
|
||||||
|
ef := b.embed.Files[name]
|
||||||
|
if ef == nil {
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
}
|
||||||
|
// return as string
|
||||||
|
return ef.Content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bts, err := b.Bytes(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(bts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustString returns the content of the file with given name as string.
|
||||||
|
// panic's on error.
|
||||||
|
func (b *Box) MustString(name string) string {
|
||||||
|
str, err := b.String(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the box
|
||||||
|
func (b *Box) Name() string {
|
||||||
|
return b.name
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
// LocateMethod defines how a box is located.
|
||||||
|
type LocateMethod int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LocateFS = LocateMethod(iota) // Locate on the filesystem according to package path.
|
||||||
|
LocateAppended // Locate boxes appended to the executable.
|
||||||
|
LocateEmbedded // Locate embedded boxes.
|
||||||
|
LocateWorkingDirectory // Locate on the binary working directory
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config allows customizing the box lookup behavior.
|
||||||
|
type Config struct {
|
||||||
|
// LocateOrder defines the priority order that boxes are searched for. By
|
||||||
|
// default, the package global FindBox searches for embedded boxes first,
|
||||||
|
// then appended boxes, and then finally boxes on the filesystem. That
|
||||||
|
// search order may be customized by provided the ordered list here. Leaving
|
||||||
|
// out a particular method will omit that from the search space. For
|
||||||
|
// example, []LocateMethod{LocateEmbedded, LocateAppended} will never search
|
||||||
|
// the filesystem for boxes.
|
||||||
|
LocateOrder []LocateMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindBox searches for boxes using the LocateOrder of the config.
|
||||||
|
func (c *Config) FindBox(boxName string) (*Box, error) {
|
||||||
|
return findBox(boxName, c.LocateOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFindBox searches for boxes using the LocateOrder of the config, like
|
||||||
|
// FindBox does. It does not return an error, instead it panics when an error
|
||||||
|
// occurs.
|
||||||
|
func (c *Config) MustFindBox(boxName string) *Box {
|
||||||
|
box, err := findBox(boxName, c.LocateOrder)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return box
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
// Debug can be set to true to enable debugging.
|
||||||
|
var Debug = false
|
|
@ -0,0 +1,90 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GeertJohan/go.rice/embedded"
|
||||||
|
)
|
||||||
|
|
||||||
|
// re-type to make exported methods invisible to user (godoc)
|
||||||
|
// they're not required for the user
|
||||||
|
// embeddedDirInfo implements os.FileInfo
|
||||||
|
type embeddedDirInfo embedded.EmbeddedDir
|
||||||
|
|
||||||
|
// Name returns the base name of the directory
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Name() string {
|
||||||
|
return ed.Filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size always returns 0
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Size() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns the file mode bits
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Mode() os.FileMode {
|
||||||
|
return os.FileMode(0555 | os.ModeDir) // dr-xr-xr-x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModTime returns the modification time
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) ModTime() time.Time {
|
||||||
|
return ed.DirModTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns the abbreviation for Mode().IsDir() (always true)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) IsDir() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sys returns the underlying data source (always nil)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-type to make exported methods invisible to user (godoc)
|
||||||
|
// they're not required for the user
|
||||||
|
// embeddedFileInfo implements os.FileInfo
|
||||||
|
type embeddedFileInfo embedded.EmbeddedFile
|
||||||
|
|
||||||
|
// Name returns the base name of the file
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Name() string {
|
||||||
|
return ef.Filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the length in bytes for regular files; system-dependent for others
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Size() int64 {
|
||||||
|
return int64(len(ef.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns the file mode bits
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Mode() os.FileMode {
|
||||||
|
return os.FileMode(0555) // r-xr-xr-x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModTime returns the modification time
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) ModTime() time.Time {
|
||||||
|
return ef.FileModTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns the abbreviation for Mode().IsDir() (always false)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) IsDir() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sys returns the underlying data source (always nil)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Package embedded defines embedded data types that are shared between the go.rice package and generated code.
|
||||||
|
package embedded
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EmbedTypeGo = 0
|
||||||
|
EmbedTypeSyso = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmbeddedBox defines an embedded box
|
||||||
|
type EmbeddedBox struct {
|
||||||
|
Name string // box name
|
||||||
|
Time time.Time // embed time
|
||||||
|
EmbedType int // kind of embedding
|
||||||
|
Files map[string]*EmbeddedFile // ALL embedded files by full path
|
||||||
|
Dirs map[string]*EmbeddedDir // ALL embedded dirs by full path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link creates the ChildDirs and ChildFiles links in all EmbeddedDir's
|
||||||
|
func (e *EmbeddedBox) Link() {
|
||||||
|
for path, ed := range e.Dirs {
|
||||||
|
fmt.Println(path)
|
||||||
|
ed.ChildDirs = make([]*EmbeddedDir, 0)
|
||||||
|
ed.ChildFiles = make([]*EmbeddedFile, 0)
|
||||||
|
}
|
||||||
|
for path, ed := range e.Dirs {
|
||||||
|
parentDirpath, _ := filepath.Split(path)
|
||||||
|
if strings.HasSuffix(parentDirpath, "/") {
|
||||||
|
parentDirpath = parentDirpath[:len(parentDirpath)-1]
|
||||||
|
}
|
||||||
|
parentDir := e.Dirs[parentDirpath]
|
||||||
|
if parentDir == nil {
|
||||||
|
panic("parentDir `" + parentDirpath + "` is missing in embedded box")
|
||||||
|
}
|
||||||
|
parentDir.ChildDirs = append(parentDir.ChildDirs, ed)
|
||||||
|
}
|
||||||
|
for path, ef := range e.Files {
|
||||||
|
dirpath, _ := filepath.Split(path)
|
||||||
|
if strings.HasSuffix(dirpath, "/") {
|
||||||
|
dirpath = dirpath[:len(dirpath)-1]
|
||||||
|
}
|
||||||
|
dir := e.Dirs[dirpath]
|
||||||
|
if dir == nil {
|
||||||
|
panic("dir `" + dirpath + "` is missing in embedded box")
|
||||||
|
}
|
||||||
|
dir.ChildFiles = append(dir.ChildFiles, ef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbeddedDir is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
|
||||||
|
type EmbeddedDir struct {
|
||||||
|
Filename string
|
||||||
|
DirModTime time.Time
|
||||||
|
ChildDirs []*EmbeddedDir // direct childs, as returned by virtualDir.Readdir()
|
||||||
|
ChildFiles []*EmbeddedFile // direct childs, as returned by virtualDir.Readdir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbeddedFile is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
|
||||||
|
type EmbeddedFile struct {
|
||||||
|
Filename string // filename
|
||||||
|
FileModTime time.Time
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbeddedBoxes is a public register of embedded boxes
|
||||||
|
var EmbeddedBoxes = make(map[string]*EmbeddedBox)
|
||||||
|
|
||||||
|
// RegisterEmbeddedBox registers an EmbeddedBox
|
||||||
|
func RegisterEmbeddedBox(name string, box *EmbeddedBox) {
|
||||||
|
if _, exists := EmbeddedBoxes[name]; exists {
|
||||||
|
panic(fmt.Sprintf("EmbeddedBox with name `%s` exists already", name))
|
||||||
|
}
|
||||||
|
EmbeddedBoxes[name] = box
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces
|
||||||
|
type File struct {
|
||||||
|
// File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File
|
||||||
|
// TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir
|
||||||
|
|
||||||
|
// real file on disk
|
||||||
|
realF *os.File
|
||||||
|
|
||||||
|
// when embedded (go)
|
||||||
|
virtualF *virtualFile
|
||||||
|
virtualD *virtualDir
|
||||||
|
|
||||||
|
// when appended (zip)
|
||||||
|
appendedF *appendedFile
|
||||||
|
appendedFileReader *bytes.Reader
|
||||||
|
// TODO: is appendedFileReader subject of races? Might need a lock here..
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is like (*os.File).Close()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Close for more information
|
||||||
|
func (f *File) Close() error {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return errors.New("already closed")
|
||||||
|
}
|
||||||
|
f.appendedFileReader = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.close()
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.close()
|
||||||
|
}
|
||||||
|
return f.realF.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat is like (*os.File).Stat()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Stat for more information
|
||||||
|
func (f *File) Stat() (os.FileInfo, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedF.dir {
|
||||||
|
return f.appendedF.dirInfo, nil
|
||||||
|
}
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return nil, errors.New("file is closed")
|
||||||
|
}
|
||||||
|
return f.appendedF.zipFile.FileInfo(), nil
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.stat()
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.stat()
|
||||||
|
}
|
||||||
|
return f.realF.Stat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir is like (*os.File).Readdir()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Readdir for more information
|
||||||
|
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedF.dir {
|
||||||
|
fi := make([]os.FileInfo, 0, len(f.appendedF.children))
|
||||||
|
for _, childAppendedFile := range f.appendedF.children {
|
||||||
|
if childAppendedFile.dir {
|
||||||
|
fi = append(fi, childAppendedFile.dirInfo)
|
||||||
|
} else {
|
||||||
|
fi = append(fi, childAppendedFile.zipFile.FileInfo())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
//++ TODO: is os.ErrInvalid the correct error for Readdir on file?
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.readdir(count)
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.readdir(count)
|
||||||
|
}
|
||||||
|
return f.realF.Readdir(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read is like (*os.File).Read()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Read for more information
|
||||||
|
func (f *File) Read(bts []byte) (int, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: filepath.Base(f.appendedF.zipFile.Name),
|
||||||
|
Err: errors.New("file is closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f.appendedF.dir {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: filepath.Base(f.appendedF.zipFile.Name),
|
||||||
|
Err: errors.New("is a directory"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.appendedFileReader.Read(bts)
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.read(bts)
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.read(bts)
|
||||||
|
}
|
||||||
|
return f.realF.Read(bts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek is like (*os.File).Seek()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Seek for more information
|
||||||
|
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: filepath.Base(f.appendedF.zipFile.Name),
|
||||||
|
Err: errors.New("file is closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.appendedFileReader.Seek(offset, whence)
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.seek(offset, whence)
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.seek(offset, whence)
|
||||||
|
}
|
||||||
|
return f.realF.Seek(offset, whence)
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPBox implements http.FileSystem which allows the use of Box with a http.FileServer.
|
||||||
|
// e.g.: http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
|
||||||
|
type HTTPBox struct {
|
||||||
|
*Box
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPBox creates a new HTTPBox from an existing Box
|
||||||
|
func (b *Box) HTTPBox() *HTTPBox {
|
||||||
|
return &HTTPBox{b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a File using the http.File interface
|
||||||
|
func (hb *HTTPBox) Open(name string) (http.File, error) {
|
||||||
|
return hb.Box.Open(name)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// SortByName allows an array of os.FileInfo objects
|
||||||
|
// to be easily sorted by filename using sort.Sort(SortByName(array))
|
||||||
|
type SortByName []os.FileInfo
|
||||||
|
|
||||||
|
func (f SortByName) Len() int { return len(f) }
|
||||||
|
func (f SortByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
||||||
|
func (f SortByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
|
|
||||||
|
// SortByModified allows an array of os.FileInfo objects
|
||||||
|
// to be easily sorted by modified date using sort.Sort(SortByModified(array))
|
||||||
|
type SortByModified []os.FileInfo
|
||||||
|
|
||||||
|
func (f SortByModified) Len() int { return len(f) }
|
||||||
|
func (f SortByModified) Less(i, j int) bool { return f[i].ModTime().Unix() > f[j].ModTime().Unix() }
|
||||||
|
func (f SortByModified) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
@ -0,0 +1,252 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/GeertJohan/go.rice/embedded"
|
||||||
|
)
|
||||||
|
|
||||||
|
//++ TODO: IDEA: merge virtualFile and virtualDir, this decreases work done by rice.File
|
||||||
|
|
||||||
|
// Error indicating some function is not implemented yet (but available to satisfy an interface)
|
||||||
|
var ErrNotImplemented = errors.New("not implemented yet")
|
||||||
|
|
||||||
|
// virtualFile is a 'stateful' virtual file.
|
||||||
|
// virtualFile wraps an *EmbeddedFile for a call to Box.Open() and virtualizes 'read cursor' (offset) and 'closing'.
|
||||||
|
// virtualFile is only internally visible and should be exposed through rice.File
|
||||||
|
type virtualFile struct {
|
||||||
|
*embedded.EmbeddedFile // the actual embedded file, embedded to obtain methods
|
||||||
|
offset int64 // read position on the virtual file
|
||||||
|
closed bool // closed when true
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new virtualFile for given EmbeddedFile
|
||||||
|
func newVirtualFile(ef *embedded.EmbeddedFile) *virtualFile {
|
||||||
|
vf := &virtualFile{
|
||||||
|
EmbeddedFile: ef,
|
||||||
|
offset: 0,
|
||||||
|
closed: false,
|
||||||
|
}
|
||||||
|
return vf
|
||||||
|
}
|
||||||
|
|
||||||
|
//++ TODO check for nil pointers in all these methods. When so: return os.PathError with Err: os.ErrInvalid
|
||||||
|
|
||||||
|
func (vf *virtualFile) close() error {
|
||||||
|
if vf.closed {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: "close",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("already closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vf.EmbeddedFile = nil
|
||||||
|
vf.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) stat() (os.FileInfo, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "stat",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*embeddedFileInfo)(vf.EmbeddedFile), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "readdir",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO: return proper error for a readdir() call on a file
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) read(bts []byte) (int, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := vf.offset + int64(len(bts))
|
||||||
|
|
||||||
|
if end >= int64(len(vf.Content)) {
|
||||||
|
// end of file, so return what we have + EOF
|
||||||
|
n := copy(bts, vf.Content[vf.offset:])
|
||||||
|
vf.offset = 0
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n := copy(bts, vf.Content[vf.offset:end])
|
||||||
|
vf.offset += int64(n)
|
||||||
|
return n, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) seek(offset int64, whence int) (int64, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var e error
|
||||||
|
|
||||||
|
//++ TODO: check if this is correct implementation for seek
|
||||||
|
switch whence {
|
||||||
|
case os.SEEK_SET:
|
||||||
|
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
||||||
|
vf.offset = offset
|
||||||
|
case os.SEEK_CUR:
|
||||||
|
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
||||||
|
vf.offset += offset
|
||||||
|
case os.SEEK_END:
|
||||||
|
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
||||||
|
vf.offset = int64(len(vf.EmbeddedFile.Content)) - offset
|
||||||
|
}
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vf.Filename,
|
||||||
|
Err: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vf.offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtualDir is a 'stateful' virtual directory.
|
||||||
|
// virtualDir wraps an *EmbeddedDir for a call to Box.Open() and virtualizes 'closing'.
|
||||||
|
// virtualDir is only internally visible and should be exposed through rice.File
|
||||||
|
type virtualDir struct {
|
||||||
|
*embedded.EmbeddedDir
|
||||||
|
offset int // readdir position on the directory
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new virtualDir for given EmbeddedDir
|
||||||
|
func newVirtualDir(ed *embedded.EmbeddedDir) *virtualDir {
|
||||||
|
vd := &virtualDir{
|
||||||
|
EmbeddedDir: ed,
|
||||||
|
offset: 0,
|
||||||
|
closed: false,
|
||||||
|
}
|
||||||
|
return vd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) close() error {
|
||||||
|
//++ TODO: needs sync mutex?
|
||||||
|
if vd.closed {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: "close",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("already closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vd.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) stat() (os.FileInfo, error) {
|
||||||
|
if vd.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "stat",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*embeddedDirInfo)(vd.EmbeddedDir), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) readdir(n int) (fi []os.FileInfo, err error) {
|
||||||
|
|
||||||
|
if vd.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "readdir",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the array of our contents
|
||||||
|
var files []os.FileInfo
|
||||||
|
|
||||||
|
// Add the child directories
|
||||||
|
for _, child := range vd.ChildDirs {
|
||||||
|
child.Filename = filepath.Base(child.Filename)
|
||||||
|
files = append(files, (*embeddedDirInfo)(child))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the child files
|
||||||
|
for _, child := range vd.ChildFiles {
|
||||||
|
child.Filename = filepath.Base(child.Filename)
|
||||||
|
files = append(files, (*embeddedFileInfo)(child))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort it by filename (lexical order)
|
||||||
|
sort.Sort(SortByName(files))
|
||||||
|
|
||||||
|
// Return all contents if that's what is requested
|
||||||
|
if n <= 0 {
|
||||||
|
vd.offset = 0
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user has requested past the end of our list
|
||||||
|
// return what we can and send an EOF
|
||||||
|
if vd.offset+n >= len(files) {
|
||||||
|
offset := vd.offset
|
||||||
|
vd.offset = 0
|
||||||
|
return files[offset:], io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := vd.offset
|
||||||
|
vd.offset += n
|
||||||
|
return files[offset : offset+n], nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) read(bts []byte) (int, error) {
|
||||||
|
if vd.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("is a directory"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) seek(offset int64, whence int) (int64, error) {
|
||||||
|
if vd.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vd.Filename,
|
||||||
|
Err: errors.New("is a directory"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walk is like filepath.Walk()
|
||||||
|
// Visit http://golang.org/pkg/path/filepath/#Walk for more information
|
||||||
|
func (b *Box) Walk(path string, walkFn filepath.WalkFunc) error {
|
||||||
|
|
||||||
|
pathFile, err := b.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer pathFile.Close()
|
||||||
|
|
||||||
|
pathInfo, err := pathFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.IsAppended() || b.IsEmbedded() {
|
||||||
|
return b.walk(path, pathInfo, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't have any embedded or appended box so use live filesystem mode
|
||||||
|
return filepath.Walk(b.absolutePath+string(os.PathSeparator)+path, func(path string, info os.FileInfo, err error) error {
|
||||||
|
|
||||||
|
// Strip out the box name from the returned paths
|
||||||
|
path = strings.TrimPrefix(path, b.absolutePath+string(os.PathSeparator))
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk recursively descends path.
|
||||||
|
// See walk() in $GOROOT/src/pkg/path/filepath/path.go
|
||||||
|
func (b *Box) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||||
|
|
||||||
|
err := walkFn(path, info, nil)
|
||||||
|
if err != nil {
|
||||||
|
if info.IsDir() && err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := b.readDirNames(path)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
|
||||||
|
filename := filepath.Join(path, name)
|
||||||
|
fileObject, err := b.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fileObject.Close()
|
||||||
|
|
||||||
|
fileInfo, err := fileObject.Stat()
|
||||||
|
if err != nil {
|
||||||
|
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = b.walk(filename, fileInfo, walkFn)
|
||||||
|
if err != nil {
|
||||||
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDirNames reads the directory named by path and returns a sorted list of directory entries.
|
||||||
|
// See readDirNames() in $GOROOT/pkg/path/filepath/path.go
|
||||||
|
func (b *Box) readDirNames(path string) ([]string, error) {
|
||||||
|
|
||||||
|
f, err := b.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
stat, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
infos, err := f.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
for _, info := range infos {
|
||||||
|
names = append(names, info.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(names)
|
||||||
|
return names, nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
Package gopreload is a bit of a hack to emulate the behavior of LD_PRELOAD [ld-preload].
|
||||||
|
This allows you to have automatically starting instrumentation, etc.
|
||||||
|
|
||||||
|
[ld-preload]: http://man7.org/linux/man-pages/man8/ld.so.8.html (see LD_PRELOAD section)
|
||||||
|
*/
|
||||||
|
package gopreload
|
|
@ -0,0 +1,26 @@
|
||||||
|
//+build linux,go1.8
|
||||||
|
|
||||||
|
package gopreload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"plugin"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gpv := os.Getenv("GO_PRELOAD")
|
||||||
|
if gpv == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, elem := range strings.Split(gpv, ",") {
|
||||||
|
log.Printf("gopreload: trying to open: %s", elem)
|
||||||
|
_, err := plugin.Open(elem)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%v from GO_PRELOAD cannot be loaded: %v", elem, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package jsonfeed is a set of types and convenience functions for reading and
|
||||||
|
parsing JSON Feed version 1 as defined here: https://jsonfeed.org/version/1
|
||||||
|
*/
|
||||||
|
package jsonfeed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CurrentVersion will point to the current specification of JSON feed
|
||||||
|
// that this package implements.
|
||||||
|
const CurrentVersion = "https://jsonfeed.org/version/1"
|
||||||
|
|
||||||
|
// Item is a single article or link in a JSON Feed.
|
||||||
|
type Item struct {
|
||||||
|
// ID is unique for that item for that feed over time. If an item
|
||||||
|
// is ever updated, the id should be unchanged. New items should
|
||||||
|
// never use a previously-used id. If an id is presented as a number
|
||||||
|
// or other type, a JSON Feed reader must coerce it to a string.
|
||||||
|
// Ideally, the id is the full URL of the resource described by the
|
||||||
|
// item, since URLs make great unique identifiers.
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
// URL is the URL of the resource described by the item. It’s the
|
||||||
|
// permalink. This may be the same as the id — but should be present
|
||||||
|
// regardless.
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
|
||||||
|
// ExternalURL is the URL of a page elsewhere. This is especially
|
||||||
|
// useful for linkblogs. If url links to where you’re talking about
|
||||||
|
// a thing, then this links to the thing you’re talking about.
|
||||||
|
ExternalURL string `json:"external_url,omitempty"`
|
||||||
|
|
||||||
|
// Title (optional, string) is plain text. Microblog items in
|
||||||
|
// particular may omit titles.
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
|
||||||
|
// ContentHTML and ContentText are each optional strings — but one
|
||||||
|
// or both must be present. This is the HTML or plain text of the
|
||||||
|
// item. Important: the only place HTML is allowed in this format
|
||||||
|
// is in content_html. A Twitter-like service might use content_text,
|
||||||
|
// while a blog might use content_html. Use whichever makes sense
|
||||||
|
// for your resource. (It doesn’t even have to be the same for each
|
||||||
|
// item in a feed.)
|
||||||
|
ContentHTML string `json:"content_html,omitempty"`
|
||||||
|
ContentText string `json:"content_text,omitempty"`
|
||||||
|
|
||||||
|
// Summary is a plain text sentence or two describing the item.
|
||||||
|
// This might be presented in a timeline, for instance, where a
|
||||||
|
// detail view would display all of ContentHTML or ContentText.
|
||||||
|
Summary string `json:"summary,omitempty"`
|
||||||
|
|
||||||
|
// Image is the URL of the main image for the item. This image
|
||||||
|
// may also appear in the content_html — if so, it’s a hint to
|
||||||
|
// the feed reader that this is the main, featured image. Feed
|
||||||
|
// readers may use the image as a preview (probably resized as
|
||||||
|
// a thumbnail and placed in a timeline).
|
||||||
|
Image string `json:"image,omitempty"`
|
||||||
|
|
||||||
|
// BannerImage is the URL of an image to use as a banner. Some
|
||||||
|
// blogging systems (such as Medium) display a different banner
|
||||||
|
// image chosen to go with each post, but that image wouldn’t
|
||||||
|
// otherwise appear in the content_html. A feed reader with a
|
||||||
|
// detail view may choose to show this banner image at the top
|
||||||
|
// of the detail view, possibly with the title overlaid.
|
||||||
|
BannerImage string `json:"banner_image,omitempty"`
|
||||||
|
|
||||||
|
// DatePublished specifies the date of this Item's publication.
|
||||||
|
DatePublished time.Time `json:"date_published,omitempty"`
|
||||||
|
|
||||||
|
// DateModified specifies the date of this Item's last modification
|
||||||
|
// (if applicable)
|
||||||
|
DateModified time.Time `json:"date_modified,omitempty"`
|
||||||
|
|
||||||
|
// Author has the same structure as the top-level author. If not
|
||||||
|
// specified in an item, then the top-level author, if present,
|
||||||
|
// is the author of the item.
|
||||||
|
Author *Author `json:"author,omitempty"`
|
||||||
|
|
||||||
|
// Tags can have any plain text values you want. Tags tend to be
|
||||||
|
// just one word, but they may be anything. Note: they are not
|
||||||
|
// the equivalent of Twitter hashtags. Some blogging systems and
|
||||||
|
// other feed formats call these categories.
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
|
||||||
|
// Attachments (optional, array) lists related resources. Podcasts,
|
||||||
|
// for instance, would include an attachment that’s an audio or
|
||||||
|
// video file.
|
||||||
|
Attachments []Attachment `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author specifies the feed author. The author object has several members.
|
||||||
|
// These are all optional, but if you provide an author object, then at
|
||||||
|
// least one is required.
|
||||||
|
type Author struct {
|
||||||
|
// Name is the author's name.
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// URL is the URL of a site owned by the author. It could be a
|
||||||
|
// blog, micro-blog, Twitter account, and so on. Ideally the linked-to
|
||||||
|
// page provides a way to contact the author, but that’s not
|
||||||
|
// required. The URL could be a mailto: link, though we suspect
|
||||||
|
// that will be rare.
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
|
||||||
|
// Avatar is the URL for an image for the author. As with icon,
|
||||||
|
// it should be square and relatively large — such as 512 x 512 —
|
||||||
|
// and should use transparency where appropriate, since it may
|
||||||
|
// be rendered on a non-white background.
|
||||||
|
Avatar string `json:"avatar,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hub describes endpoints that can be used to subscribe to real-time
|
||||||
|
// notifications from the publisher of this feed. Each object has a type
|
||||||
|
// and url, both of which are required.
|
||||||
|
type Hub struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment is a related resource to an Item. If the Feed describes a
|
||||||
|
// podcast, this would refer to the episodes of said podcast.
|
||||||
|
type Attachment struct {
|
||||||
|
// URL specifies the location of the attachment.
|
||||||
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
// MIMEType specifies the type of the attachment, such as "audio/mpeg".
|
||||||
|
MIMEType string `json:"mime_type"`
|
||||||
|
|
||||||
|
// Title is a name for the attachment. Important: if there are multiple
|
||||||
|
// attachments, and two or more have the exact same title (when title
|
||||||
|
// is present), then they are considered as alternate representations
|
||||||
|
// of the same thing. In this way a podcaster, for instance, might
|
||||||
|
// provide an audio recording in different formats.
|
||||||
|
Title string `json:"title,omitifempty"`
|
||||||
|
|
||||||
|
// SizeInBytes specifies the attachment filesize in bytes.
|
||||||
|
SizeInBytes int64 `json:"size_in_bytes,omitempty"`
|
||||||
|
|
||||||
|
// DurationInSeconds specifies how long the attachment takes to listen
|
||||||
|
// to or watch.
|
||||||
|
DurationInSeconds int64 `json:"duration_in_seconds,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed is a list that may change over time, and the individual items in the
|
||||||
|
// list may change.
|
||||||
|
//
|
||||||
|
// Think of a blog or microblog, Twitter or Facebook timeline, set of commits
|
||||||
|
// to a repository, or even a server log. These are all lists, and each could
|
||||||
|
// be described by a Feed.
|
||||||
|
//
|
||||||
|
// A JSON Feed starts with some info at the top: it says where the Feed comes
|
||||||
|
// from, and may say who created it and so on.
|
||||||
|
type Feed struct {
|
||||||
|
// Version is the URL of the version of the format the Feed uses.
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// Title is the name of the Feed, which will often correspond to the
|
||||||
|
// name of the website (blog, for instance), though not necessarily.
|
||||||
|
Title string `json:"title"`
|
||||||
|
|
||||||
|
// HomePageURL is the URL of the resource that the Feed describes.
|
||||||
|
// This resource may or may not actually be a “home” page, but it
|
||||||
|
// should be an HTML page. If a Feed is published on the public web,
|
||||||
|
// this should be considered as required. But it may not make sense
|
||||||
|
// in the case of a file created on a desktop computer, when that
|
||||||
|
// file is not shared or is shared only privately.
|
||||||
|
//
|
||||||
|
// This field is strongly reccomended, but not required.
|
||||||
|
HomePageURL string `json:"home_page_url,omitempty"`
|
||||||
|
|
||||||
|
// FeedURL is the URL of the Feed, and serves as the unique identifier
|
||||||
|
// for the Feed. As with home_page_url, this should be considered
|
||||||
|
// required for Feeds on the public web.
|
||||||
|
//
|
||||||
|
// This field is strongly reccomended, but not required.
|
||||||
|
FeedURL string `json:"Feed_url,omitempty"`
|
||||||
|
|
||||||
|
// Description provides more detail, beyond the title, on what the Feed
|
||||||
|
// is about. A Feed reader may display this text.
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
|
||||||
|
// UserComment is a description of the purpose of the Feed. This is for
|
||||||
|
// the use of people looking at the raw JSON, and should be ignored by
|
||||||
|
// Feed readers.
|
||||||
|
UserComment string `json:"user_comment,omitempty"`
|
||||||
|
|
||||||
|
// NextURL is the URL of a Feed that provides the next n items, where
|
||||||
|
// n is determined by the publisher. This allows for pagination, but
|
||||||
|
// with the expectation that reader software is not required to use it
|
||||||
|
// and probably won’t use it very often. next_url must not be the same
|
||||||
|
// as Feed_url, and it must not be the same as a previous next_url
|
||||||
|
// (to avoid infinite loops).
|
||||||
|
NextURL string `json:"next_url,omitempty"`
|
||||||
|
|
||||||
|
// Icon is the URL of an image for the Feed suitable to be used in a
|
||||||
|
// timeline, much the way an avatar might be used. It should be square
|
||||||
|
// and relatively large — such as 512 x 512 — so that it can be scaled-down
|
||||||
|
// and so that it can look good on retina displays. It should use transparency
|
||||||
|
// where appropriate, since it may be rendered on a non-white background.
|
||||||
|
Icon string `json:"icon,omitempty"`
|
||||||
|
|
||||||
|
// Favicon is the URL of an image for the Feed suitable to be used in a
|
||||||
|
// source list. It should be square and relatively small, but not smaller
|
||||||
|
// than 64 x 64 (so that it can look good on retina displays). As with icon,
|
||||||
|
// this image should use transparency where appropriate, since it may be
|
||||||
|
// rendered on a non-white background.
|
||||||
|
Favicon string `json:"favicon,omitempty"`
|
||||||
|
|
||||||
|
// Author specifies the Feed author.
|
||||||
|
Author Author `json:"author,omitempty"`
|
||||||
|
|
||||||
|
// Expired specifies if the Feed will never update again. A Feed for a
|
||||||
|
// temporary event, such as an instance of the Olympics, could expire.
|
||||||
|
// If the value is true, then it’s expired. Any other value, or the
|
||||||
|
// absence of expired, means the Feed may continue to update.
|
||||||
|
Expired bool `json:"expired,omitempty"`
|
||||||
|
|
||||||
|
// Hubs describes endpoints that can be used to subscribe to real-time
|
||||||
|
// notifications from the publisher of this Feed.
|
||||||
|
Hubs []Hub `json:"hubs,omitempty"`
|
||||||
|
|
||||||
|
// Items is the list of Items in this Feed.
|
||||||
|
Items []Item `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse reads a JSON feed object out of a reader.
|
||||||
|
func Parse(r io.Reader) (Feed, error) {
|
||||||
|
var feed Feed
|
||||||
|
decoder := json.NewDecoder(r)
|
||||||
|
if err := decoder.Decode(&feed); err != nil {
|
||||||
|
return Feed{}, err
|
||||||
|
}
|
||||||
|
return feed, nil
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package ln
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter interface for defining chain filters
|
||||||
|
type Filter interface {
|
||||||
|
Apply(Event) bool
|
||||||
|
Run()
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterFunc allows simple functions to implement the Filter interface
|
||||||
|
type FilterFunc func(e Event) bool
|
||||||
|
|
||||||
|
// Apply implements the Filter interface
|
||||||
|
func (ff FilterFunc) Apply(e Event) bool {
|
||||||
|
return ff(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements the Filter interface
|
||||||
|
func (ff FilterFunc) Run() {}
|
||||||
|
|
||||||
|
// Close implements the Filter interface
|
||||||
|
func (ff FilterFunc) Close() {}
|
||||||
|
|
||||||
|
// WriterFilter implements a filter, which arbitrarily writes to an io.Writer
|
||||||
|
type WriterFilter struct {
|
||||||
|
sync.Mutex
|
||||||
|
Out io.Writer
|
||||||
|
Formatter Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriterFilter creates a filter to add to the chain
|
||||||
|
func NewWriterFilter(out io.Writer, format Formatter) *WriterFilter {
|
||||||
|
if format == nil {
|
||||||
|
format = DefaultFormatter
|
||||||
|
}
|
||||||
|
return &WriterFilter{
|
||||||
|
Out: out,
|
||||||
|
Formatter: format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements the Filter interface
|
||||||
|
func (w *WriterFilter) Apply(e Event) bool {
|
||||||
|
output, err := w.Formatter.Format(e)
|
||||||
|
if err == nil {
|
||||||
|
w.Lock()
|
||||||
|
w.Out.Write(output)
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements the Filter interface
|
||||||
|
func (w *WriterFilter) Run() {}
|
||||||
|
|
||||||
|
// Close implements the Filter interface
|
||||||
|
func (w *WriterFilter) Close() {}
|
||||||
|
|
||||||
|
// NilFilter is safe to return as a Filter, but does nothing
|
||||||
|
var NilFilter = FilterFunc(func(e Event) bool { return true })
|
|
@ -0,0 +1,110 @@
|
||||||
|
package ln
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultTimeFormat represents the way in which time will be formatted by default
|
||||||
|
DefaultTimeFormat = time.RFC3339
|
||||||
|
)
|
||||||
|
|
||||||
|
// Formatter defines the formatting of events
|
||||||
|
type Formatter interface {
|
||||||
|
Format(Event) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFormatter is the default way in which to format events
|
||||||
|
var DefaultFormatter Formatter
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DefaultFormatter = NewTextFormatter()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextFormatter formats events as key value pairs.
|
||||||
|
// Any remaining text not wrapped in an instance of `F` will be
|
||||||
|
// placed at the end.
|
||||||
|
type TextFormatter struct {
|
||||||
|
TimeFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTextFormatter returns a Formatter that outputs as text.
|
||||||
|
func NewTextFormatter() Formatter {
|
||||||
|
return &TextFormatter{TimeFormat: DefaultTimeFormat}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format implements the Formatter interface
|
||||||
|
func (t *TextFormatter) Format(e Event) ([]byte, error) {
|
||||||
|
var writer bytes.Buffer
|
||||||
|
|
||||||
|
writer.WriteString("time=\"")
|
||||||
|
writer.WriteString(e.Time.Format(t.TimeFormat))
|
||||||
|
writer.WriteString("\"")
|
||||||
|
|
||||||
|
keys := make([]string, len(e.Data))
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range e.Data {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
v := e.Data[k]
|
||||||
|
|
||||||
|
writer.WriteByte(' ')
|
||||||
|
if shouldQuote(k) {
|
||||||
|
writer.WriteString(fmt.Sprintf("%q", k))
|
||||||
|
} else {
|
||||||
|
writer.WriteString(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteByte('=')
|
||||||
|
|
||||||
|
switch v.(type) {
|
||||||
|
case string:
|
||||||
|
vs, _ := v.(string)
|
||||||
|
if shouldQuote(vs) {
|
||||||
|
fmt.Fprintf(&writer, "%q", vs)
|
||||||
|
} else {
|
||||||
|
writer.WriteString(vs)
|
||||||
|
}
|
||||||
|
case error:
|
||||||
|
tmperr, _ := v.(error)
|
||||||
|
es := tmperr.Error()
|
||||||
|
|
||||||
|
if shouldQuote(es) {
|
||||||
|
fmt.Fprintf(&writer, "%q", es)
|
||||||
|
} else {
|
||||||
|
writer.WriteString(es)
|
||||||
|
}
|
||||||
|
case time.Time:
|
||||||
|
tmptime, _ := v.(time.Time)
|
||||||
|
writer.WriteString(tmptime.Format(time.RFC3339))
|
||||||
|
default:
|
||||||
|
fmt.Fprint(&writer, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Message) > 0 {
|
||||||
|
fmt.Fprintf(&writer, " _msg=%q", e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteByte('\n')
|
||||||
|
return writer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldQuote(s string) bool {
|
||||||
|
for _, b := range s {
|
||||||
|
if !((b >= 'A' && b <= 'Z') ||
|
||||||
|
(b >= 'a' && b <= 'z') ||
|
||||||
|
(b >= '0' && b <= '9') ||
|
||||||
|
(b == '-' || b == '.' || b == '#' ||
|
||||||
|
b == '/' || b == '_')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package ln
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger holds the current priority and list of filters
|
||||||
|
type Logger struct {
|
||||||
|
Filters []Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLogger is the default implementation of Logger
|
||||||
|
var DefaultLogger *Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var defaultFilters []Filter
|
||||||
|
|
||||||
|
// Default to STDOUT for logging, but allow LN_OUT to change it.
|
||||||
|
out := os.Stdout
|
||||||
|
if os.Getenv("LN_OUT") == "<stderr>" {
|
||||||
|
out = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultFilters = append(defaultFilters, NewWriterFilter(out, nil))
|
||||||
|
|
||||||
|
DefaultLogger = &Logger{
|
||||||
|
Filters: defaultFilters,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// F is a key-value mapping for structured data.
|
||||||
|
type F map[string]interface{}
|
||||||
|
|
||||||
|
type Fer interface {
|
||||||
|
F() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event represents an event
|
||||||
|
type Event struct {
|
||||||
|
Time time.Time
|
||||||
|
Data F
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log is the generic logging method.
|
||||||
|
func (l *Logger) Log(xs ...interface{}) {
|
||||||
|
var bits []interface{}
|
||||||
|
event := Event{Time: time.Now()}
|
||||||
|
|
||||||
|
addF := func(bf F) {
|
||||||
|
if event.Data == nil {
|
||||||
|
event.Data = bf
|
||||||
|
} else {
|
||||||
|
for k, v := range bf {
|
||||||
|
event.Data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the event
|
||||||
|
for _, b := range xs {
|
||||||
|
if bf, ok := b.(F); ok {
|
||||||
|
addF(bf)
|
||||||
|
} else if fer, ok := b.(Fer); ok {
|
||||||
|
addF(F(fer.F()))
|
||||||
|
} else {
|
||||||
|
bits = append(bits, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Message = fmt.Sprint(bits...)
|
||||||
|
|
||||||
|
if os.Getenv("LN_DEBUG_ALL_EVENTS") == "1" {
|
||||||
|
frame := callersFrame()
|
||||||
|
if event.Data == nil {
|
||||||
|
event.Data = make(F)
|
||||||
|
}
|
||||||
|
event.Data["_lineno"] = frame.lineno
|
||||||
|
event.Data["_function"] = frame.function
|
||||||
|
event.Data["_filename"] = frame.filename
|
||||||
|
}
|
||||||
|
|
||||||
|
l.filter(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) filter(e Event) {
|
||||||
|
for _, f := range l.Filters {
|
||||||
|
if !f.Apply(e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs an error and information about the context of said error.
|
||||||
|
func (l *Logger) Error(err error, xs ...interface{}) {
|
||||||
|
data := F{}
|
||||||
|
frame := callersFrame()
|
||||||
|
|
||||||
|
data["_lineno"] = frame.lineno
|
||||||
|
data["_function"] = frame.function
|
||||||
|
data["_filename"] = frame.filename
|
||||||
|
data["err"] = err
|
||||||
|
|
||||||
|
cause := errors.Cause(err)
|
||||||
|
if cause != nil {
|
||||||
|
data["cause"] = cause.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
xs = append(xs, data)
|
||||||
|
|
||||||
|
l.Log(xs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs this set of values, then exits with status code 1.
|
||||||
|
func (l *Logger) Fatal(xs ...interface{}) {
|
||||||
|
l.Log(xs...)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default Implementation
|
||||||
|
|
||||||
|
// Log is the generic logging method.
|
||||||
|
func Log(xs ...interface{}) {
|
||||||
|
DefaultLogger.Log(xs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs an error and information about the context of said error.
|
||||||
|
func Error(err error, xs ...interface{}) {
|
||||||
|
DefaultLogger.Error(err, xs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs this set of values, then exits with status code 1.
|
||||||
|
func Fatal(xs ...interface{}) {
|
||||||
|
DefaultLogger.Fatal(xs...)
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ln
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type frame struct {
|
||||||
|
filename string
|
||||||
|
function string
|
||||||
|
lineno int
|
||||||
|
}
|
||||||
|
|
||||||
|
// skips 2 frames, since Caller returns the current frame, and we need
|
||||||
|
// the caller's caller.
|
||||||
|
func callersFrame() frame {
|
||||||
|
var out frame
|
||||||
|
pc, file, line, ok := runtime.Caller(3)
|
||||||
|
if !ok {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
srcLoc := strings.LastIndex(file, "/src/")
|
||||||
|
if srcLoc >= 0 {
|
||||||
|
file = file[srcLoc+5:]
|
||||||
|
}
|
||||||
|
out.filename = file
|
||||||
|
out.function = functionName(pc)
|
||||||
|
out.lineno = line
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func functionName(pc uintptr) string {
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
return "???"
|
||||||
|
}
|
||||||
|
name := fn.Name()
|
||||||
|
beg := strings.LastIndex(name, string(os.PathSeparator))
|
||||||
|
return name[beg+1:]
|
||||||
|
// end := strings.LastIndex(name, string(os.PathSeparator))
|
||||||
|
// return name[end+1 : len(name)]
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Package zipexe attempts to open an executable binary file as a zip file.
|
||||||
|
package zipexe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"debug/elf"
|
||||||
|
"debug/macho"
|
||||||
|
"debug/pe"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Opens a zip file by path.
|
||||||
|
func Open(path string) (*zip.Reader, error) {
|
||||||
|
_, rd, err := OpenCloser(path)
|
||||||
|
return rd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenCloser is like Open but returns an additional Closer to avoid leaking open files.
|
||||||
|
func OpenCloser(path string) (io.Closer, *zip.Reader, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
finfo, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
zr, err := NewReader(file, finfo.Size())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return file, zr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a zip file, specially handling various binaries that may have been
|
||||||
|
// augmented with zip data.
|
||||||
|
func NewReader(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
||||||
|
handlers := []func(io.ReaderAt, int64) (*zip.Reader, error){
|
||||||
|
zip.NewReader,
|
||||||
|
zipExeReaderMacho,
|
||||||
|
zipExeReaderElf,
|
||||||
|
zipExeReaderPe,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, handler := range handlers {
|
||||||
|
zfile, err := handler(rda, size)
|
||||||
|
if err == nil {
|
||||||
|
return zfile, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("Couldn't Open As Executable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// zipExeReaderMacho treats the file as a Mach-O binary
|
||||||
|
// (Mac OS X / Darwin executable) and attempts to find a zip archive.
|
||||||
|
func zipExeReaderMacho(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
||||||
|
file, err := macho.NewFile(rda)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var max int64
|
||||||
|
for _, load := range file.Loads {
|
||||||
|
seg, ok := load.(*macho.Segment)
|
||||||
|
if ok {
|
||||||
|
// Check if the segment contains a zip file
|
||||||
|
if zfile, err := zip.NewReader(seg, int64(seg.Filesz)); err == nil {
|
||||||
|
return zfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise move end of file pointer
|
||||||
|
end := int64(seg.Offset + seg.Filesz)
|
||||||
|
if end > max {
|
||||||
|
max = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No zip file within binary, try appended to end
|
||||||
|
section := io.NewSectionReader(rda, max, size-max)
|
||||||
|
return zip.NewReader(section, section.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
// zipExeReaderPe treats the file as a Portable Exectuable binary
|
||||||
|
// (Windows executable) and attempts to find a zip archive.
|
||||||
|
func zipExeReaderPe(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
||||||
|
file, err := pe.NewFile(rda)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var max int64
|
||||||
|
for _, sec := range file.Sections {
|
||||||
|
// Check if this section has a zip file
|
||||||
|
if zfile, err := zip.NewReader(sec, int64(sec.Size)); err == nil {
|
||||||
|
return zfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise move end of file pointer
|
||||||
|
end := int64(sec.Offset + sec.Size)
|
||||||
|
if end > max {
|
||||||
|
max = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No zip file within binary, try appended to end
|
||||||
|
section := io.NewSectionReader(rda, max, size-max)
|
||||||
|
return zip.NewReader(section, section.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
// zipExeReaderElf treats the file as a ELF binary
|
||||||
|
// (linux/BSD/etc... executable) and attempts to find a zip archive.
|
||||||
|
func zipExeReaderElf(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
||||||
|
file, err := elf.NewFile(rda)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var max int64
|
||||||
|
for _, sect := range file.Sections {
|
||||||
|
if sect.Type == elf.SHT_NOBITS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this section has a zip file
|
||||||
|
if zfile, err := zip.NewReader(sect, int64(sect.Size)); err == nil {
|
||||||
|
return zfile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise move end of file pointer
|
||||||
|
end := int64(sect.Offset + sect.Size)
|
||||||
|
if end > max {
|
||||||
|
max = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No zip file within binary, try appended to end
|
||||||
|
section := io.NewSectionReader(rda, max, size-max)
|
||||||
|
return zip.NewReader(section, section.Size())
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Copyright 2016 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 agent provides hooks programs can register to retrieve
|
||||||
|
// diagnostics data by using gops.
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
gosignal "os/signal"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
"runtime/trace"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bufio"
|
||||||
|
|
||||||
|
"github.com/google/gops/internal"
|
||||||
|
"github.com/google/gops/signal"
|
||||||
|
"github.com/kardianos/osext"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultAddr = "127.0.0.1:0"
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
portfile string
|
||||||
|
listener net.Listener
|
||||||
|
|
||||||
|
units = []string{" bytes", "KB", "MB", "GB", "TB", "PB"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options allows configuring the started agent.
|
||||||
|
type Options struct {
|
||||||
|
// Addr is the host:port the agent will be listening at.
|
||||||
|
// Optional.
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
// NoShutdownCleanup tells the agent not to automatically cleanup
|
||||||
|
// resources if the running process receives an interrupt.
|
||||||
|
// Optional.
|
||||||
|
NoShutdownCleanup bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen starts the gops agent on a host process. Once agent started, users
|
||||||
|
// can use the advanced gops features. The agent will listen to Interrupt
|
||||||
|
// signals and exit the process, if you need to perform further work on the
|
||||||
|
// Interrupt signal use the options parameter to configure the agent
|
||||||
|
// accordingly.
|
||||||
|
//
|
||||||
|
// Note: The agent exposes an endpoint via a TCP connection that can be used by
|
||||||
|
// any program on the system. Review your security requirements before starting
|
||||||
|
// the agent.
|
||||||
|
func Listen(opts *Options) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if opts == nil {
|
||||||
|
opts = &Options{}
|
||||||
|
}
|
||||||
|
if portfile != "" {
|
||||||
|
return fmt.Errorf("gops: agent already listening at: %v", listener.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
|
gopsdir, err := internal.ConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(gopsdir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !opts.NoShutdownCleanup {
|
||||||
|
gracefulShutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := opts.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = defaultAddr
|
||||||
|
}
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listener = ln
|
||||||
|
port := listener.Addr().(*net.TCPAddr).Port
|
||||||
|
portfile = fmt.Sprintf("%s/%d", gopsdir, os.Getpid())
|
||||||
|
err = ioutil.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go listen()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listen() {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
fd, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gops: %v", err)
|
||||||
|
if netErr, ok := err.(net.Error); ok && !netErr.Temporary() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := fd.Read(buf); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gops: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := handle(fd, buf); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gops: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gracefulShutdown() {
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
gosignal.Notify(c, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
// cleanup the socket on shutdown.
|
||||||
|
<-c
|
||||||
|
Close()
|
||||||
|
os.Exit(1)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the agent, removing temporary files and closing the TCP listener.
|
||||||
|
// If no agent is listening, Close does nothing.
|
||||||
|
func Close() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if portfile != "" {
|
||||||
|
os.Remove(portfile)
|
||||||
|
portfile = ""
|
||||||
|
}
|
||||||
|
if listener != nil {
|
||||||
|
listener.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatBytes(val uint64) string {
|
||||||
|
var i int
|
||||||
|
var target uint64
|
||||||
|
for i = range units {
|
||||||
|
target = 1 << uint(10*(i+1))
|
||||||
|
if val < target {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
return fmt.Sprintf("%0.2f%s (%d bytes)", float64(val)/(float64(target)/1024), units[i], val)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d bytes", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(conn io.Writer, msg []byte) error {
|
||||||
|
switch msg[0] {
|
||||||
|
case signal.StackTrace:
|
||||||
|
return pprof.Lookup("goroutine").WriteTo(conn, 2)
|
||||||
|
case signal.GC:
|
||||||
|
runtime.GC()
|
||||||
|
_, err := conn.Write([]byte("ok"))
|
||||||
|
return err
|
||||||
|
case signal.MemStats:
|
||||||
|
var s runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&s)
|
||||||
|
fmt.Fprintf(conn, "alloc: %v\n", formatBytes(s.Alloc))
|
||||||
|
fmt.Fprintf(conn, "total-alloc: %v\n", formatBytes(s.TotalAlloc))
|
||||||
|
fmt.Fprintf(conn, "sys: %v\n", formatBytes(s.Sys))
|
||||||
|
fmt.Fprintf(conn, "lookups: %v\n", s.Lookups)
|
||||||
|
fmt.Fprintf(conn, "mallocs: %v\n", s.Mallocs)
|
||||||
|
fmt.Fprintf(conn, "frees: %v\n", s.Frees)
|
||||||
|
fmt.Fprintf(conn, "heap-alloc: %v\n", formatBytes(s.HeapAlloc))
|
||||||
|
fmt.Fprintf(conn, "heap-sys: %v\n", formatBytes(s.HeapSys))
|
||||||
|
fmt.Fprintf(conn, "heap-idle: %v\n", formatBytes(s.HeapIdle))
|
||||||
|
fmt.Fprintf(conn, "heap-in-use: %v\n", formatBytes(s.HeapInuse))
|
||||||
|
fmt.Fprintf(conn, "heap-released: %v\n", formatBytes(s.HeapReleased))
|
||||||
|
fmt.Fprintf(conn, "heap-objects: %v\n", s.HeapObjects)
|
||||||
|
fmt.Fprintf(conn, "stack-in-use: %v\n", formatBytes(s.StackInuse))
|
||||||
|
fmt.Fprintf(conn, "stack-sys: %v\n", formatBytes(s.StackSys))
|
||||||
|
fmt.Fprintf(conn, "next-gc: when heap-alloc >= %v\n", formatBytes(s.NextGC))
|
||||||
|
lastGC := "-"
|
||||||
|
if s.LastGC != 0 {
|
||||||
|
lastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC)))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(conn, "last-gc: %v\n", lastGC)
|
||||||
|
fmt.Fprintf(conn, "gc-pause: %v\n", time.Duration(s.PauseTotalNs))
|
||||||
|
fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC)
|
||||||
|
fmt.Fprintf(conn, "enable-gc: %v\n", s.EnableGC)
|
||||||
|
fmt.Fprintf(conn, "debug-gc: %v\n", s.DebugGC)
|
||||||
|
case signal.Version:
|
||||||
|
fmt.Fprintf(conn, "%v\n", runtime.Version())
|
||||||
|
case signal.HeapProfile:
|
||||||
|
pprof.WriteHeapProfile(conn)
|
||||||
|
case signal.CPUProfile:
|
||||||
|
if err := pprof.StartCPUProfile(conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
case signal.Stats:
|
||||||
|
fmt.Fprintf(conn, "goroutines: %v\n", runtime.NumGoroutine())
|
||||||
|
fmt.Fprintf(conn, "OS threads: %v\n", pprof.Lookup("threadcreate").Count())
|
||||||
|
fmt.Fprintf(conn, "GOMAXPROCS: %v\n", runtime.GOMAXPROCS(0))
|
||||||
|
fmt.Fprintf(conn, "num CPU: %v\n", runtime.NumCPU())
|
||||||
|
case signal.BinaryDump:
|
||||||
|
path, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = bufio.NewReader(f).WriteTo(conn)
|
||||||
|
return err
|
||||||
|
case signal.Trace:
|
||||||
|
trace.Start(conn)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
trace.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConfigDir() (string, error) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return filepath.Join(os.Getenv("APPDATA"), "gops"), nil
|
||||||
|
}
|
||||||
|
homeDir := guessUnixHomeDir()
|
||||||
|
if homeDir == "" {
|
||||||
|
return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty")
|
||||||
|
}
|
||||||
|
return filepath.Join(homeDir, ".config", "gops"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func guessUnixHomeDir() string {
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
return usr.HomeDir
|
||||||
|
}
|
||||||
|
return os.Getenv("HOME")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PIDFile(pid int) (string, error) {
|
||||||
|
gopsdir, err := ConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%d", gopsdir, pid), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPort(pid int) (string, error) {
|
||||||
|
portfile, err := PIDFile(pid)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadFile(portfile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
port := strings.TrimSpace(string(b))
|
||||||
|
return port, nil
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2016 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 signal contains signals used to communicate to the gops agents.
|
||||||
|
package signal
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StackTrace represents a command to print stack trace.
|
||||||
|
StackTrace = byte(0x1)
|
||||||
|
|
||||||
|
// GC runs the garbage collector.
|
||||||
|
GC = byte(0x2)
|
||||||
|
|
||||||
|
// MemStats reports memory stats.
|
||||||
|
MemStats = byte(0x3)
|
||||||
|
|
||||||
|
// Version prints the Go version.
|
||||||
|
Version = byte(0x4)
|
||||||
|
|
||||||
|
// HeapProfile starts `go tool pprof` with the current memory profile.
|
||||||
|
HeapProfile = byte(0x5)
|
||||||
|
|
||||||
|
// CPUProfile starts `go tool pprof` with the current CPU profile
|
||||||
|
CPUProfile = byte(0x6)
|
||||||
|
|
||||||
|
// Stats returns Go runtime statistics such as number of goroutines, GOMAXPROCS, and NumCPU.
|
||||||
|
Stats = byte(0x7)
|
||||||
|
|
||||||
|
// Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool.
|
||||||
|
Trace = byte(0x8)
|
||||||
|
|
||||||
|
// BinaryDump returns running binary file.
|
||||||
|
BinaryDump = byte(0x9)
|
||||||
|
)
|
|
@ -0,0 +1,163 @@
|
||||||
|
package feeds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generates Atom feed as XML
|
||||||
|
|
||||||
|
const ns = "http://www.w3.org/2005/Atom"
|
||||||
|
|
||||||
|
type AtomPerson struct {
|
||||||
|
Name string `xml:"name,omitempty"`
|
||||||
|
Uri string `xml:"uri,omitempty"`
|
||||||
|
Email string `xml:"email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtomSummary struct {
|
||||||
|
XMLName xml.Name `xml:"summary"`
|
||||||
|
Content string `xml:",chardata"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtomContent struct {
|
||||||
|
XMLName xml.Name `xml:"content"`
|
||||||
|
Content string `xml:",chardata"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtomAuthor struct {
|
||||||
|
XMLName xml.Name `xml:"author"`
|
||||||
|
AtomPerson
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtomContributor struct {
|
||||||
|
XMLName xml.Name `xml:"contributor"`
|
||||||
|
AtomPerson
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtomEntry struct {
|
||||||
|
XMLName xml.Name `xml:"entry"`
|
||||||
|
Xmlns string `xml:"xmlns,attr,omitempty"`
|
||||||
|
Title string `xml:"title"` // required
|
||||||
|
Updated string `xml:"updated"` // required
|
||||||
|
Id string `xml:"id"` // required
|
||||||
|
Category string `xml:"category,omitempty"`
|
||||||
|
Content *AtomContent
|
||||||
|
Rights string `xml:"rights,omitempty"`
|
||||||
|
Source string `xml:"source,omitempty"`
|
||||||
|
Published string `xml:"published,omitempty"`
|
||||||
|
Contributor *AtomContributor
|
||||||
|
Link *AtomLink // required if no child 'content' elements
|
||||||
|
Summary *AtomSummary // required if content has src or content is base64
|
||||||
|
Author *AtomAuthor // required if feed lacks an author
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtomLink struct {
|
||||||
|
//Atom 1.0 <link rel="enclosure" type="audio/mpeg" title="MP3" href="http://www.example.org/myaudiofile.mp3" length="1234" />
|
||||||
|
XMLName xml.Name `xml:"link"`
|
||||||
|
Href string `xml:"href,attr"`
|
||||||
|
Rel string `xml:"rel,attr,omitempty"`
|
||||||
|
Type string `xml:"type,attr,omitempty"`
|
||||||
|
Length string `xml:"length,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtomFeed struct {
|
||||||
|
XMLName xml.Name `xml:"feed"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Title string `xml:"title"` // required
|
||||||
|
Id string `xml:"id"` // required
|
||||||
|
Updated string `xml:"updated"` // required
|
||||||
|
Category string `xml:"category,omitempty"`
|
||||||
|
Icon string `xml:"icon,omitempty"`
|
||||||
|
Logo string `xml:"logo,omitempty"`
|
||||||
|
Rights string `xml:"rights,omitempty"` // copyright used
|
||||||
|
Subtitle string `xml:"subtitle,omitempty"`
|
||||||
|
Link *AtomLink
|
||||||
|
Author *AtomAuthor `xml:"author,omitempty"`
|
||||||
|
Contributor *AtomContributor
|
||||||
|
Entries []*AtomEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type Atom struct {
|
||||||
|
*Feed
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAtomEntry(i *Item) *AtomEntry {
|
||||||
|
id := i.Id
|
||||||
|
// assume the description is html
|
||||||
|
c := &AtomContent{Content: i.Description, Type: "html"}
|
||||||
|
|
||||||
|
if len(id) == 0 {
|
||||||
|
// if there's no id set, try to create one, either from data or just a uuid
|
||||||
|
if len(i.Link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) {
|
||||||
|
dateStr := anyTimeFormat("2006-01-02", i.Updated, i.Created)
|
||||||
|
host, path := i.Link.Href, "/invalid.html"
|
||||||
|
if url, err := url.Parse(i.Link.Href); err == nil {
|
||||||
|
host, path = url.Host, url.Path
|
||||||
|
}
|
||||||
|
id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path)
|
||||||
|
} else {
|
||||||
|
id = "urn:uuid:" + NewUUID().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var name, email string
|
||||||
|
if i.Author != nil {
|
||||||
|
name, email = i.Author.Name, i.Author.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
x := &AtomEntry{
|
||||||
|
Title: i.Title,
|
||||||
|
Link: &AtomLink{Href: i.Link.Href, Rel: i.Link.Rel, Type: i.Link.Type},
|
||||||
|
Content: c,
|
||||||
|
Id: id,
|
||||||
|
Updated: anyTimeFormat(time.RFC3339, i.Updated, i.Created),
|
||||||
|
}
|
||||||
|
|
||||||
|
intLength, err := strconv.ParseInt(i.Link.Length, 10, 64)
|
||||||
|
|
||||||
|
if err == nil && (intLength > 0 || i.Link.Type != "") {
|
||||||
|
i.Link.Rel = "enclosure"
|
||||||
|
x.Link = &AtomLink{Href: i.Link.Href, Rel: i.Link.Rel, Type: i.Link.Type, Length: i.Link.Length}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) > 0 || len(email) > 0 {
|
||||||
|
x.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: name, Email: email}}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new AtomFeed with a generic Feed struct's data
|
||||||
|
func (a *Atom) AtomFeed() *AtomFeed {
|
||||||
|
updated := anyTimeFormat(time.RFC3339, a.Updated, a.Created)
|
||||||
|
feed := &AtomFeed{
|
||||||
|
Xmlns: ns,
|
||||||
|
Title: a.Title,
|
||||||
|
Link: &AtomLink{Href: a.Link.Href, Rel: a.Link.Rel},
|
||||||
|
Subtitle: a.Description,
|
||||||
|
Id: a.Link.Href,
|
||||||
|
Updated: updated,
|
||||||
|
Rights: a.Copyright,
|
||||||
|
}
|
||||||
|
if a.Author != nil {
|
||||||
|
feed.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: a.Author.Name, Email: a.Author.Email}}
|
||||||
|
}
|
||||||
|
for _, e := range a.Items {
|
||||||
|
feed.Entries = append(feed.Entries, newAtomEntry(e))
|
||||||
|
}
|
||||||
|
return feed
|
||||||
|
}
|
||||||
|
|
||||||
|
// return an XML-Ready object for an Atom object
|
||||||
|
func (a *Atom) FeedXml() interface{} {
|
||||||
|
return a.AtomFeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// return an XML-ready object for an AtomFeed object
|
||||||
|
func (a *AtomFeed) FeedXml() interface{} {
|
||||||
|
return a
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Syndication (feed) generator library for golang.
|
||||||
|
|
||||||
|
Installing
|
||||||
|
|
||||||
|
go get github.com/gorilla/feeds
|
||||||
|
|
||||||
|
Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS and Atom specific RssFeed and AtomFeed objects which allow access to all of each spec's defined elements.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
Create a Feed and some Items in that feed using the generic interfaces:
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
. "github.com/gorilla/feeds
|
||||||
|
)
|
||||||
|
|
||||||
|
now = time.Now()
|
||||||
|
|
||||||
|
feed := &Feed{
|
||||||
|
Title: "jmoiron.net blog",
|
||||||
|
Link: &Link{Href: "http://jmoiron.net/blog"},
|
||||||
|
Description: "discussion about tech, footie, photos",
|
||||||
|
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
|
||||||
|
Created: now,
|
||||||
|
Copyright: "This work is copyright © Benjamin Button",
|
||||||
|
}
|
||||||
|
|
||||||
|
feed.Items = []*Item{
|
||||||
|
&Item{
|
||||||
|
Title: "Limiting Concurrency in Go",
|
||||||
|
Link: &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
|
||||||
|
Description: "A discussion on controlled parallelism in golang",
|
||||||
|
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
|
||||||
|
Created: now,
|
||||||
|
},
|
||||||
|
&Item{
|
||||||
|
Title: "Logic-less Template Redux",
|
||||||
|
Link: &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
|
||||||
|
Description: "More thoughts on logicless templates",
|
||||||
|
Created: now,
|
||||||
|
},
|
||||||
|
&Item{
|
||||||
|
Title: "Idiomatic Code Reuse in Go",
|
||||||
|
Link: &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
|
||||||
|
Description: "How to use interfaces <em>effectively</em>",
|
||||||
|
Created: now,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
From here, you can output Atom or RSS versions of this feed easily
|
||||||
|
|
||||||
|
atom, err := feed.ToAtom()
|
||||||
|
rss, err := feed.ToRss()
|
||||||
|
|
||||||
|
You can also get access to the underlying objects that feeds uses to export its XML
|
||||||
|
|
||||||
|
atomFeed := &Atom{feed}.AtomFeed()
|
||||||
|
rssFeed := &Rss{feed}.RssFeed()
|
||||||
|
|
||||||
|
From here, you can modify or add each syndication's specific fields before outputting
|
||||||
|
|
||||||
|
atomFeed.Subtitle = "plays the blues"
|
||||||
|
atom, err := ToXML(atomFeed)
|
||||||
|
rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)"
|
||||||
|
rss, err := ToXML(rssFeed)
|
||||||
|
|
||||||
|
*/
|
||||||
|
package feeds
|
|
@ -0,0 +1,106 @@
|
||||||
|
package feeds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
Href, Rel, Type, Length string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Author struct {
|
||||||
|
Name, Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
Title string
|
||||||
|
Link *Link
|
||||||
|
Author *Author
|
||||||
|
Description string // used as description in rss, summary in atom
|
||||||
|
Id string // used as guid in rss, id in atom
|
||||||
|
Updated time.Time
|
||||||
|
Created time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type Feed struct {
|
||||||
|
Title string
|
||||||
|
Link *Link
|
||||||
|
Description string
|
||||||
|
Author *Author
|
||||||
|
Updated time.Time
|
||||||
|
Created time.Time
|
||||||
|
Id string
|
||||||
|
Subtitle string
|
||||||
|
Items []*Item
|
||||||
|
Copyright string
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a new Item to a Feed
|
||||||
|
func (f *Feed) Add(item *Item) {
|
||||||
|
f.Items = append(f.Items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the first non-zero time formatted as a string or ""
|
||||||
|
func anyTimeFormat(format string, times ...time.Time) string {
|
||||||
|
for _, t := range times {
|
||||||
|
if !t.IsZero() {
|
||||||
|
return t.Format(format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface used by ToXML to get a object suitable for exporting XML.
|
||||||
|
type XmlFeed interface {
|
||||||
|
FeedXml() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn a feed object (either a Feed, AtomFeed, or RssFeed) into xml
|
||||||
|
// returns an error if xml marshaling fails
|
||||||
|
func ToXML(feed XmlFeed) (string, error) {
|
||||||
|
x := feed.FeedXml()
|
||||||
|
data, err := xml.MarshalIndent(x, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// strip empty line from default xml header
|
||||||
|
s := xml.Header[:len(xml.Header)-1] + string(data)
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a feed object (either a Feed, AtomFeed, or RssFeed) as XML into
|
||||||
|
// the writer. Returns an error if XML marshaling fails.
|
||||||
|
func WriteXML(feed XmlFeed, w io.Writer) error {
|
||||||
|
x := feed.FeedXml()
|
||||||
|
// write default xml header, without the newline
|
||||||
|
if _, err := w.Write([]byte(xml.Header[:len(xml.Header)-1])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e := xml.NewEncoder(w)
|
||||||
|
e.Indent("", " ")
|
||||||
|
return e.Encode(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates an Atom representation of this feed
|
||||||
|
func (f *Feed) ToAtom() (string, error) {
|
||||||
|
a := &Atom{f}
|
||||||
|
return ToXML(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes an Atom representation of this feed to the writer.
|
||||||
|
func (f *Feed) WriteAtom(w io.Writer) error {
|
||||||
|
return WriteXML(&Atom{f}, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates an Rss representation of this feed
|
||||||
|
func (f *Feed) ToRss() (string, error) {
|
||||||
|
r := &Rss{f}
|
||||||
|
return ToXML(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes an RSS representation of this feed to the writer.
|
||||||
|
func (f *Feed) WriteRss(w io.Writer) error {
|
||||||
|
return WriteXML(&Rss{f}, w)
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package feeds
|
||||||
|
|
||||||
|
// rss support
|
||||||
|
// validation done according to spec here:
|
||||||
|
// http://cyber.law.harvard.edu/rss/rss.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// private wrapper around the RssFeed which gives us the <rss>..</rss> xml
|
||||||
|
type rssFeedXml struct {
|
||||||
|
XMLName xml.Name `xml:"rss"`
|
||||||
|
Version string `xml:"version,attr"`
|
||||||
|
Channel *RssFeed
|
||||||
|
}
|
||||||
|
|
||||||
|
type RssImage struct {
|
||||||
|
XMLName xml.Name `xml:"image"`
|
||||||
|
Url string `xml:"url"`
|
||||||
|
Title string `xml:"title"`
|
||||||
|
Link string `xml:"link"`
|
||||||
|
Width int `xml:"width,omitempty"`
|
||||||
|
Height int `xml:"height,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RssTextInput struct {
|
||||||
|
XMLName xml.Name `xml:"textInput"`
|
||||||
|
Title string `xml:"title"`
|
||||||
|
Description string `xml:"description"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Link string `xml:"link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RssFeed struct {
|
||||||
|
XMLName xml.Name `xml:"channel"`
|
||||||
|
Title string `xml:"title"` // required
|
||||||
|
Link string `xml:"link"` // required
|
||||||
|
Description string `xml:"description"` // required
|
||||||
|
Language string `xml:"language,omitempty"`
|
||||||
|
Copyright string `xml:"copyright,omitempty"`
|
||||||
|
ManagingEditor string `xml:"managingEditor,omitempty"` // Author used
|
||||||
|
WebMaster string `xml:"webMaster,omitempty"`
|
||||||
|
PubDate string `xml:"pubDate,omitempty"` // created or updated
|
||||||
|
LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used
|
||||||
|
Category string `xml:"category,omitempty"`
|
||||||
|
Generator string `xml:"generator,omitempty"`
|
||||||
|
Docs string `xml:"docs,omitempty"`
|
||||||
|
Cloud string `xml:"cloud,omitempty"`
|
||||||
|
Ttl int `xml:"ttl,omitempty"`
|
||||||
|
Rating string `xml:"rating,omitempty"`
|
||||||
|
SkipHours string `xml:"skipHours,omitempty"`
|
||||||
|
SkipDays string `xml:"skipDays,omitempty"`
|
||||||
|
Image *RssImage
|
||||||
|
TextInput *RssTextInput
|
||||||
|
Items []*RssItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type RssItem struct {
|
||||||
|
XMLName xml.Name `xml:"item"`
|
||||||
|
Title string `xml:"title"` // required
|
||||||
|
Link string `xml:"link"` // required
|
||||||
|
Description string `xml:"description"` // required
|
||||||
|
Author string `xml:"author,omitempty"`
|
||||||
|
Category string `xml:"category,omitempty"`
|
||||||
|
Comments string `xml:"comments,omitempty"`
|
||||||
|
Enclosure *RssEnclosure
|
||||||
|
Guid string `xml:"guid,omitempty"` // Id used
|
||||||
|
PubDate string `xml:"pubDate,omitempty"` // created or updated
|
||||||
|
Source string `xml:"source,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RssEnclosure struct {
|
||||||
|
//RSS 2.0 <enclosure url="http://example.com/file.mp3" length="123456789" type="audio/mpeg" />
|
||||||
|
XMLName xml.Name `xml:"enclosure"`
|
||||||
|
Url string `xml:"url,attr"`
|
||||||
|
Length string `xml:"length,attr"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rss struct {
|
||||||
|
*Feed
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new RssItem with a generic Item struct's data
|
||||||
|
func newRssItem(i *Item) *RssItem {
|
||||||
|
item := &RssItem{
|
||||||
|
Title: i.Title,
|
||||||
|
Link: i.Link.Href,
|
||||||
|
Description: i.Description,
|
||||||
|
Guid: i.Id,
|
||||||
|
PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated),
|
||||||
|
}
|
||||||
|
|
||||||
|
intLength, err := strconv.ParseInt(i.Link.Length, 10, 64)
|
||||||
|
|
||||||
|
if err == nil && (intLength > 0 || i.Link.Type != "") {
|
||||||
|
item.Enclosure = &RssEnclosure{Url: i.Link.Href, Type: i.Link.Type, Length: i.Link.Length}
|
||||||
|
}
|
||||||
|
if i.Author != nil {
|
||||||
|
item.Author = i.Author.Name
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new RssFeed with a generic Feed struct's data
|
||||||
|
func (r *Rss) RssFeed() *RssFeed {
|
||||||
|
pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated)
|
||||||
|
build := anyTimeFormat(time.RFC1123Z, r.Updated)
|
||||||
|
author := ""
|
||||||
|
if r.Author != nil {
|
||||||
|
author = r.Author.Email
|
||||||
|
if len(r.Author.Name) > 0 {
|
||||||
|
author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := &RssFeed{
|
||||||
|
Title: r.Title,
|
||||||
|
Link: r.Link.Href,
|
||||||
|
Description: r.Description,
|
||||||
|
ManagingEditor: author,
|
||||||
|
PubDate: pub,
|
||||||
|
LastBuildDate: build,
|
||||||
|
Copyright: r.Copyright,
|
||||||
|
}
|
||||||
|
for _, i := range r.Items {
|
||||||
|
channel.Items = append(channel.Items, newRssItem(i))
|
||||||
|
}
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
||||||
|
// return an XML-Ready object for an Rss object
|
||||||
|
func (r *Rss) FeedXml() interface{} {
|
||||||
|
// only generate version 2.0 feeds for now
|
||||||
|
return r.RssFeed().FeedXml()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// return an XML-ready object for an RssFeed object
|
||||||
|
func (r *RssFeed) FeedXml() interface{} {
|
||||||
|
return &rssFeedXml{Version: "2.0", Channel: r}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package feeds
|
||||||
|
|
||||||
|
// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UUID [16]byte
|
||||||
|
|
||||||
|
// create a new uuid v4
|
||||||
|
func NewUUID() *UUID {
|
||||||
|
u := &UUID{}
|
||||||
|
_, err := rand.Read(u[:16])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u[8] = (u[8] | 0x80) & 0xBf
|
||||||
|
u[6] = (u[6] | 0x40) & 0x4f
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UUID) String() string {
|
||||||
|
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// Extensions to the standard "os" package.
|
||||||
|
package osext // import "github.com/kardianos/osext"
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
var cx, ce = executableClean()
|
||||||
|
|
||||||
|
func executableClean() (string, error) {
|
||||||
|
p, err := executable()
|
||||||
|
return filepath.Clean(p), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executable returns an absolute path that can be used to
|
||||||
|
// re-invoke the current program.
|
||||||
|
// It may not be valid after the current program exits.
|
||||||
|
func Executable() (string, error) {
|
||||||
|
return cx, ce
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns same path as Executable, returns just the folder
|
||||||
|
// path. Excludes the executable name and any trailing slash.
|
||||||
|
func ExecutableFolder() (string, error) {
|
||||||
|
p, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Dir(p), nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2012 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 osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return syscall.Fd2path(int(f.Fd()))
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// +build linux netbsd solaris dragonfly
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
const deletedTag = " (deleted)"
|
||||||
|
execpath, err := os.Readlink("/proc/self/exe")
|
||||||
|
if err != nil {
|
||||||
|
return execpath, err
|
||||||
|
}
|
||||||
|
execpath = strings.TrimSuffix(execpath, deletedTag)
|
||||||
|
execpath = strings.TrimPrefix(execpath, deletedTag)
|
||||||
|
return execpath, nil
|
||||||
|
case "netbsd":
|
||||||
|
return os.Readlink("/proc/curproc/exe")
|
||||||
|
case "dragonfly":
|
||||||
|
return os.Readlink("/proc/curproc/file")
|
||||||
|
case "solaris":
|
||||||
|
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
||||||
|
}
|
||||||
|
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright 2012 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.
|
||||||
|
|
||||||
|
// +build darwin freebsd openbsd
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initCwd, initCwdErr = os.Getwd()
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
var mib [4]int32
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "freebsd":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
||||||
|
case "darwin":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
||||||
|
case "openbsd":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 55 /* KERN_PROC_ARGS */, int32(os.Getpid()), 1 /* KERN_PROC_ARGV */}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uintptr(0)
|
||||||
|
// Get length.
|
||||||
|
_, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if errNum != 0 {
|
||||||
|
return "", errNum
|
||||||
|
}
|
||||||
|
if n == 0 { // This shouldn't happen.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, n)
|
||||||
|
_, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if errNum != 0 {
|
||||||
|
return "", errNum
|
||||||
|
}
|
||||||
|
if n == 0 { // This shouldn't happen.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var execPath string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "openbsd":
|
||||||
|
// buf now contains **argv, with pointers to each of the C-style
|
||||||
|
// NULL terminated arguments.
|
||||||
|
var args []string
|
||||||
|
argv := uintptr(unsafe.Pointer(&buf[0]))
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
argp := *(**[1 << 20]byte)(unsafe.Pointer(argv))
|
||||||
|
if argp == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := 0; uintptr(i) < n; i++ {
|
||||||
|
// we don't want the full arguments list
|
||||||
|
if string(argp[i]) == " " {
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
if argp[i] != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args = append(args, string(argp[:i]))
|
||||||
|
n -= uintptr(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if n < unsafe.Sizeof(argv) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
argv += unsafe.Sizeof(argv)
|
||||||
|
n -= unsafe.Sizeof(argv)
|
||||||
|
}
|
||||||
|
execPath = args[0]
|
||||||
|
// There is no canonical way to get an executable path on
|
||||||
|
// OpenBSD, so check PATH in case we are called directly
|
||||||
|
if execPath[0] != '/' && execPath[0] != '.' {
|
||||||
|
execIsInPath, err := exec.LookPath(execPath)
|
||||||
|
if err == nil {
|
||||||
|
execPath = execIsInPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for i, v := range buf {
|
||||||
|
if v == 0 {
|
||||||
|
buf = buf[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
execPath = string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// execPath will not be empty due to above checks.
|
||||||
|
// Try to get the absolute path if the execPath is not rooted.
|
||||||
|
if execPath[0] != '/' {
|
||||||
|
execPath, err = getAbs(execPath)
|
||||||
|
if err != nil {
|
||||||
|
return execPath, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For darwin KERN_PROCARGS may return the path to a symlink rather than the
|
||||||
|
// actual executable.
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
|
||||||
|
return execPath, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return execPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAbs(execPath string) (string, error) {
|
||||||
|
if initCwdErr != nil {
|
||||||
|
return execPath, initCwdErr
|
||||||
|
}
|
||||||
|
// The execPath may begin with a "../" or a "./" so clean it first.
|
||||||
|
// Join the two paths, trailing and starting slashes undetermined, so use
|
||||||
|
// the generic Join function.
|
||||||
|
return filepath.Join(initCwd, filepath.Clean(execPath)), nil
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2012 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 osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel = syscall.MustLoadDLL("kernel32.dll")
|
||||||
|
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModuleFileName() with hModule = NULL
|
||||||
|
func executable() (exePath string, err error) {
|
||||||
|
return getModuleFileName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModuleFileName() (string, error) {
|
||||||
|
var n uint32
|
||||||
|
b := make([]uint16, syscall.MAX_PATH)
|
||||||
|
size := uint32(len(b))
|
||||||
|
|
||||||
|
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
||||||
|
n = uint32(r0)
|
||||||
|
if n == 0 {
|
||||||
|
return "", e1
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(b[0:n])), nil
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
// Package errors provides simple error handling primitives.
|
||||||
|
//
|
||||||
|
// The traditional error handling idiom in Go is roughly akin to
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// which applied recursively up the call stack results in error reports
|
||||||
|
// without context or debugging information. The errors package allows
|
||||||
|
// programmers to add context to the failure path in their code in a way
|
||||||
|
// that does not destroy the original value of the error.
|
||||||
|
//
|
||||||
|
// Adding context to an error
|
||||||
|
//
|
||||||
|
// The errors.Wrap function returns a new error that adds context to the
|
||||||
|
// original error by recording a stack trace at the point Wrap is called,
|
||||||
|
// and the supplied message. For example
|
||||||
|
//
|
||||||
|
// _, err := ioutil.ReadAll(r)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.Wrap(err, "read failed")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||||
|
// functions destructure errors.Wrap into its component operations of annotating
|
||||||
|
// an error with a stack trace and an a message, respectively.
|
||||||
|
//
|
||||||
|
// Retrieving the cause of an error
|
||||||
|
//
|
||||||
|
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||||
|
// preceding error. Depending on the nature of the error it may be necessary
|
||||||
|
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||||
|
// for inspection. Any error value which implements this interface
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||||
|
// the topmost error which does not implement causer, which is assumed to be
|
||||||
|
// the original cause. For example:
|
||||||
|
//
|
||||||
|
// switch err := errors.Cause(err).(type) {
|
||||||
|
// case *MyError:
|
||||||
|
// // handle specifically
|
||||||
|
// default:
|
||||||
|
// // unknown error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// causer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// Formatted printing of errors
|
||||||
|
//
|
||||||
|
// All error values returned from this package implement fmt.Formatter and can
|
||||||
|
// be formatted by the fmt package. The following verbs are supported
|
||||||
|
//
|
||||||
|
// %s print the error. If the error has a Cause it will be
|
||||||
|
// printed recursively
|
||||||
|
// %v see %s
|
||||||
|
// %+v extended format. Each Frame of the error's StackTrace will
|
||||||
|
// be printed in detail.
|
||||||
|
//
|
||||||
|
// Retrieving the stack trace of an error or wrapper
|
||||||
|
//
|
||||||
|
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||||
|
// invoked. This information can be retrieved with the following interface.
|
||||||
|
//
|
||||||
|
// type stackTracer interface {
|
||||||
|
// StackTrace() errors.StackTrace
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Where errors.StackTrace is defined as
|
||||||
|
//
|
||||||
|
// type StackTrace []Frame
|
||||||
|
//
|
||||||
|
// The Frame type represents a call site in the stack trace. Frame supports
|
||||||
|
// the fmt.Formatter interface that can be used for printing information about
|
||||||
|
// the stack trace of this error. For example:
|
||||||
|
//
|
||||||
|
// if err, ok := err.(stackTracer); ok {
|
||||||
|
// for _, f := range err.StackTrace() {
|
||||||
|
// fmt.Printf("%+s:%d", f)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// stackTracer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// See the documentation for Frame.Format for more details.
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns an error with the supplied message.
|
||||||
|
// New also records the stack trace at the point it was called.
|
||||||
|
func New(message string) error {
|
||||||
|
return &fundamental{
|
||||||
|
msg: message,
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf formats according to a format specifier and returns the string
|
||||||
|
// as a value that satisfies error.
|
||||||
|
// Errorf also records the stack trace at the point it was called.
|
||||||
|
func Errorf(format string, args ...interface{}) error {
|
||||||
|
return &fundamental{
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
stack: callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fundamental is an error that has a message and a stack, but no caller.
|
||||||
|
type fundamental struct {
|
||||||
|
msg string
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fundamental) Error() string { return f.msg }
|
||||||
|
|
||||||
|
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
f.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, f.msg)
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", f.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||||
|
// If err is nil, WithStack returns nil.
|
||||||
|
func WithStack(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withStack struct {
|
||||||
|
error
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withStack) Cause() error { return w.error }
|
||||||
|
|
||||||
|
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v", w.Cause())
|
||||||
|
w.stack.Format(s, verb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrap is called, and the supplied message.
|
||||||
|
// If err is nil, Wrap returns nil.
|
||||||
|
func Wrap(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrapf is call, and the format specifier.
|
||||||
|
// If err is nil, Wrapf returns nil.
|
||||||
|
func Wrapf(err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
return &withStack{
|
||||||
|
err,
|
||||||
|
callers(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessage annotates err with a new message.
|
||||||
|
// If err is nil, WithMessage returns nil.
|
||||||
|
func WithMessage(err error, message string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &withMessage{
|
||||||
|
cause: err,
|
||||||
|
msg: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withMessage struct {
|
||||||
|
cause error
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||||
|
func (w *withMessage) Cause() error { return w.cause }
|
||||||
|
|
||||||
|
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||||
|
io.WriteString(s, w.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 's', 'q':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cause returns the underlying cause of the error, if possible.
|
||||||
|
// An error value has a cause if it implements the following
|
||||||
|
// interface:
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If the error does not implement Cause, the original error will
|
||||||
|
// be returned. If the error is nil, nil will be returned without further
|
||||||
|
// investigation.
|
||||||
|
func Cause(err error) error {
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
|
||||||
|
for err != nil {
|
||||||
|
cause, ok := err.(causer)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = cause.Cause()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frame represents a program counter inside a stack frame.
|
||||||
|
type Frame uintptr
|
||||||
|
|
||||||
|
// pc returns the program counter for this frame;
|
||||||
|
// multiple frames may have the same PC value.
|
||||||
|
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||||
|
|
||||||
|
// file returns the full path to the file that contains the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) file() string {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
file, _ := fn.FileLine(f.pc())
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// line returns the line number of source code of the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) line() int {
|
||||||
|
fn := runtime.FuncForPC(f.pc())
|
||||||
|
if fn == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
_, line := fn.FileLine(f.pc())
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format formats the frame according to the fmt.Formatter interface.
|
||||||
|
//
|
||||||
|
// %s source file
|
||||||
|
// %d source line
|
||||||
|
// %n function name
|
||||||
|
// %v equivalent to %s:%d
|
||||||
|
//
|
||||||
|
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||||
|
//
|
||||||
|
// %+s path of source file relative to the compile time GOPATH
|
||||||
|
// %+v equivalent to %+s:%d
|
||||||
|
func (f Frame) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 's':
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
pc := f.pc()
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
io.WriteString(s, "unknown")
|
||||||
|
} else {
|
||||||
|
file, _ := fn.FileLine(pc)
|
||||||
|
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
io.WriteString(s, path.Base(f.file()))
|
||||||
|
}
|
||||||
|
case 'd':
|
||||||
|
fmt.Fprintf(s, "%d", f.line())
|
||||||
|
case 'n':
|
||||||
|
name := runtime.FuncForPC(f.pc()).Name()
|
||||||
|
io.WriteString(s, funcname(name))
|
||||||
|
case 'v':
|
||||||
|
f.Format(s, 's')
|
||||||
|
io.WriteString(s, ":")
|
||||||
|
f.Format(s, 'd')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||||
|
type StackTrace []Frame
|
||||||
|
|
||||||
|
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case s.Flag('+'):
|
||||||
|
for _, f := range st {
|
||||||
|
fmt.Fprintf(s, "\n%+v", f)
|
||||||
|
}
|
||||||
|
case s.Flag('#'):
|
||||||
|
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(s, "%v", []Frame(st))
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
fmt.Fprintf(s, "%s", []Frame(st))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack represents a stack of program counters.
|
||||||
|
type stack []uintptr
|
||||||
|
|
||||||
|
func (s *stack) Format(st fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case st.Flag('+'):
|
||||||
|
for _, pc := range *s {
|
||||||
|
f := Frame(pc)
|
||||||
|
fmt.Fprintf(st, "\n%+v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stack) StackTrace() StackTrace {
|
||||||
|
f := make([]Frame, len(*s))
|
||||||
|
for i := 0; i < len(f); i++ {
|
||||||
|
f[i] = Frame((*s)[i])
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func callers() *stack {
|
||||||
|
const depth = 32
|
||||||
|
var pcs [depth]uintptr
|
||||||
|
n := runtime.Callers(3, pcs[:])
|
||||||
|
var st stack = pcs[0:n]
|
||||||
|
return &st
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||||
|
func funcname(name string) string {
|
||||||
|
i := strings.LastIndex(name, "/")
|
||||||
|
name = name[i+1:]
|
||||||
|
i = strings.Index(name, ".")
|
||||||
|
return name[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimGOPATH(name, file string) string {
|
||||||
|
// Here we want to get the source file path relative to the compile time
|
||||||
|
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||||
|
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||||
|
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||||
|
// the import path, which does not include the GOPATH. Thus we can trim
|
||||||
|
// segments from the beginning of the file path until the number of path
|
||||||
|
// separators remaining is one more than the number of path separators in
|
||||||
|
// the function name. For example, given:
|
||||||
|
//
|
||||||
|
// GOPATH /home/user
|
||||||
|
// file /home/user/src/pkg/sub/file.go
|
||||||
|
// fn.Name() pkg/sub.Type.Method
|
||||||
|
//
|
||||||
|
// We want to produce:
|
||||||
|
//
|
||||||
|
// pkg/sub/file.go
|
||||||
|
//
|
||||||
|
// From this we can easily see that fn.Name() has one less path separator
|
||||||
|
// than our desired output. We count separators from the end of the file
|
||||||
|
// path until it finds two more than in the function name and then move
|
||||||
|
// one character forward to preserve the initial path segment without a
|
||||||
|
// leading separator.
|
||||||
|
const sep = "/"
|
||||||
|
goal := strings.Count(name, sep) + 2
|
||||||
|
i := len(file)
|
||||||
|
for n := 0; n < goal; n++ {
|
||||||
|
i = strings.LastIndex(file[:i], sep)
|
||||||
|
if i == -1 {
|
||||||
|
// not enough separators found, set i so that the slice expression
|
||||||
|
// below leaves file unmodified
|
||||||
|
i = -len(sep)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get back to 0 or trim the leading separator
|
||||||
|
file = file[i+len(sep):]
|
||||||
|
return file
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
||||||
|
// Package blackfriday is a Markdown processor.
|
||||||
|
//
|
||||||
|
// It translates plain text with simple formatting rules into HTML or LaTeX.
|
||||||
|
//
|
||||||
|
// Sanitized Anchor Names
|
||||||
|
//
|
||||||
|
// Blackfriday includes an algorithm for creating sanitized anchor names
|
||||||
|
// corresponding to a given input text. This algorithm is used to create
|
||||||
|
// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The
|
||||||
|
// algorithm is specified below, so that other packages can create
|
||||||
|
// compatible anchor names and links to those anchors.
|
||||||
|
//
|
||||||
|
// The algorithm iterates over the input text, interpreted as UTF-8,
|
||||||
|
// one Unicode code point (rune) at a time. All runes that are letters (category L)
|
||||||
|
// or numbers (category N) are considered valid characters. They are mapped to
|
||||||
|
// lower case, and included in the output. All other runes are considered
|
||||||
|
// invalid characters. Invalid characters that preceed the first valid character,
|
||||||
|
// as well as invalid character that follow the last valid character
|
||||||
|
// are dropped completely. All other sequences of invalid characters
|
||||||
|
// between two valid characters are replaced with a single dash character '-'.
|
||||||
|
//
|
||||||
|
// SanitizedAnchorName exposes this functionality, and can be used to
|
||||||
|
// create compatible links to the anchor names generated by blackfriday.
|
||||||
|
// This algorithm is also implemented in a small standalone package at
|
||||||
|
// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
|
||||||
|
// that want a small package and don't need full functionality of blackfriday.
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
|
||||||
|
// github.com/shurcooL/sanitized_anchor_name.
|
||||||
|
// Otherwise, users of sanitized_anchor_name will get anchor names
|
||||||
|
// that are incompatible with those generated by blackfriday.
|
|
@ -0,0 +1,949 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// HTML rendering backend
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Html renderer configuration options.
|
||||||
|
const (
|
||||||
|
HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks
|
||||||
|
HTML_SKIP_STYLE // skip embedded <style> elements
|
||||||
|
HTML_SKIP_IMAGES // skip embedded images
|
||||||
|
HTML_SKIP_LINKS // skip all links
|
||||||
|
HTML_SAFELINK // only link to trusted protocols
|
||||||
|
HTML_NOFOLLOW_LINKS // only link with rel="nofollow"
|
||||||
|
HTML_NOREFERRER_LINKS // only link with rel="noreferrer"
|
||||||
|
HTML_HREF_TARGET_BLANK // add a blank target
|
||||||
|
HTML_TOC // generate a table of contents
|
||||||
|
HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents)
|
||||||
|
HTML_COMPLETE_PAGE // generate a complete HTML page
|
||||||
|
HTML_USE_XHTML // generate XHTML output instead of HTML
|
||||||
|
HTML_USE_SMARTYPANTS // enable smart punctuation substitutions
|
||||||
|
HTML_SMARTYPANTS_FRACTIONS // enable smart fractions (with HTML_USE_SMARTYPANTS)
|
||||||
|
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
|
||||||
|
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
|
||||||
|
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
|
||||||
|
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alignments = []string{
|
||||||
|
"left",
|
||||||
|
"right",
|
||||||
|
"center",
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: improve this regexp to catch all possible entities:
|
||||||
|
htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type HtmlRendererParameters struct {
|
||||||
|
// Prepend this text to each relative URL.
|
||||||
|
AbsolutePrefix string
|
||||||
|
// Add this text to each footnote anchor, to ensure uniqueness.
|
||||||
|
FootnoteAnchorPrefix string
|
||||||
|
// Show this text inside the <a> tag for a footnote return link, if the
|
||||||
|
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
|
||||||
|
// <sup>[return]</sup> is used.
|
||||||
|
FootnoteReturnLinkContents string
|
||||||
|
// If set, add this text to the front of each Header ID, to ensure
|
||||||
|
// uniqueness.
|
||||||
|
HeaderIDPrefix string
|
||||||
|
// If set, add this text to the back of each Header ID, to ensure uniqueness.
|
||||||
|
HeaderIDSuffix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Html is a type that implements the Renderer interface for HTML output.
|
||||||
|
//
|
||||||
|
// Do not create this directly, instead use the HtmlRenderer function.
|
||||||
|
type Html struct {
|
||||||
|
flags int // HTML_* options
|
||||||
|
closeTag string // how to end singleton tags: either " />" or ">"
|
||||||
|
title string // document title
|
||||||
|
css string // optional css file url (used with HTML_COMPLETE_PAGE)
|
||||||
|
|
||||||
|
parameters HtmlRendererParameters
|
||||||
|
|
||||||
|
// table of contents data
|
||||||
|
tocMarker int
|
||||||
|
headerCount int
|
||||||
|
currentLevel int
|
||||||
|
toc *bytes.Buffer
|
||||||
|
|
||||||
|
// Track header IDs to prevent ID collision in a single generation.
|
||||||
|
headerIDs map[string]int
|
||||||
|
|
||||||
|
smartypants *smartypantsRenderer
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
xhtmlClose = " />"
|
||||||
|
htmlClose = ">"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HtmlRenderer creates and configures an Html object, which
|
||||||
|
// satisfies the Renderer interface.
|
||||||
|
//
|
||||||
|
// flags is a set of HTML_* options ORed together.
|
||||||
|
// title is the title of the document, and css is a URL for the document's
|
||||||
|
// stylesheet.
|
||||||
|
// title and css are only used when HTML_COMPLETE_PAGE is selected.
|
||||||
|
func HtmlRenderer(flags int, title string, css string) Renderer {
|
||||||
|
return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func HtmlRendererWithParameters(flags int, title string,
|
||||||
|
css string, renderParameters HtmlRendererParameters) Renderer {
|
||||||
|
// configure the rendering engine
|
||||||
|
closeTag := htmlClose
|
||||||
|
if flags&HTML_USE_XHTML != 0 {
|
||||||
|
closeTag = xhtmlClose
|
||||||
|
}
|
||||||
|
|
||||||
|
if renderParameters.FootnoteReturnLinkContents == "" {
|
||||||
|
renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Html{
|
||||||
|
flags: flags,
|
||||||
|
closeTag: closeTag,
|
||||||
|
title: title,
|
||||||
|
css: css,
|
||||||
|
parameters: renderParameters,
|
||||||
|
|
||||||
|
headerCount: 0,
|
||||||
|
currentLevel: 0,
|
||||||
|
toc: new(bytes.Buffer),
|
||||||
|
|
||||||
|
headerIDs: make(map[string]int),
|
||||||
|
|
||||||
|
smartypants: smartypants(flags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using if statements is a bit faster than a switch statement. As the compiler
|
||||||
|
// improves, this should be unnecessary this is only worthwhile because
|
||||||
|
// attrEscape is the single largest CPU user in normal use.
|
||||||
|
// Also tried using map, but that gave a ~3x slowdown.
|
||||||
|
func escapeSingleChar(char byte) (string, bool) {
|
||||||
|
if char == '"' {
|
||||||
|
return """, true
|
||||||
|
}
|
||||||
|
if char == '&' {
|
||||||
|
return "&", true
|
||||||
|
}
|
||||||
|
if char == '<' {
|
||||||
|
return "<", true
|
||||||
|
}
|
||||||
|
if char == '>' {
|
||||||
|
return ">", true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func attrEscape(out *bytes.Buffer, src []byte) {
|
||||||
|
org := 0
|
||||||
|
for i, ch := range src {
|
||||||
|
if entity, ok := escapeSingleChar(ch); ok {
|
||||||
|
if i > org {
|
||||||
|
// copy all the normal characters since the last escape
|
||||||
|
out.Write(src[org:i])
|
||||||
|
}
|
||||||
|
org = i + 1
|
||||||
|
out.WriteString(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if org < len(src) {
|
||||||
|
out.Write(src[org:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) {
|
||||||
|
end := 0
|
||||||
|
for _, rang := range skipRanges {
|
||||||
|
attrEscape(out, src[end:rang[0]])
|
||||||
|
out.Write(src[rang[0]:rang[1]])
|
||||||
|
end = rang[1]
|
||||||
|
}
|
||||||
|
attrEscape(out, src[end:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) GetFlags() int {
|
||||||
|
return options.flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) {
|
||||||
|
text = bytes.TrimPrefix(text, []byte("% "))
|
||||||
|
text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
|
||||||
|
out.WriteString("<h1 class=\"title\">")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n</h1>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||||
|
marker := out.Len()
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
if id == "" && options.flags&HTML_TOC != 0 {
|
||||||
|
id = fmt.Sprintf("toc_%d", options.headerCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id != "" {
|
||||||
|
id = options.ensureUniqueHeaderID(id)
|
||||||
|
|
||||||
|
if options.parameters.HeaderIDPrefix != "" {
|
||||||
|
id = options.parameters.HeaderIDPrefix + id
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.parameters.HeaderIDSuffix != "" {
|
||||||
|
id = id + options.parameters.HeaderIDSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
|
||||||
|
} else {
|
||||||
|
out.WriteString(fmt.Sprintf("<h%d>", level))
|
||||||
|
}
|
||||||
|
|
||||||
|
tocMarker := out.Len()
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// are we building a table of contents?
|
||||||
|
if options.flags&HTML_TOC != 0 {
|
||||||
|
options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString(fmt.Sprintf("</h%d>\n", level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) {
|
||||||
|
if options.flags&HTML_SKIP_HTML != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
doubleSpace(out)
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) HRule(out *bytes.Buffer) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<hr")
|
||||||
|
out.WriteString(options.closeTag)
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
// parse out the language names/classes
|
||||||
|
count := 0
|
||||||
|
for _, elt := range strings.Fields(lang) {
|
||||||
|
if elt[0] == '.' {
|
||||||
|
elt = elt[1:]
|
||||||
|
}
|
||||||
|
if len(elt) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
out.WriteString("<pre><code class=\"language-")
|
||||||
|
} else {
|
||||||
|
out.WriteByte(' ')
|
||||||
|
}
|
||||||
|
attrEscape(out, []byte(elt))
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
out.WriteString("<pre><code>")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\">")
|
||||||
|
}
|
||||||
|
|
||||||
|
attrEscape(out, text)
|
||||||
|
out.WriteString("</code></pre>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<blockquote>\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</blockquote>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<table>\n<thead>\n")
|
||||||
|
out.Write(header)
|
||||||
|
out.WriteString("</thead>\n\n<tbody>\n")
|
||||||
|
out.Write(body)
|
||||||
|
out.WriteString("</tbody>\n</table>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TableRow(out *bytes.Buffer, text []byte) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<tr>\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n</tr>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
doubleSpace(out)
|
||||||
|
switch align {
|
||||||
|
case TABLE_ALIGNMENT_LEFT:
|
||||||
|
out.WriteString("<th align=\"left\">")
|
||||||
|
case TABLE_ALIGNMENT_RIGHT:
|
||||||
|
out.WriteString("<th align=\"right\">")
|
||||||
|
case TABLE_ALIGNMENT_CENTER:
|
||||||
|
out.WriteString("<th align=\"center\">")
|
||||||
|
default:
|
||||||
|
out.WriteString("<th>")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</th>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
doubleSpace(out)
|
||||||
|
switch align {
|
||||||
|
case TABLE_ALIGNMENT_LEFT:
|
||||||
|
out.WriteString("<td align=\"left\">")
|
||||||
|
case TABLE_ALIGNMENT_RIGHT:
|
||||||
|
out.WriteString("<td align=\"right\">")
|
||||||
|
case TABLE_ALIGNMENT_CENTER:
|
||||||
|
out.WriteString("<td align=\"center\">")
|
||||||
|
default:
|
||||||
|
out.WriteString("<td>")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</td>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) {
|
||||||
|
out.WriteString("<div class=\"footnotes\">\n")
|
||||||
|
options.HRule(out)
|
||||||
|
options.List(out, text, LIST_TYPE_ORDERED)
|
||||||
|
out.WriteString("</div>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
|
||||||
|
if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||||
|
doubleSpace(out)
|
||||||
|
}
|
||||||
|
slug := slugify(name)
|
||||||
|
out.WriteString(`<li id="`)
|
||||||
|
out.WriteString(`fn:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`">`)
|
||||||
|
out.Write(text)
|
||||||
|
if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 {
|
||||||
|
out.WriteString(` <a class="footnote-return" href="#`)
|
||||||
|
out.WriteString(`fnref:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`">`)
|
||||||
|
out.WriteString(options.parameters.FootnoteReturnLinkContents)
|
||||||
|
out.WriteString(`</a>`)
|
||||||
|
}
|
||||||
|
out.WriteString("</li>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||||
|
marker := out.Len()
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("<dl>")
|
||||||
|
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("<ol>")
|
||||||
|
} else {
|
||||||
|
out.WriteString("<ul>")
|
||||||
|
}
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("</dl>\n")
|
||||||
|
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("</ol>\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("</ul>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||||
|
if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) ||
|
||||||
|
flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||||
|
doubleSpace(out)
|
||||||
|
}
|
||||||
|
if flags&LIST_TYPE_TERM != 0 {
|
||||||
|
out.WriteString("<dt>")
|
||||||
|
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("<dd>")
|
||||||
|
} else {
|
||||||
|
out.WriteString("<li>")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
if flags&LIST_TYPE_TERM != 0 {
|
||||||
|
out.WriteString("</dt>\n")
|
||||||
|
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("</dd>\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("</li>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||||
|
marker := out.Len()
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
out.WriteString("<p>")
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("</p>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||||
|
skipRanges := htmlEntity.FindAllIndex(link, -1)
|
||||||
|
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL {
|
||||||
|
// mark it but don't link it if it is not a safe link: no smartypants
|
||||||
|
out.WriteString("<tt>")
|
||||||
|
entityEscapeWithSkip(out, link, skipRanges)
|
||||||
|
out.WriteString("</tt>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("<a href=\"")
|
||||||
|
if kind == LINK_TYPE_EMAIL {
|
||||||
|
out.WriteString("mailto:")
|
||||||
|
} else {
|
||||||
|
options.maybeWriteAbsolutePrefix(out, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
entityEscapeWithSkip(out, link, skipRanges)
|
||||||
|
|
||||||
|
var relAttrs []string
|
||||||
|
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "nofollow")
|
||||||
|
}
|
||||||
|
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "noreferrer")
|
||||||
|
}
|
||||||
|
if len(relAttrs) > 0 {
|
||||||
|
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// blank target only add to external link
|
||||||
|
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
|
||||||
|
out.WriteString("\" target=\"_blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("\">")
|
||||||
|
|
||||||
|
// Pretty print: if we get an email address as
|
||||||
|
// an actual URI, e.g. `mailto:foo@bar.com`, we don't
|
||||||
|
// want to print the `mailto:` prefix
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(link, []byte("mailto://")):
|
||||||
|
attrEscape(out, link[len("mailto://"):])
|
||||||
|
case bytes.HasPrefix(link, []byte("mailto:")):
|
||||||
|
attrEscape(out, link[len("mailto:"):])
|
||||||
|
default:
|
||||||
|
entityEscapeWithSkip(out, link, skipRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("</a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<code>")
|
||||||
|
attrEscape(out, text)
|
||||||
|
out.WriteString("</code>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<strong>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</strong>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Emphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
if len(text) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("<em>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</em>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) {
|
||||||
|
if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
|
||||||
|
out.WriteString(options.parameters.AbsolutePrefix)
|
||||||
|
if link[0] != '/' {
|
||||||
|
out.WriteByte('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||||
|
if options.flags&HTML_SKIP_IMAGES != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("<img src=\"")
|
||||||
|
options.maybeWriteAbsolutePrefix(out, link)
|
||||||
|
attrEscape(out, link)
|
||||||
|
out.WriteString("\" alt=\"")
|
||||||
|
if len(alt) > 0 {
|
||||||
|
attrEscape(out, alt)
|
||||||
|
}
|
||||||
|
if len(title) > 0 {
|
||||||
|
out.WriteString("\" title=\"")
|
||||||
|
attrEscape(out, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte('"')
|
||||||
|
out.WriteString(options.closeTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) LineBreak(out *bytes.Buffer) {
|
||||||
|
out.WriteString("<br")
|
||||||
|
out.WriteString(options.closeTag)
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||||
|
if options.flags&HTML_SKIP_LINKS != 0 {
|
||||||
|
// write the link text out but don't link it, just mark it with typewriter font
|
||||||
|
out.WriteString("<tt>")
|
||||||
|
attrEscape(out, content)
|
||||||
|
out.WriteString("</tt>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) {
|
||||||
|
// write the link text out but don't link it, just mark it with typewriter font
|
||||||
|
out.WriteString("<tt>")
|
||||||
|
attrEscape(out, content)
|
||||||
|
out.WriteString("</tt>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("<a href=\"")
|
||||||
|
options.maybeWriteAbsolutePrefix(out, link)
|
||||||
|
attrEscape(out, link)
|
||||||
|
if len(title) > 0 {
|
||||||
|
out.WriteString("\" title=\"")
|
||||||
|
attrEscape(out, title)
|
||||||
|
}
|
||||||
|
var relAttrs []string
|
||||||
|
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "nofollow")
|
||||||
|
}
|
||||||
|
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "noreferrer")
|
||||||
|
}
|
||||||
|
if len(relAttrs) > 0 {
|
||||||
|
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// blank target only add to external link
|
||||||
|
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
|
||||||
|
out.WriteString("\" target=\"_blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("\">")
|
||||||
|
out.Write(content)
|
||||||
|
out.WriteString("</a>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) {
|
||||||
|
if options.flags&HTML_SKIP_HTML != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<strong><em>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</em></strong>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<del>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</del>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
||||||
|
slug := slugify(ref)
|
||||||
|
out.WriteString(`<sup class="footnote-ref" id="`)
|
||||||
|
out.WriteString(`fnref:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`"><a rel="footnote" href="#`)
|
||||||
|
out.WriteString(`fn:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`">`)
|
||||||
|
out.WriteString(strconv.Itoa(id))
|
||||||
|
out.WriteString(`</a></sup>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Entity(out *bytes.Buffer, entity []byte) {
|
||||||
|
out.Write(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) NormalText(out *bytes.Buffer, text []byte) {
|
||||||
|
if options.flags&HTML_USE_SMARTYPANTS != 0 {
|
||||||
|
options.Smartypants(out, text)
|
||||||
|
} else {
|
||||||
|
attrEscape(out, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Smartypants(out *bytes.Buffer, text []byte) {
|
||||||
|
smrt := smartypantsData{false, false}
|
||||||
|
|
||||||
|
// first do normal entity escaping
|
||||||
|
var escaped bytes.Buffer
|
||||||
|
attrEscape(&escaped, text)
|
||||||
|
text = escaped.Bytes()
|
||||||
|
|
||||||
|
mark := 0
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
if action := options.smartypants[text[i]]; action != nil {
|
||||||
|
if i > mark {
|
||||||
|
out.Write(text[mark:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
previousChar := byte(0)
|
||||||
|
if i > 0 {
|
||||||
|
previousChar = text[i-1]
|
||||||
|
}
|
||||||
|
i += action(out, &smrt, previousChar, text[i:])
|
||||||
|
mark = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mark < len(text) {
|
||||||
|
out.Write(text[mark:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) DocumentHeader(out *bytes.Buffer) {
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ending := ""
|
||||||
|
if options.flags&HTML_USE_XHTML != 0 {
|
||||||
|
out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
|
||||||
|
out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
|
||||||
|
out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
|
||||||
|
ending = " /"
|
||||||
|
} else {
|
||||||
|
out.WriteString("<!DOCTYPE html>\n")
|
||||||
|
out.WriteString("<html>\n")
|
||||||
|
}
|
||||||
|
out.WriteString("<head>\n")
|
||||||
|
out.WriteString(" <title>")
|
||||||
|
options.NormalText(out, []byte(options.title))
|
||||||
|
out.WriteString("</title>\n")
|
||||||
|
out.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
|
||||||
|
out.WriteString(VERSION)
|
||||||
|
out.WriteString("\"")
|
||||||
|
out.WriteString(ending)
|
||||||
|
out.WriteString(">\n")
|
||||||
|
out.WriteString(" <meta charset=\"utf-8\"")
|
||||||
|
out.WriteString(ending)
|
||||||
|
out.WriteString(">\n")
|
||||||
|
if options.css != "" {
|
||||||
|
out.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")
|
||||||
|
attrEscape(out, []byte(options.css))
|
||||||
|
out.WriteString("\"")
|
||||||
|
out.WriteString(ending)
|
||||||
|
out.WriteString(">\n")
|
||||||
|
}
|
||||||
|
out.WriteString("</head>\n")
|
||||||
|
out.WriteString("<body>\n")
|
||||||
|
|
||||||
|
options.tocMarker = out.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) DocumentFooter(out *bytes.Buffer) {
|
||||||
|
// finalize and insert the table of contents
|
||||||
|
if options.flags&HTML_TOC != 0 {
|
||||||
|
options.TocFinalize()
|
||||||
|
|
||||||
|
// now we have to insert the table of contents into the document
|
||||||
|
var temp bytes.Buffer
|
||||||
|
|
||||||
|
// start by making a copy of everything after the document header
|
||||||
|
temp.Write(out.Bytes()[options.tocMarker:])
|
||||||
|
|
||||||
|
// now clear the copied material from the main output buffer
|
||||||
|
out.Truncate(options.tocMarker)
|
||||||
|
|
||||||
|
// corner case spacing issue
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE != 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the table of contents
|
||||||
|
out.WriteString("<nav>\n")
|
||||||
|
out.Write(options.toc.Bytes())
|
||||||
|
out.WriteString("</nav>\n")
|
||||||
|
|
||||||
|
// corner case spacing issue
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out everything that came after it
|
||||||
|
if options.flags&HTML_OMIT_CONTENTS == 0 {
|
||||||
|
out.Write(temp.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE != 0 {
|
||||||
|
out.WriteString("\n</body>\n")
|
||||||
|
out.WriteString("</html>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) {
|
||||||
|
for level > options.currentLevel {
|
||||||
|
switch {
|
||||||
|
case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")):
|
||||||
|
// this sublist can nest underneath a header
|
||||||
|
size := options.toc.Len()
|
||||||
|
options.toc.Truncate(size - len("</li>\n"))
|
||||||
|
|
||||||
|
case options.currentLevel > 0:
|
||||||
|
options.toc.WriteString("<li>")
|
||||||
|
}
|
||||||
|
if options.toc.Len() > 0 {
|
||||||
|
options.toc.WriteByte('\n')
|
||||||
|
}
|
||||||
|
options.toc.WriteString("<ul>\n")
|
||||||
|
options.currentLevel++
|
||||||
|
}
|
||||||
|
|
||||||
|
for level < options.currentLevel {
|
||||||
|
options.toc.WriteString("</ul>")
|
||||||
|
if options.currentLevel > 1 {
|
||||||
|
options.toc.WriteString("</li>\n")
|
||||||
|
}
|
||||||
|
options.currentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
options.toc.WriteString("<li><a href=\"#")
|
||||||
|
if anchor != "" {
|
||||||
|
options.toc.WriteString(anchor)
|
||||||
|
} else {
|
||||||
|
options.toc.WriteString("toc_")
|
||||||
|
options.toc.WriteString(strconv.Itoa(options.headerCount))
|
||||||
|
}
|
||||||
|
options.toc.WriteString("\">")
|
||||||
|
options.headerCount++
|
||||||
|
|
||||||
|
options.toc.Write(text)
|
||||||
|
|
||||||
|
options.toc.WriteString("</a></li>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TocHeader(text []byte, level int) {
|
||||||
|
options.TocHeaderWithAnchor(text, level, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TocFinalize() {
|
||||||
|
for options.currentLevel > 1 {
|
||||||
|
options.toc.WriteString("</ul></li>\n")
|
||||||
|
options.currentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.currentLevel > 0 {
|
||||||
|
options.toc.WriteString("</ul>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHtmlTag(tag []byte, tagname string) bool {
|
||||||
|
found, _ := findHtmlTagPos(tag, tagname)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for a character, but ignore it when it's in any kind of quotes, it
|
||||||
|
// might be JavaScript
|
||||||
|
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
|
||||||
|
inSingleQuote := false
|
||||||
|
inDoubleQuote := false
|
||||||
|
inGraveQuote := false
|
||||||
|
i := start
|
||||||
|
for i < len(html) {
|
||||||
|
switch {
|
||||||
|
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
|
||||||
|
return i
|
||||||
|
case html[i] == '\'':
|
||||||
|
inSingleQuote = !inSingleQuote
|
||||||
|
case html[i] == '"':
|
||||||
|
inDoubleQuote = !inDoubleQuote
|
||||||
|
case html[i] == '`':
|
||||||
|
inGraveQuote = !inGraveQuote
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
|
||||||
|
i := 0
|
||||||
|
if i < len(tag) && tag[0] != '<' {
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
i = skipSpace(tag, i)
|
||||||
|
|
||||||
|
if i < len(tag) && tag[i] == '/' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
i = skipSpace(tag, i)
|
||||||
|
j := 0
|
||||||
|
for ; i < len(tag); i, j = i+1, j+1 {
|
||||||
|
if j >= len(tagname) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == len(tag) {
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
|
||||||
|
if rightAngle > i {
|
||||||
|
return true, rightAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipUntilChar(text []byte, start int, char byte) int {
|
||||||
|
i := start
|
||||||
|
for i < len(text) && text[i] != char {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipSpace(tag []byte, i int) int {
|
||||||
|
for i < len(tag) && isspace(tag[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipChar(data []byte, start int, char byte) int {
|
||||||
|
i := start
|
||||||
|
for i < len(data) && data[i] == char {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func doubleSpace(out *bytes.Buffer) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRelativeLink(link []byte) (yes bool) {
|
||||||
|
// a tag begin with '#'
|
||||||
|
if link[0] == '#' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// link begin with '/' but not '//', the second maybe a protocol relative link
|
||||||
|
if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// only the root '/'
|
||||||
|
if len(link) == 1 && link[0] == '/' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// current directory : begin with "./"
|
||||||
|
if bytes.HasPrefix(link, []byte("./")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent directory : begin with "../"
|
||||||
|
if bytes.HasPrefix(link, []byte("../")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) ensureUniqueHeaderID(id string) string {
|
||||||
|
for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] {
|
||||||
|
tmp := fmt.Sprintf("%s-%d", id, count+1)
|
||||||
|
|
||||||
|
if _, tmpFound := options.headerIDs[tmp]; !tmpFound {
|
||||||
|
options.headerIDs[id] = count + 1
|
||||||
|
id = tmp
|
||||||
|
} else {
|
||||||
|
id = id + "-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := options.headerIDs[id]; !found {
|
||||||
|
options.headerIDs[id] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,332 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// LaTeX rendering backend
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Latex is a type that implements the Renderer interface for LaTeX output.
|
||||||
|
//
|
||||||
|
// Do not create this directly, instead use the LatexRenderer function.
|
||||||
|
type Latex struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatexRenderer creates and configures a Latex object, which
|
||||||
|
// satisfies the Renderer interface.
|
||||||
|
//
|
||||||
|
// flags is a set of LATEX_* options ORed together (currently no such options
|
||||||
|
// are defined).
|
||||||
|
func LatexRenderer(flags int) Renderer {
|
||||||
|
return &Latex{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) GetFlags() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// render code chunks using verbatim, or listings if we have a language
|
||||||
|
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||||
|
if lang == "" {
|
||||||
|
out.WriteString("\n\\begin{verbatim}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\begin{lstlisting}[language=")
|
||||||
|
out.WriteString(lang)
|
||||||
|
out.WriteString("]\n")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
if lang == "" {
|
||||||
|
out.WriteString("\n\\end{verbatim}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\end{lstlisting}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\n\\begin{quotation}\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n\\end{quotation}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
|
||||||
|
// a pretty lame thing to do...
|
||||||
|
out.WriteString("\n\\begin{verbatim}\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n\\end{verbatim}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||||
|
marker := out.Len()
|
||||||
|
|
||||||
|
switch level {
|
||||||
|
case 1:
|
||||||
|
out.WriteString("\n\\section{")
|
||||||
|
case 2:
|
||||||
|
out.WriteString("\n\\subsection{")
|
||||||
|
case 3:
|
||||||
|
out.WriteString("\n\\subsubsection{")
|
||||||
|
case 4:
|
||||||
|
out.WriteString("\n\\paragraph{")
|
||||||
|
case 5:
|
||||||
|
out.WriteString("\n\\subparagraph{")
|
||||||
|
case 6:
|
||||||
|
out.WriteString("\n\\textbf{")
|
||||||
|
}
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) HRule(out *bytes.Buffer) {
|
||||||
|
out.WriteString("\n\\HRule\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||||
|
marker := out.Len()
|
||||||
|
if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("\n\\begin{enumerate}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\begin{itemize}\n")
|
||||||
|
}
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("\n\\end{enumerate}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\end{itemize}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||||
|
out.WriteString("\n\\item ")
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||||
|
marker := out.Len()
|
||||||
|
out.WriteString("\n")
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
||||||
|
out.WriteString("\n\\begin{tabular}{")
|
||||||
|
for _, elt := range columnData {
|
||||||
|
switch elt {
|
||||||
|
case TABLE_ALIGNMENT_LEFT:
|
||||||
|
out.WriteByte('l')
|
||||||
|
case TABLE_ALIGNMENT_RIGHT:
|
||||||
|
out.WriteByte('r')
|
||||||
|
default:
|
||||||
|
out.WriteByte('c')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.WriteString("}\n")
|
||||||
|
out.Write(header)
|
||||||
|
out.WriteString(" \\\\\n\\hline\n")
|
||||||
|
out.Write(body)
|
||||||
|
out.WriteString("\n\\end{tabular}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteString(" \\\\\n")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteString(" & ")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteString(" & ")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this
|
||||||
|
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||||
|
out.WriteString("\\href{")
|
||||||
|
if kind == LINK_TYPE_EMAIL {
|
||||||
|
out.WriteString("mailto:")
|
||||||
|
}
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\texttt{")
|
||||||
|
escapeSpecialChars(out, text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\textbf{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\textit{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||||
|
if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
|
||||||
|
// treat it like a link
|
||||||
|
out.WriteString("\\href{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}{")
|
||||||
|
out.Write(alt)
|
||||||
|
out.WriteString("}")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\\includegraphics{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) LineBreak(out *bytes.Buffer) {
|
||||||
|
out.WriteString(" \\\\\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||||
|
out.WriteString("\\href{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}{")
|
||||||
|
out.Write(content)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\textbf{\\textit{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\sout{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this
|
||||||
|
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func needsBackslash(c byte) bool {
|
||||||
|
for _, r := range []byte("_{}%$&\\~#") {
|
||||||
|
if c == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
// directly copy normal characters
|
||||||
|
org := i
|
||||||
|
|
||||||
|
for i < len(text) && !needsBackslash(text[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i > org {
|
||||||
|
out.Write(text[org:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// escape a character
|
||||||
|
if i >= len(text) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out.WriteByte('\\')
|
||||||
|
out.WriteByte(text[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
|
||||||
|
// TODO: convert this into a unicode character or something
|
||||||
|
out.Write(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
|
||||||
|
escapeSpecialChars(out, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// header and footer
|
||||||
|
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
|
||||||
|
out.WriteString("\\documentclass{article}\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\usepackage{graphicx}\n")
|
||||||
|
out.WriteString("\\usepackage{listings}\n")
|
||||||
|
out.WriteString("\\usepackage[margin=1in]{geometry}\n")
|
||||||
|
out.WriteString("\\usepackage[utf8]{inputenc}\n")
|
||||||
|
out.WriteString("\\usepackage{verbatim}\n")
|
||||||
|
out.WriteString("\\usepackage[normalem]{ulem}\n")
|
||||||
|
out.WriteString("\\usepackage{hyperref}\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\hypersetup{colorlinks,%\n")
|
||||||
|
out.WriteString(" citecolor=black,%\n")
|
||||||
|
out.WriteString(" filecolor=black,%\n")
|
||||||
|
out.WriteString(" linkcolor=black,%\n")
|
||||||
|
out.WriteString(" urlcolor=black,%\n")
|
||||||
|
out.WriteString(" pdfstartview=FitH,%\n")
|
||||||
|
out.WriteString(" breaklinks=true,%\n")
|
||||||
|
out.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
|
||||||
|
out.WriteString(VERSION)
|
||||||
|
out.WriteString("}}\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
|
||||||
|
out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
|
||||||
|
out.WriteString("\\parindent=0pt\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\begin{document}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
|
||||||
|
out.WriteString("\n\\end{document}\n")
|
||||||
|
}
|
|
@ -0,0 +1,924 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Markdown parsing and processing
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const VERSION = "1.5"
|
||||||
|
|
||||||
|
// These are the supported markdown parsing extensions.
|
||||||
|
// OR these values together to select multiple extensions.
|
||||||
|
const (
|
||||||
|
EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words
|
||||||
|
EXTENSION_TABLES // render tables
|
||||||
|
EXTENSION_FENCED_CODE // render fenced code blocks
|
||||||
|
EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked
|
||||||
|
EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~
|
||||||
|
EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules
|
||||||
|
EXTENSION_SPACE_HEADERS // be strict about prefix header rules
|
||||||
|
EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks
|
||||||
|
EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four
|
||||||
|
EXTENSION_FOOTNOTES // Pandoc-style footnotes
|
||||||
|
EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
||||||
|
EXTENSION_HEADER_IDS // specify header IDs with {#id}
|
||||||
|
EXTENSION_TITLEBLOCK // Titleblock ala pandoc
|
||||||
|
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
|
||||||
|
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
|
||||||
|
EXTENSION_DEFINITION_LISTS // render definition lists
|
||||||
|
EXTENSION_JOIN_LINES // delete newline and join lines
|
||||||
|
|
||||||
|
commonHtmlFlags = 0 |
|
||||||
|
HTML_USE_XHTML |
|
||||||
|
HTML_USE_SMARTYPANTS |
|
||||||
|
HTML_SMARTYPANTS_FRACTIONS |
|
||||||
|
HTML_SMARTYPANTS_DASHES |
|
||||||
|
HTML_SMARTYPANTS_LATEX_DASHES
|
||||||
|
|
||||||
|
commonExtensions = 0 |
|
||||||
|
EXTENSION_NO_INTRA_EMPHASIS |
|
||||||
|
EXTENSION_TABLES |
|
||||||
|
EXTENSION_FENCED_CODE |
|
||||||
|
EXTENSION_AUTOLINK |
|
||||||
|
EXTENSION_STRIKETHROUGH |
|
||||||
|
EXTENSION_SPACE_HEADERS |
|
||||||
|
EXTENSION_HEADER_IDS |
|
||||||
|
EXTENSION_BACKSLASH_LINE_BREAK |
|
||||||
|
EXTENSION_DEFINITION_LISTS
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the possible flag values for the link renderer.
|
||||||
|
// Only a single one of these values will be used; they are not ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const (
|
||||||
|
LINK_TYPE_NOT_AUTOLINK = iota
|
||||||
|
LINK_TYPE_NORMAL
|
||||||
|
LINK_TYPE_EMAIL
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the possible flag values for the ListItem renderer.
|
||||||
|
// Multiple flag values may be ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const (
|
||||||
|
LIST_TYPE_ORDERED = 1 << iota
|
||||||
|
LIST_TYPE_DEFINITION
|
||||||
|
LIST_TYPE_TERM
|
||||||
|
LIST_ITEM_CONTAINS_BLOCK
|
||||||
|
LIST_ITEM_BEGINNING_OF_LIST
|
||||||
|
LIST_ITEM_END_OF_LIST
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the possible flag values for the table cell renderer.
|
||||||
|
// Only a single one of these values will be used; they are not ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const (
|
||||||
|
TABLE_ALIGNMENT_LEFT = 1 << iota
|
||||||
|
TABLE_ALIGNMENT_RIGHT
|
||||||
|
TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
|
// The size of a tab stop.
|
||||||
|
const (
|
||||||
|
TAB_SIZE_DEFAULT = 4
|
||||||
|
TAB_SIZE_EIGHT = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// blockTags is a set of tags that are recognized as HTML block tags.
|
||||||
|
// Any of these can be included in markdown text without special escaping.
|
||||||
|
var blockTags = map[string]struct{}{
|
||||||
|
"blockquote": {},
|
||||||
|
"del": {},
|
||||||
|
"div": {},
|
||||||
|
"dl": {},
|
||||||
|
"fieldset": {},
|
||||||
|
"form": {},
|
||||||
|
"h1": {},
|
||||||
|
"h2": {},
|
||||||
|
"h3": {},
|
||||||
|
"h4": {},
|
||||||
|
"h5": {},
|
||||||
|
"h6": {},
|
||||||
|
"iframe": {},
|
||||||
|
"ins": {},
|
||||||
|
"math": {},
|
||||||
|
"noscript": {},
|
||||||
|
"ol": {},
|
||||||
|
"pre": {},
|
||||||
|
"p": {},
|
||||||
|
"script": {},
|
||||||
|
"style": {},
|
||||||
|
"table": {},
|
||||||
|
"ul": {},
|
||||||
|
|
||||||
|
// HTML5
|
||||||
|
"address": {},
|
||||||
|
"article": {},
|
||||||
|
"aside": {},
|
||||||
|
"canvas": {},
|
||||||
|
"figcaption": {},
|
||||||
|
"figure": {},
|
||||||
|
"footer": {},
|
||||||
|
"header": {},
|
||||||
|
"hgroup": {},
|
||||||
|
"main": {},
|
||||||
|
"nav": {},
|
||||||
|
"output": {},
|
||||||
|
"progress": {},
|
||||||
|
"section": {},
|
||||||
|
"video": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderer is the rendering interface.
|
||||||
|
// This is mostly of interest if you are implementing a new rendering format.
|
||||||
|
//
|
||||||
|
// When a byte slice is provided, it contains the (rendered) contents of the
|
||||||
|
// element.
|
||||||
|
//
|
||||||
|
// When a callback is provided instead, it will write the contents of the
|
||||||
|
// respective element directly to the output buffer and return true on success.
|
||||||
|
// If the callback returns false, the rendering function should reset the
|
||||||
|
// output buffer as though it had never been called.
|
||||||
|
//
|
||||||
|
// Currently Html and Latex implementations are provided
|
||||||
|
type Renderer interface {
|
||||||
|
// block-level callbacks
|
||||||
|
BlockCode(out *bytes.Buffer, text []byte, lang string)
|
||||||
|
BlockQuote(out *bytes.Buffer, text []byte)
|
||||||
|
BlockHtml(out *bytes.Buffer, text []byte)
|
||||||
|
Header(out *bytes.Buffer, text func() bool, level int, id string)
|
||||||
|
HRule(out *bytes.Buffer)
|
||||||
|
List(out *bytes.Buffer, text func() bool, flags int)
|
||||||
|
ListItem(out *bytes.Buffer, text []byte, flags int)
|
||||||
|
Paragraph(out *bytes.Buffer, text func() bool)
|
||||||
|
Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
|
||||||
|
TableRow(out *bytes.Buffer, text []byte)
|
||||||
|
TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
|
||||||
|
TableCell(out *bytes.Buffer, text []byte, flags int)
|
||||||
|
Footnotes(out *bytes.Buffer, text func() bool)
|
||||||
|
FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
|
||||||
|
TitleBlock(out *bytes.Buffer, text []byte)
|
||||||
|
|
||||||
|
// Span-level callbacks
|
||||||
|
AutoLink(out *bytes.Buffer, link []byte, kind int)
|
||||||
|
CodeSpan(out *bytes.Buffer, text []byte)
|
||||||
|
DoubleEmphasis(out *bytes.Buffer, text []byte)
|
||||||
|
Emphasis(out *bytes.Buffer, text []byte)
|
||||||
|
Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
|
||||||
|
LineBreak(out *bytes.Buffer)
|
||||||
|
Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
|
||||||
|
RawHtmlTag(out *bytes.Buffer, tag []byte)
|
||||||
|
TripleEmphasis(out *bytes.Buffer, text []byte)
|
||||||
|
StrikeThrough(out *bytes.Buffer, text []byte)
|
||||||
|
FootnoteRef(out *bytes.Buffer, ref []byte, id int)
|
||||||
|
|
||||||
|
// Low-level callbacks
|
||||||
|
Entity(out *bytes.Buffer, entity []byte)
|
||||||
|
NormalText(out *bytes.Buffer, text []byte)
|
||||||
|
|
||||||
|
// Header and footer
|
||||||
|
DocumentHeader(out *bytes.Buffer)
|
||||||
|
DocumentFooter(out *bytes.Buffer)
|
||||||
|
|
||||||
|
GetFlags() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback functions for inline parsing. One such function is defined
|
||||||
|
// for each character that triggers a response when parsing inline data.
|
||||||
|
type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int
|
||||||
|
|
||||||
|
// Parser holds runtime state used by the parser.
|
||||||
|
// This is constructed by the Markdown function.
|
||||||
|
type parser struct {
|
||||||
|
r Renderer
|
||||||
|
refOverride ReferenceOverrideFunc
|
||||||
|
refs map[string]*reference
|
||||||
|
inlineCallback [256]inlineParser
|
||||||
|
flags int
|
||||||
|
nesting int
|
||||||
|
maxNesting int
|
||||||
|
insideLink bool
|
||||||
|
|
||||||
|
// Footnotes need to be ordered as well as available to quickly check for
|
||||||
|
// presence. If a ref is also a footnote, it's stored both in refs and here
|
||||||
|
// in notes. Slice is nil if footnotes not enabled.
|
||||||
|
notes []*reference
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||||
|
if p.refOverride != nil {
|
||||||
|
r, overridden := p.refOverride(refid)
|
||||||
|
if overridden {
|
||||||
|
if r == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return &reference{
|
||||||
|
link: []byte(r.Link),
|
||||||
|
title: []byte(r.Title),
|
||||||
|
noteId: 0,
|
||||||
|
hasBlock: false,
|
||||||
|
text: []byte(r.Text)}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// refs are case insensitive
|
||||||
|
ref, found = p.refs[strings.ToLower(refid)]
|
||||||
|
return ref, found
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Public interface
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// Reference represents the details of a link.
|
||||||
|
// See the documentation in Options for more details on use-case.
|
||||||
|
type Reference struct {
|
||||||
|
// Link is usually the URL the reference points to.
|
||||||
|
Link string
|
||||||
|
// Title is the alternate text describing the link in more detail.
|
||||||
|
Title string
|
||||||
|
// Text is the optional text to override the ref with if the syntax used was
|
||||||
|
// [refid][]
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferenceOverrideFunc is expected to be called with a reference string and
|
||||||
|
// return either a valid Reference type that the reference string maps to or
|
||||||
|
// nil. If overridden is false, the default reference logic will be executed.
|
||||||
|
// See the documentation in Options for more details on use-case.
|
||||||
|
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
|
||||||
|
|
||||||
|
// Options represents configurable overrides and callbacks (in addition to the
|
||||||
|
// extension flag set) for configuring a Markdown parse.
|
||||||
|
type Options struct {
|
||||||
|
// Extensions is a flag set of bit-wise ORed extension bits. See the
|
||||||
|
// EXTENSION_* flags defined in this package.
|
||||||
|
Extensions int
|
||||||
|
|
||||||
|
// ReferenceOverride is an optional function callback that is called every
|
||||||
|
// time a reference is resolved.
|
||||||
|
//
|
||||||
|
// In Markdown, the link reference syntax can be made to resolve a link to
|
||||||
|
// a reference instead of an inline URL, in one of the following ways:
|
||||||
|
//
|
||||||
|
// * [link text][refid]
|
||||||
|
// * [refid][]
|
||||||
|
//
|
||||||
|
// Usually, the refid is defined at the bottom of the Markdown document. If
|
||||||
|
// this override function is provided, the refid is passed to the override
|
||||||
|
// function first, before consulting the defined refids at the bottom. If
|
||||||
|
// the override function indicates an override did not occur, the refids at
|
||||||
|
// the bottom will be used to fill in the link details.
|
||||||
|
ReferenceOverride ReferenceOverrideFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkdownBasic is a convenience function for simple rendering.
|
||||||
|
// It processes markdown input with no extensions enabled.
|
||||||
|
func MarkdownBasic(input []byte) []byte {
|
||||||
|
// set up the HTML renderer
|
||||||
|
htmlFlags := HTML_USE_XHTML
|
||||||
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
|
// set up the parser
|
||||||
|
return MarkdownOptions(input, renderer, Options{Extensions: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Markdown with most useful extensions enabled
|
||||||
|
// MarkdownCommon is a convenience function for simple rendering.
|
||||||
|
// It processes markdown input with common extensions enabled, including:
|
||||||
|
//
|
||||||
|
// * Smartypants processing with smart fractions and LaTeX dashes
|
||||||
|
//
|
||||||
|
// * Intra-word emphasis suppression
|
||||||
|
//
|
||||||
|
// * Tables
|
||||||
|
//
|
||||||
|
// * Fenced code blocks
|
||||||
|
//
|
||||||
|
// * Autolinking
|
||||||
|
//
|
||||||
|
// * Strikethrough support
|
||||||
|
//
|
||||||
|
// * Strict header parsing
|
||||||
|
//
|
||||||
|
// * Custom Header IDs
|
||||||
|
func MarkdownCommon(input []byte) []byte {
|
||||||
|
// set up the HTML renderer
|
||||||
|
renderer := HtmlRenderer(commonHtmlFlags, "", "")
|
||||||
|
return MarkdownOptions(input, renderer, Options{
|
||||||
|
Extensions: commonExtensions})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Markdown is the main rendering function.
|
||||||
|
// It parses and renders a block of markdown-encoded text.
|
||||||
|
// The supplied Renderer is used to format the output, and extensions dictates
|
||||||
|
// which non-standard extensions are enabled.
|
||||||
|
//
|
||||||
|
// To use the supplied Html or LaTeX renderers, see HtmlRenderer and
|
||||||
|
// LatexRenderer, respectively.
|
||||||
|
func Markdown(input []byte, renderer Renderer, extensions int) []byte {
|
||||||
|
return MarkdownOptions(input, renderer, Options{
|
||||||
|
Extensions: extensions})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkdownOptions is just like Markdown but takes additional options through
|
||||||
|
// the Options struct.
|
||||||
|
func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
|
||||||
|
// no point in parsing if we can't render
|
||||||
|
if renderer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions := opts.Extensions
|
||||||
|
|
||||||
|
// fill in the render structure
|
||||||
|
p := new(parser)
|
||||||
|
p.r = renderer
|
||||||
|
p.flags = extensions
|
||||||
|
p.refOverride = opts.ReferenceOverride
|
||||||
|
p.refs = make(map[string]*reference)
|
||||||
|
p.maxNesting = 16
|
||||||
|
p.insideLink = false
|
||||||
|
|
||||||
|
// register inline parsers
|
||||||
|
p.inlineCallback['*'] = emphasis
|
||||||
|
p.inlineCallback['_'] = emphasis
|
||||||
|
if extensions&EXTENSION_STRIKETHROUGH != 0 {
|
||||||
|
p.inlineCallback['~'] = emphasis
|
||||||
|
}
|
||||||
|
p.inlineCallback['`'] = codeSpan
|
||||||
|
p.inlineCallback['\n'] = lineBreak
|
||||||
|
p.inlineCallback['['] = link
|
||||||
|
p.inlineCallback['<'] = leftAngle
|
||||||
|
p.inlineCallback['\\'] = escape
|
||||||
|
p.inlineCallback['&'] = entity
|
||||||
|
|
||||||
|
if extensions&EXTENSION_AUTOLINK != 0 {
|
||||||
|
p.inlineCallback[':'] = autoLink
|
||||||
|
}
|
||||||
|
|
||||||
|
if extensions&EXTENSION_FOOTNOTES != 0 {
|
||||||
|
p.notes = make([]*reference, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
first := firstPass(p, input)
|
||||||
|
second := secondPass(p, first)
|
||||||
|
return second
|
||||||
|
}
|
||||||
|
|
||||||
|
// first pass:
|
||||||
|
// - normalize newlines
|
||||||
|
// - extract references (outside of fenced code blocks)
|
||||||
|
// - expand tabs (outside of fenced code blocks)
|
||||||
|
// - copy everything else
|
||||||
|
func firstPass(p *parser, input []byte) []byte {
|
||||||
|
var out bytes.Buffer
|
||||||
|
tabSize := TAB_SIZE_DEFAULT
|
||||||
|
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
|
||||||
|
tabSize = TAB_SIZE_EIGHT
|
||||||
|
}
|
||||||
|
beg := 0
|
||||||
|
lastFencedCodeBlockEnd := 0
|
||||||
|
for beg < len(input) {
|
||||||
|
// Find end of this line, then process the line.
|
||||||
|
end := beg
|
||||||
|
for end < len(input) && input[end] != '\n' && input[end] != '\r' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||||
|
// track fenced code block boundaries to suppress tab expansion
|
||||||
|
// and reference extraction inside them:
|
||||||
|
if beg >= lastFencedCodeBlockEnd {
|
||||||
|
if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 {
|
||||||
|
lastFencedCodeBlockEnd = beg + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the line body if present
|
||||||
|
if end > beg {
|
||||||
|
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
|
||||||
|
out.Write(input[beg:end])
|
||||||
|
} else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
|
||||||
|
beg += refEnd
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
expandTabs(&out, input[beg:end], tabSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if end < len(input) && input[end] == '\r' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
if end < len(input) && input[end] == '\n' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
out.WriteByte('\n')
|
||||||
|
|
||||||
|
beg = end
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty input?
|
||||||
|
if out.Len() == 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// second pass: actual rendering
|
||||||
|
func secondPass(p *parser, input []byte) []byte {
|
||||||
|
var output bytes.Buffer
|
||||||
|
|
||||||
|
p.r.DocumentHeader(&output)
|
||||||
|
p.block(&output, input)
|
||||||
|
|
||||||
|
if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
|
||||||
|
p.r.Footnotes(&output, func() bool {
|
||||||
|
flags := LIST_ITEM_BEGINNING_OF_LIST
|
||||||
|
for i := 0; i < len(p.notes); i += 1 {
|
||||||
|
ref := p.notes[i]
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if ref.hasBlock {
|
||||||
|
flags |= LIST_ITEM_CONTAINS_BLOCK
|
||||||
|
p.block(&buf, ref.title)
|
||||||
|
} else {
|
||||||
|
p.inline(&buf, ref.title)
|
||||||
|
}
|
||||||
|
p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
|
||||||
|
flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
p.r.DocumentFooter(&output)
|
||||||
|
|
||||||
|
if p.nesting != 0 {
|
||||||
|
panic("Nesting level did not end at zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Link references
|
||||||
|
//
|
||||||
|
// This section implements support for references that (usually) appear
|
||||||
|
// as footnotes in a document, and can be referenced anywhere in the document.
|
||||||
|
// The basic format is:
|
||||||
|
//
|
||||||
|
// [1]: http://www.google.com/ "Google"
|
||||||
|
// [2]: http://www.github.com/ "Github"
|
||||||
|
//
|
||||||
|
// Anywhere in the document, the reference can be linked by referring to its
|
||||||
|
// label, i.e., 1 and 2 in this example, as in:
|
||||||
|
//
|
||||||
|
// This library is hosted on [Github][2], a git hosting site.
|
||||||
|
//
|
||||||
|
// Actual footnotes as specified in Pandoc and supported by some other Markdown
|
||||||
|
// libraries such as php-markdown are also taken care of. They look like this:
|
||||||
|
//
|
||||||
|
// This sentence needs a bit of further explanation.[^note]
|
||||||
|
//
|
||||||
|
// [^note]: This is the explanation.
|
||||||
|
//
|
||||||
|
// Footnotes should be placed at the end of the document in an ordered list.
|
||||||
|
// Inline footnotes such as:
|
||||||
|
//
|
||||||
|
// Inline footnotes^[Not supported.] also exist.
|
||||||
|
//
|
||||||
|
// are not yet supported.
|
||||||
|
|
||||||
|
// References are parsed and stored in this struct.
|
||||||
|
type reference struct {
|
||||||
|
link []byte
|
||||||
|
title []byte
|
||||||
|
noteId int // 0 if not a footnote ref
|
||||||
|
hasBlock bool
|
||||||
|
text []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) String() string {
|
||||||
|
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}",
|
||||||
|
r.link, r.title, r.text, r.noteId, r.hasBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether or not data starts with a reference link.
|
||||||
|
// If so, it is parsed and stored in the list of references
|
||||||
|
// (in the render struct).
|
||||||
|
// Returns the number of bytes to skip to move past it,
|
||||||
|
// or zero if the first line is not a reference.
|
||||||
|
func isReference(p *parser, data []byte, tabSize int) int {
|
||||||
|
// up to 3 optional leading spaces
|
||||||
|
if len(data) < 4 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for i < 3 && data[i] == ' ' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
noteId := 0
|
||||||
|
|
||||||
|
// id part: anything but a newline between brackets
|
||||||
|
if data[i] != '[' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if p.flags&EXTENSION_FOOTNOTES != 0 {
|
||||||
|
if i < len(data) && data[i] == '^' {
|
||||||
|
// we can set it to anything here because the proper noteIds will
|
||||||
|
// be assigned later during the second pass. It just has to be != 0
|
||||||
|
noteId = 1
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idOffset := i
|
||||||
|
for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(data) || data[i] != ']' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
idEnd := i
|
||||||
|
|
||||||
|
// spacer: colon (space | tab)* newline? (space | tab)*
|
||||||
|
i++
|
||||||
|
if i >= len(data) || data[i] != ':' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
|
||||||
|
i++
|
||||||
|
if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(data) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
linkOffset, linkEnd int
|
||||||
|
titleOffset, titleEnd int
|
||||||
|
lineEnd int
|
||||||
|
raw []byte
|
||||||
|
hasBlock bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
|
||||||
|
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
|
||||||
|
lineEnd = linkEnd
|
||||||
|
} else {
|
||||||
|
linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
|
||||||
|
}
|
||||||
|
if lineEnd == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// a valid ref has been found
|
||||||
|
|
||||||
|
ref := &reference{
|
||||||
|
noteId: noteId,
|
||||||
|
hasBlock: hasBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
if noteId > 0 {
|
||||||
|
// reusing the link field for the id since footnotes don't have links
|
||||||
|
ref.link = data[idOffset:idEnd]
|
||||||
|
// if footnote, it's not really a title, it's the contained text
|
||||||
|
ref.title = raw
|
||||||
|
} else {
|
||||||
|
ref.link = data[linkOffset:linkEnd]
|
||||||
|
ref.title = data[titleOffset:titleEnd]
|
||||||
|
}
|
||||||
|
|
||||||
|
// id matches are case-insensitive
|
||||||
|
id := string(bytes.ToLower(data[idOffset:idEnd]))
|
||||||
|
|
||||||
|
p.refs[id] = ref
|
||||||
|
|
||||||
|
return lineEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
|
||||||
|
// link: whitespace-free sequence, optionally between angle brackets
|
||||||
|
if data[i] == '<' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
linkOffset = i
|
||||||
|
if i == len(data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
linkEnd = i
|
||||||
|
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
|
||||||
|
linkOffset++
|
||||||
|
linkEnd--
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute end-of-line
|
||||||
|
if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
|
||||||
|
lineEnd = i
|
||||||
|
}
|
||||||
|
if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
|
||||||
|
lineEnd++
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional (space|tab)* spacer after a newline
|
||||||
|
if lineEnd > 0 {
|
||||||
|
i = lineEnd + 1
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional title: any non-newline sequence enclosed in '"() alone on its line
|
||||||
|
if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
|
||||||
|
i++
|
||||||
|
titleOffset = i
|
||||||
|
|
||||||
|
// look for EOL
|
||||||
|
for i < len(data) && data[i] != '\n' && data[i] != '\r' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
|
||||||
|
titleEnd = i + 1
|
||||||
|
} else {
|
||||||
|
titleEnd = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// step back
|
||||||
|
i--
|
||||||
|
for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
|
||||||
|
lineEnd = titleEnd
|
||||||
|
titleEnd = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first bit of this logic is the same as (*parser).listItem, but the rest
|
||||||
|
// is much simpler. This function simply finds the entire block and shifts it
|
||||||
|
// over by one tab if it is indeed a block (just returns the line if it's not).
|
||||||
|
// blockEnd is the end of the section in the input buffer, and contents is the
|
||||||
|
// extracted text that was shifted over one tab. It will need to be rendered at
|
||||||
|
// the end of the document.
|
||||||
|
func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
|
||||||
|
if i == 0 || len(data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip leading whitespace on first line
|
||||||
|
for i < len(data) && data[i] == ' ' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
blockStart = i
|
||||||
|
|
||||||
|
// find the end of the line
|
||||||
|
blockEnd = i
|
||||||
|
for i < len(data) && data[i-1] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// get working buffer
|
||||||
|
var raw bytes.Buffer
|
||||||
|
|
||||||
|
// put the first line into the working buffer
|
||||||
|
raw.Write(data[blockEnd:i])
|
||||||
|
blockEnd = i
|
||||||
|
|
||||||
|
// process the following lines
|
||||||
|
containsBlankLine := false
|
||||||
|
|
||||||
|
gatherLines:
|
||||||
|
for blockEnd < len(data) {
|
||||||
|
i++
|
||||||
|
|
||||||
|
// find the end of this line
|
||||||
|
for i < len(data) && data[i-1] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is an empty line, guess that it is part of this item
|
||||||
|
// and move on to the next line
|
||||||
|
if p.isEmpty(data[blockEnd:i]) > 0 {
|
||||||
|
containsBlankLine = true
|
||||||
|
blockEnd = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 0
|
||||||
|
if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
|
||||||
|
// this is the end of the block.
|
||||||
|
// we don't want to include this last line in the index.
|
||||||
|
break gatherLines
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there were blank lines before this one, insert a new one now
|
||||||
|
if containsBlankLine {
|
||||||
|
raw.WriteByte('\n')
|
||||||
|
containsBlankLine = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// get rid of that first tab, write to buffer
|
||||||
|
raw.Write(data[blockEnd+n : i])
|
||||||
|
hasBlock = true
|
||||||
|
|
||||||
|
blockEnd = i
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[blockEnd-1] != '\n' {
|
||||||
|
raw.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = raw.Bytes()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Miscellaneous helper functions
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// Test if a character is a punctuation symbol.
|
||||||
|
// Taken from a private function in regexp in the stdlib.
|
||||||
|
func ispunct(c byte) bool {
|
||||||
|
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
|
||||||
|
if c == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a character is a whitespace character.
|
||||||
|
func isspace(c byte) bool {
|
||||||
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a character is letter.
|
||||||
|
func isletter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a character is a letter or a digit.
|
||||||
|
// TODO: check when this is looking for ASCII alnum and when it should use unicode
|
||||||
|
func isalnum(c byte) bool {
|
||||||
|
return (c >= '0' && c <= '9') || isletter(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
|
||||||
|
// always ends output with a newline
|
||||||
|
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
|
||||||
|
// first, check for common cases: no tabs, or only tabs at beginning of line
|
||||||
|
i, prefix := 0, 0
|
||||||
|
slowcase := false
|
||||||
|
for i = 0; i < len(line); i++ {
|
||||||
|
if line[i] == '\t' {
|
||||||
|
if prefix == i {
|
||||||
|
prefix++
|
||||||
|
} else {
|
||||||
|
slowcase = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to decode runes if all tabs are at the beginning of the line
|
||||||
|
if !slowcase {
|
||||||
|
for i = 0; i < prefix*tabSize; i++ {
|
||||||
|
out.WriteByte(' ')
|
||||||
|
}
|
||||||
|
out.Write(line[prefix:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the slow case: we need to count runes to figure out how
|
||||||
|
// many spaces to insert for each tab
|
||||||
|
column := 0
|
||||||
|
i = 0
|
||||||
|
for i < len(line) {
|
||||||
|
start := i
|
||||||
|
for i < len(line) && line[i] != '\t' {
|
||||||
|
_, size := utf8.DecodeRune(line[i:])
|
||||||
|
i += size
|
||||||
|
column++
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > start {
|
||||||
|
out.Write(line[start:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= len(line) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
out.WriteByte(' ')
|
||||||
|
column++
|
||||||
|
if column%tabSize == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find if a line counts as indented or not.
|
||||||
|
// Returns number of characters the indent is (0 = not indented).
|
||||||
|
func isIndented(data []byte, indentSize int) int {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if data[0] == '\t' {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if len(data) < indentSize {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for i := 0; i < indentSize; i++ {
|
||||||
|
if data[i] != ' ' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indentSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a url-safe slug for fragments
|
||||||
|
func slugify(in []byte) []byte {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
out := make([]byte, 0, len(in))
|
||||||
|
sym := false
|
||||||
|
|
||||||
|
for _, ch := range in {
|
||||||
|
if isalnum(ch) {
|
||||||
|
sym = false
|
||||||
|
out = append(out, ch)
|
||||||
|
} else if sym {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
out = append(out, '-')
|
||||||
|
sym = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var a, b int
|
||||||
|
var ch byte
|
||||||
|
for a, ch = range out {
|
||||||
|
if ch != '-' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for b = len(out) - 1; b > 0; b-- {
|
||||||
|
if out[b] != '-' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out[a : b+1]
|
||||||
|
}
|
|
@ -0,0 +1,400 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// SmartyPants rendering
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type smartypantsData struct {
|
||||||
|
inSingleQuote bool
|
||||||
|
inDoubleQuote bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func wordBoundary(c byte) bool {
|
||||||
|
return c == 0 || isspace(c) || ispunct(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tolower(c byte) byte {
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
return c - 'A' + 'a'
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func isdigit(c byte) bool {
|
||||||
|
return c >= '0' && c <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
|
||||||
|
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||||
|
// so we treat it like text sometimes
|
||||||
|
|
||||||
|
// enumerate all sixteen possibilities for (previousChar, nextChar)
|
||||||
|
// each can be one of {0, space, punct, other}
|
||||||
|
switch {
|
||||||
|
case previousChar == 0 && nextChar == 0:
|
||||||
|
// context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen
|
||||||
|
case isspace(previousChar) && nextChar == 0:
|
||||||
|
// [ "] might be [ "<code>foo...]
|
||||||
|
*isOpen = true
|
||||||
|
case ispunct(previousChar) && nextChar == 0:
|
||||||
|
// [!"] hmm... could be [Run!"] or [("<code>...]
|
||||||
|
*isOpen = false
|
||||||
|
case /* isnormal(previousChar) && */ nextChar == 0:
|
||||||
|
// [a"] is probably a close
|
||||||
|
*isOpen = false
|
||||||
|
case previousChar == 0 && isspace(nextChar):
|
||||||
|
// [" ] might be [...foo</code>" ]
|
||||||
|
*isOpen = false
|
||||||
|
case isspace(previousChar) && isspace(nextChar):
|
||||||
|
// [ " ] context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen
|
||||||
|
case ispunct(previousChar) && isspace(nextChar):
|
||||||
|
// [!" ] is probably a close
|
||||||
|
*isOpen = false
|
||||||
|
case /* isnormal(previousChar) && */ isspace(nextChar):
|
||||||
|
// [a" ] this is one of the easy cases
|
||||||
|
*isOpen = false
|
||||||
|
case previousChar == 0 && ispunct(nextChar):
|
||||||
|
// ["!] hmm... could be ["$1.95] or [</code>"!...]
|
||||||
|
*isOpen = false
|
||||||
|
case isspace(previousChar) && ispunct(nextChar):
|
||||||
|
// [ "!] looks more like [ "$1.95]
|
||||||
|
*isOpen = true
|
||||||
|
case ispunct(previousChar) && ispunct(nextChar):
|
||||||
|
// [!"!] context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen
|
||||||
|
case /* isnormal(previousChar) && */ ispunct(nextChar):
|
||||||
|
// [a"!] is probably a close
|
||||||
|
*isOpen = false
|
||||||
|
case previousChar == 0 /* && isnormal(nextChar) */ :
|
||||||
|
// ["a] is probably an open
|
||||||
|
*isOpen = true
|
||||||
|
case isspace(previousChar) /* && isnormal(nextChar) */ :
|
||||||
|
// [ "a] this is one of the easy cases
|
||||||
|
*isOpen = true
|
||||||
|
case ispunct(previousChar) /* && isnormal(nextChar) */ :
|
||||||
|
// [!"a] is probably an open
|
||||||
|
*isOpen = true
|
||||||
|
default:
|
||||||
|
// [a'b] maybe a contraction?
|
||||||
|
*isOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte('&')
|
||||||
|
if *isOpen {
|
||||||
|
out.WriteByte('l')
|
||||||
|
} else {
|
||||||
|
out.WriteByte('r')
|
||||||
|
}
|
||||||
|
out.WriteByte(quote)
|
||||||
|
out.WriteString("quo;")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 2 {
|
||||||
|
t1 := tolower(text[1])
|
||||||
|
|
||||||
|
if t1 == '\'' {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) >= 3 {
|
||||||
|
nextChar = text[2]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
|
||||||
|
out.WriteString("’")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(text) >= 3 {
|
||||||
|
t2 := tolower(text[2])
|
||||||
|
|
||||||
|
if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
|
||||||
|
(len(text) < 4 || wordBoundary(text[3])) {
|
||||||
|
out.WriteString("’")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) > 1 {
|
||||||
|
nextChar = text[1]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 3 {
|
||||||
|
t1 := tolower(text[1])
|
||||||
|
t2 := tolower(text[2])
|
||||||
|
|
||||||
|
if t1 == 'c' && t2 == ')' {
|
||||||
|
out.WriteString("©")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if t1 == 'r' && t2 == ')' {
|
||||||
|
out.WriteString("®")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
|
||||||
|
out.WriteString("™")
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 2 {
|
||||||
|
if text[1] == '-' {
|
||||||
|
out.WriteString("—")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if wordBoundary(previousChar) && wordBoundary(text[1]) {
|
||||||
|
out.WriteString("–")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
|
||||||
|
out.WriteString("—")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if len(text) >= 2 && text[1] == '-' {
|
||||||
|
out.WriteString("–")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||||
|
if bytes.HasPrefix(text, []byte(""")) {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) >= 7 {
|
||||||
|
nextChar = text[6]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.HasPrefix(text, []byte("�")) {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte('&')
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartAmpVariant(out, smrt, previousChar, text, 'd')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartAmpVariant(out, smrt, previousChar, text, 'a')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
|
||||||
|
out.WriteString("…")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
|
||||||
|
out.WriteString("…")
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 2 && text[1] == '`' {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) >= 3 {
|
||||||
|
nextChar = text[2]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||||
|
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
|
||||||
|
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
|
||||||
|
// and avoid changing dates like 1/23/2005 into fractions.
|
||||||
|
numEnd := 0
|
||||||
|
for len(text) > numEnd && isdigit(text[numEnd]) {
|
||||||
|
numEnd++
|
||||||
|
}
|
||||||
|
if numEnd == 0 {
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
denStart := numEnd + 1
|
||||||
|
if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
|
||||||
|
denStart = numEnd + 3
|
||||||
|
} else if len(text) < numEnd+2 || text[numEnd] != '/' {
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
denEnd := denStart
|
||||||
|
for len(text) > denEnd && isdigit(text[denEnd]) {
|
||||||
|
denEnd++
|
||||||
|
}
|
||||||
|
if denEnd == denStart {
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
|
||||||
|
out.WriteString("<sup>")
|
||||||
|
out.Write(text[:numEnd])
|
||||||
|
out.WriteString("</sup>⁄<sub>")
|
||||||
|
out.Write(text[denStart:denEnd])
|
||||||
|
out.WriteString("</sub>")
|
||||||
|
return denEnd - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||||
|
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
|
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
|
||||||
|
out.WriteString("½")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if text[0] == '1' && text[1] == '/' && text[2] == '4' {
|
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
|
||||||
|
out.WriteString("¼")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if text[0] == '3' && text[1] == '/' && text[2] == '4' {
|
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
|
||||||
|
out.WriteString("¾")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) > 1 {
|
||||||
|
nextChar = text[1]
|
||||||
|
}
|
||||||
|
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||||
|
out.WriteString(""")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for i < len(text) && text[i] != '>' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Write(text[:i+1])
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
|
||||||
|
|
||||||
|
type smartypantsRenderer [256]smartCallback
|
||||||
|
|
||||||
|
func smartypants(flags int) *smartypantsRenderer {
|
||||||
|
r := new(smartypantsRenderer)
|
||||||
|
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
||||||
|
r['"'] = smartDoubleQuote
|
||||||
|
r['&'] = smartAmp
|
||||||
|
} else {
|
||||||
|
r['"'] = smartAngledDoubleQuote
|
||||||
|
r['&'] = smartAmpAngledQuote
|
||||||
|
}
|
||||||
|
r['\''] = smartSingleQuote
|
||||||
|
r['('] = smartParens
|
||||||
|
if flags&HTML_SMARTYPANTS_DASHES != 0 {
|
||||||
|
if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
|
||||||
|
r['-'] = smartDash
|
||||||
|
} else {
|
||||||
|
r['-'] = smartDashLatex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r['.'] = smartPeriod
|
||||||
|
if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
|
||||||
|
r['1'] = smartNumber
|
||||||
|
r['3'] = smartNumber
|
||||||
|
} else {
|
||||||
|
for ch := '1'; ch <= '9'; ch++ {
|
||||||
|
r[ch] = smartNumberGeneric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r['<'] = smartLeftAngle
|
||||||
|
r['`'] = smartBacktick
|
||||||
|
return r
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Package front provides YAML frontmatter unmarshalling.
|
||||||
|
package front
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delimiter.
|
||||||
|
var delim = []byte("---")
|
||||||
|
|
||||||
|
// Unmarshal parses YAML frontmatter and returns the content. When no
|
||||||
|
// frontmatter delimiters are present the original content is returned.
|
||||||
|
func Unmarshal(b []byte, v interface{}) (content []byte, err error) {
|
||||||
|
if !bytes.HasPrefix(b, delim) {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := bytes.SplitN(b, delim, 3)
|
||||||
|
content = parts[2]
|
||||||
|
err = yaml.Unmarshal(parts[1], v)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,742 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
|
||||||
|
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
|
||||||
|
|
||||||
|
// Check if we can move the queue at the beginning of the buffer.
|
||||||
|
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
|
||||||
|
if parser.tokens_head != len(parser.tokens) {
|
||||||
|
copy(parser.tokens, parser.tokens[parser.tokens_head:])
|
||||||
|
}
|
||||||
|
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
|
||||||
|
parser.tokens_head = 0
|
||||||
|
}
|
||||||
|
parser.tokens = append(parser.tokens, *token)
|
||||||
|
if pos < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
|
||||||
|
parser.tokens[parser.tokens_head+pos] = *token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new parser object.
|
||||||
|
func yaml_parser_initialize(parser *yaml_parser_t) bool {
|
||||||
|
*parser = yaml_parser_t{
|
||||||
|
raw_buffer: make([]byte, 0, input_raw_buffer_size),
|
||||||
|
buffer: make([]byte, 0, input_buffer_size),
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy a parser object.
|
||||||
|
func yaml_parser_delete(parser *yaml_parser_t) {
|
||||||
|
*parser = yaml_parser_t{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String read handler.
|
||||||
|
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||||
|
if parser.input_pos == len(parser.input) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n = copy(buffer, parser.input[parser.input_pos:])
|
||||||
|
parser.input_pos += n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// File read handler.
|
||||||
|
func yaml_file_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||||
|
return parser.input_file.Read(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a string input.
|
||||||
|
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
|
||||||
|
if parser.read_handler != nil {
|
||||||
|
panic("must set the input source only once")
|
||||||
|
}
|
||||||
|
parser.read_handler = yaml_string_read_handler
|
||||||
|
parser.input = input
|
||||||
|
parser.input_pos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a file input.
|
||||||
|
func yaml_parser_set_input_file(parser *yaml_parser_t, file *os.File) {
|
||||||
|
if parser.read_handler != nil {
|
||||||
|
panic("must set the input source only once")
|
||||||
|
}
|
||||||
|
parser.read_handler = yaml_file_read_handler
|
||||||
|
parser.input_file = file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the source encoding.
|
||||||
|
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
|
||||||
|
if parser.encoding != yaml_ANY_ENCODING {
|
||||||
|
panic("must set the encoding only once")
|
||||||
|
}
|
||||||
|
parser.encoding = encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new emitter object.
|
||||||
|
func yaml_emitter_initialize(emitter *yaml_emitter_t) bool {
|
||||||
|
*emitter = yaml_emitter_t{
|
||||||
|
buffer: make([]byte, output_buffer_size),
|
||||||
|
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||||
|
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||||
|
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy an emitter object.
|
||||||
|
func yaml_emitter_delete(emitter *yaml_emitter_t) {
|
||||||
|
*emitter = yaml_emitter_t{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String write handler.
|
||||||
|
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||||
|
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// File write handler.
|
||||||
|
func yaml_file_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||||
|
_, err := emitter.output_file.Write(buffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a string output.
|
||||||
|
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
|
||||||
|
if emitter.write_handler != nil {
|
||||||
|
panic("must set the output target only once")
|
||||||
|
}
|
||||||
|
emitter.write_handler = yaml_string_write_handler
|
||||||
|
emitter.output_buffer = output_buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a file output.
|
||||||
|
func yaml_emitter_set_output_file(emitter *yaml_emitter_t, file io.Writer) {
|
||||||
|
if emitter.write_handler != nil {
|
||||||
|
panic("must set the output target only once")
|
||||||
|
}
|
||||||
|
emitter.write_handler = yaml_file_write_handler
|
||||||
|
emitter.output_file = file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the output encoding.
|
||||||
|
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
|
||||||
|
if emitter.encoding != yaml_ANY_ENCODING {
|
||||||
|
panic("must set the output encoding only once")
|
||||||
|
}
|
||||||
|
emitter.encoding = encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the canonical output style.
|
||||||
|
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
|
||||||
|
emitter.canonical = canonical
|
||||||
|
}
|
||||||
|
|
||||||
|
//// Set the indentation increment.
|
||||||
|
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
|
||||||
|
if indent < 2 || indent > 9 {
|
||||||
|
indent = 2
|
||||||
|
}
|
||||||
|
emitter.best_indent = indent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the preferred line width.
|
||||||
|
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
|
||||||
|
if width < 0 {
|
||||||
|
width = -1
|
||||||
|
}
|
||||||
|
emitter.best_width = width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set if unescaped non-ASCII characters are allowed.
|
||||||
|
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
|
||||||
|
emitter.unicode = unicode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the preferred line break character.
|
||||||
|
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
|
||||||
|
emitter.line_break = line_break
|
||||||
|
}
|
||||||
|
|
||||||
|
///*
|
||||||
|
// * Destroy a token object.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(void)
|
||||||
|
//yaml_token_delete(yaml_token_t *token)
|
||||||
|
//{
|
||||||
|
// assert(token); // Non-NULL token object expected.
|
||||||
|
//
|
||||||
|
// switch (token.type)
|
||||||
|
// {
|
||||||
|
// case YAML_TAG_DIRECTIVE_TOKEN:
|
||||||
|
// yaml_free(token.data.tag_directive.handle);
|
||||||
|
// yaml_free(token.data.tag_directive.prefix);
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case YAML_ALIAS_TOKEN:
|
||||||
|
// yaml_free(token.data.alias.value);
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case YAML_ANCHOR_TOKEN:
|
||||||
|
// yaml_free(token.data.anchor.value);
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case YAML_TAG_TOKEN:
|
||||||
|
// yaml_free(token.data.tag.handle);
|
||||||
|
// yaml_free(token.data.tag.suffix);
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case YAML_SCALAR_TOKEN:
|
||||||
|
// yaml_free(token.data.scalar.value);
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// default:
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// memset(token, 0, sizeof(yaml_token_t));
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Check if a string is a valid UTF-8 sequence.
|
||||||
|
// *
|
||||||
|
// * Check 'reader.c' for more details on UTF-8 encoding.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//static int
|
||||||
|
//yaml_check_utf8(yaml_char_t *start, size_t length)
|
||||||
|
//{
|
||||||
|
// yaml_char_t *end = start+length;
|
||||||
|
// yaml_char_t *pointer = start;
|
||||||
|
//
|
||||||
|
// while (pointer < end) {
|
||||||
|
// unsigned char octet;
|
||||||
|
// unsigned int width;
|
||||||
|
// unsigned int value;
|
||||||
|
// size_t k;
|
||||||
|
//
|
||||||
|
// octet = pointer[0];
|
||||||
|
// width = (octet & 0x80) == 0x00 ? 1 :
|
||||||
|
// (octet & 0xE0) == 0xC0 ? 2 :
|
||||||
|
// (octet & 0xF0) == 0xE0 ? 3 :
|
||||||
|
// (octet & 0xF8) == 0xF0 ? 4 : 0;
|
||||||
|
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
|
||||||
|
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
|
||||||
|
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
|
||||||
|
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
|
||||||
|
// if (!width) return 0;
|
||||||
|
// if (pointer+width > end) return 0;
|
||||||
|
// for (k = 1; k < width; k ++) {
|
||||||
|
// octet = pointer[k];
|
||||||
|
// if ((octet & 0xC0) != 0x80) return 0;
|
||||||
|
// value = (value << 6) + (octet & 0x3F);
|
||||||
|
// }
|
||||||
|
// if (!((width == 1) ||
|
||||||
|
// (width == 2 && value >= 0x80) ||
|
||||||
|
// (width == 3 && value >= 0x800) ||
|
||||||
|
// (width == 4 && value >= 0x10000))) return 0;
|
||||||
|
//
|
||||||
|
// pointer += width;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return 1;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
|
||||||
|
// Create STREAM-START.
|
||||||
|
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_STREAM_START_EVENT,
|
||||||
|
encoding: encoding,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create STREAM-END.
|
||||||
|
func yaml_stream_end_event_initialize(event *yaml_event_t) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_STREAM_END_EVENT,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create DOCUMENT-START.
|
||||||
|
func yaml_document_start_event_initialize(event *yaml_event_t, version_directive *yaml_version_directive_t,
|
||||||
|
tag_directives []yaml_tag_directive_t, implicit bool) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_DOCUMENT_START_EVENT,
|
||||||
|
version_directive: version_directive,
|
||||||
|
tag_directives: tag_directives,
|
||||||
|
implicit: implicit,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create DOCUMENT-END.
|
||||||
|
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_DOCUMENT_END_EVENT,
|
||||||
|
implicit: implicit,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
///*
|
||||||
|
// * Create ALIAS.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(int)
|
||||||
|
//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
|
||||||
|
//{
|
||||||
|
// mark yaml_mark_t = { 0, 0, 0 }
|
||||||
|
// anchor_copy *yaml_char_t = NULL
|
||||||
|
//
|
||||||
|
// assert(event) // Non-NULL event object is expected.
|
||||||
|
// assert(anchor) // Non-NULL anchor is expected.
|
||||||
|
//
|
||||||
|
// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
|
||||||
|
//
|
||||||
|
// anchor_copy = yaml_strdup(anchor)
|
||||||
|
// if (!anchor_copy)
|
||||||
|
// return 0
|
||||||
|
//
|
||||||
|
// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
|
||||||
|
//
|
||||||
|
// return 1
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Create SCALAR.
|
||||||
|
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_SCALAR_EVENT,
|
||||||
|
anchor: anchor,
|
||||||
|
tag: tag,
|
||||||
|
value: value,
|
||||||
|
implicit: plain_implicit,
|
||||||
|
quoted_implicit: quoted_implicit,
|
||||||
|
style: yaml_style_t(style),
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create SEQUENCE-START.
|
||||||
|
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_SEQUENCE_START_EVENT,
|
||||||
|
anchor: anchor,
|
||||||
|
tag: tag,
|
||||||
|
implicit: implicit,
|
||||||
|
style: yaml_style_t(style),
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create SEQUENCE-END.
|
||||||
|
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_SEQUENCE_END_EVENT,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create MAPPING-START.
|
||||||
|
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_MAPPING_START_EVENT,
|
||||||
|
anchor: anchor,
|
||||||
|
tag: tag,
|
||||||
|
implicit: implicit,
|
||||||
|
style: yaml_style_t(style),
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create MAPPING-END.
|
||||||
|
func yaml_mapping_end_event_initialize(event *yaml_event_t) bool {
|
||||||
|
*event = yaml_event_t{
|
||||||
|
typ: yaml_MAPPING_END_EVENT,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy an event object.
|
||||||
|
func yaml_event_delete(event *yaml_event_t) {
|
||||||
|
*event = yaml_event_t{}
|
||||||
|
}
|
||||||
|
|
||||||
|
///*
|
||||||
|
// * Create a document object.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(int)
|
||||||
|
//yaml_document_initialize(document *yaml_document_t,
|
||||||
|
// version_directive *yaml_version_directive_t,
|
||||||
|
// tag_directives_start *yaml_tag_directive_t,
|
||||||
|
// tag_directives_end *yaml_tag_directive_t,
|
||||||
|
// start_implicit int, end_implicit int)
|
||||||
|
//{
|
||||||
|
// struct {
|
||||||
|
// error yaml_error_type_t
|
||||||
|
// } context
|
||||||
|
// struct {
|
||||||
|
// start *yaml_node_t
|
||||||
|
// end *yaml_node_t
|
||||||
|
// top *yaml_node_t
|
||||||
|
// } nodes = { NULL, NULL, NULL }
|
||||||
|
// version_directive_copy *yaml_version_directive_t = NULL
|
||||||
|
// struct {
|
||||||
|
// start *yaml_tag_directive_t
|
||||||
|
// end *yaml_tag_directive_t
|
||||||
|
// top *yaml_tag_directive_t
|
||||||
|
// } tag_directives_copy = { NULL, NULL, NULL }
|
||||||
|
// value yaml_tag_directive_t = { NULL, NULL }
|
||||||
|
// mark yaml_mark_t = { 0, 0, 0 }
|
||||||
|
//
|
||||||
|
// assert(document) // Non-NULL document object is expected.
|
||||||
|
// assert((tag_directives_start && tag_directives_end) ||
|
||||||
|
// (tag_directives_start == tag_directives_end))
|
||||||
|
// // Valid tag directives are expected.
|
||||||
|
//
|
||||||
|
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
|
||||||
|
//
|
||||||
|
// if (version_directive) {
|
||||||
|
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
|
||||||
|
// if (!version_directive_copy) goto error
|
||||||
|
// version_directive_copy.major = version_directive.major
|
||||||
|
// version_directive_copy.minor = version_directive.minor
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (tag_directives_start != tag_directives_end) {
|
||||||
|
// tag_directive *yaml_tag_directive_t
|
||||||
|
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
|
||||||
|
// goto error
|
||||||
|
// for (tag_directive = tag_directives_start
|
||||||
|
// tag_directive != tag_directives_end; tag_directive ++) {
|
||||||
|
// assert(tag_directive.handle)
|
||||||
|
// assert(tag_directive.prefix)
|
||||||
|
// if (!yaml_check_utf8(tag_directive.handle,
|
||||||
|
// strlen((char *)tag_directive.handle)))
|
||||||
|
// goto error
|
||||||
|
// if (!yaml_check_utf8(tag_directive.prefix,
|
||||||
|
// strlen((char *)tag_directive.prefix)))
|
||||||
|
// goto error
|
||||||
|
// value.handle = yaml_strdup(tag_directive.handle)
|
||||||
|
// value.prefix = yaml_strdup(tag_directive.prefix)
|
||||||
|
// if (!value.handle || !value.prefix) goto error
|
||||||
|
// if (!PUSH(&context, tag_directives_copy, value))
|
||||||
|
// goto error
|
||||||
|
// value.handle = NULL
|
||||||
|
// value.prefix = NULL
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
|
||||||
|
// tag_directives_copy.start, tag_directives_copy.top,
|
||||||
|
// start_implicit, end_implicit, mark, mark)
|
||||||
|
//
|
||||||
|
// return 1
|
||||||
|
//
|
||||||
|
//error:
|
||||||
|
// STACK_DEL(&context, nodes)
|
||||||
|
// yaml_free(version_directive_copy)
|
||||||
|
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
|
||||||
|
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
|
||||||
|
// yaml_free(value.handle)
|
||||||
|
// yaml_free(value.prefix)
|
||||||
|
// }
|
||||||
|
// STACK_DEL(&context, tag_directives_copy)
|
||||||
|
// yaml_free(value.handle)
|
||||||
|
// yaml_free(value.prefix)
|
||||||
|
//
|
||||||
|
// return 0
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Destroy a document object.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(void)
|
||||||
|
//yaml_document_delete(document *yaml_document_t)
|
||||||
|
//{
|
||||||
|
// struct {
|
||||||
|
// error yaml_error_type_t
|
||||||
|
// } context
|
||||||
|
// tag_directive *yaml_tag_directive_t
|
||||||
|
//
|
||||||
|
// context.error = YAML_NO_ERROR // Eliminate a compliler warning.
|
||||||
|
//
|
||||||
|
// assert(document) // Non-NULL document object is expected.
|
||||||
|
//
|
||||||
|
// while (!STACK_EMPTY(&context, document.nodes)) {
|
||||||
|
// node yaml_node_t = POP(&context, document.nodes)
|
||||||
|
// yaml_free(node.tag)
|
||||||
|
// switch (node.type) {
|
||||||
|
// case YAML_SCALAR_NODE:
|
||||||
|
// yaml_free(node.data.scalar.value)
|
||||||
|
// break
|
||||||
|
// case YAML_SEQUENCE_NODE:
|
||||||
|
// STACK_DEL(&context, node.data.sequence.items)
|
||||||
|
// break
|
||||||
|
// case YAML_MAPPING_NODE:
|
||||||
|
// STACK_DEL(&context, node.data.mapping.pairs)
|
||||||
|
// break
|
||||||
|
// default:
|
||||||
|
// assert(0) // Should not happen.
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// STACK_DEL(&context, document.nodes)
|
||||||
|
//
|
||||||
|
// yaml_free(document.version_directive)
|
||||||
|
// for (tag_directive = document.tag_directives.start
|
||||||
|
// tag_directive != document.tag_directives.end
|
||||||
|
// tag_directive++) {
|
||||||
|
// yaml_free(tag_directive.handle)
|
||||||
|
// yaml_free(tag_directive.prefix)
|
||||||
|
// }
|
||||||
|
// yaml_free(document.tag_directives.start)
|
||||||
|
//
|
||||||
|
// memset(document, 0, sizeof(yaml_document_t))
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Get a document node.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(yaml_node_t *)
|
||||||
|
//yaml_document_get_node(document *yaml_document_t, index int)
|
||||||
|
//{
|
||||||
|
// assert(document) // Non-NULL document object is expected.
|
||||||
|
//
|
||||||
|
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
|
||||||
|
// return document.nodes.start + index - 1
|
||||||
|
// }
|
||||||
|
// return NULL
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Get the root object.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(yaml_node_t *)
|
||||||
|
//yaml_document_get_root_node(document *yaml_document_t)
|
||||||
|
//{
|
||||||
|
// assert(document) // Non-NULL document object is expected.
|
||||||
|
//
|
||||||
|
// if (document.nodes.top != document.nodes.start) {
|
||||||
|
// return document.nodes.start
|
||||||
|
// }
|
||||||
|
// return NULL
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Add a scalar node to a document.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(int)
|
||||||
|
//yaml_document_add_scalar(document *yaml_document_t,
|
||||||
|
// tag *yaml_char_t, value *yaml_char_t, length int,
|
||||||
|
// style yaml_scalar_style_t)
|
||||||
|
//{
|
||||||
|
// struct {
|
||||||
|
// error yaml_error_type_t
|
||||||
|
// } context
|
||||||
|
// mark yaml_mark_t = { 0, 0, 0 }
|
||||||
|
// tag_copy *yaml_char_t = NULL
|
||||||
|
// value_copy *yaml_char_t = NULL
|
||||||
|
// node yaml_node_t
|
||||||
|
//
|
||||||
|
// assert(document) // Non-NULL document object is expected.
|
||||||
|
// assert(value) // Non-NULL value is expected.
|
||||||
|
//
|
||||||
|
// if (!tag) {
|
||||||
|
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||||
|
// tag_copy = yaml_strdup(tag)
|
||||||
|
// if (!tag_copy) goto error
|
||||||
|
//
|
||||||
|
// if (length < 0) {
|
||||||
|
// length = strlen((char *)value)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!yaml_check_utf8(value, length)) goto error
|
||||||
|
// value_copy = yaml_malloc(length+1)
|
||||||
|
// if (!value_copy) goto error
|
||||||
|
// memcpy(value_copy, value, length)
|
||||||
|
// value_copy[length] = '\0'
|
||||||
|
//
|
||||||
|
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
|
||||||
|
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||||
|
//
|
||||||
|
// return document.nodes.top - document.nodes.start
|
||||||
|
//
|
||||||
|
//error:
|
||||||
|
// yaml_free(tag_copy)
|
||||||
|
// yaml_free(value_copy)
|
||||||
|
//
|
||||||
|
// return 0
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Add a sequence node to a document.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(int)
|
||||||
|
//yaml_document_add_sequence(document *yaml_document_t,
|
||||||
|
// tag *yaml_char_t, style yaml_sequence_style_t)
|
||||||
|
//{
|
||||||
|
// struct {
|
||||||
|
// error yaml_error_type_t
|
||||||
|
// } context
|
||||||
|
// mark yaml_mark_t = { 0, 0, 0 }
|
||||||
|
// tag_copy *yaml_char_t = NULL
|
||||||
|
// struct {
|
||||||
|
// start *yaml_node_item_t
|
||||||
|
// end *yaml_node_item_t
|
||||||
|
// top *yaml_node_item_t
|
||||||
|
// } items = { NULL, NULL, NULL }
|
||||||
|
// node yaml_node_t
|
||||||
|
//
|
||||||
|
// assert(document) // Non-NULL document object is expected.
|
||||||
|
//
|
||||||
|
// if (!tag) {
|
||||||
|
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||||
|
// tag_copy = yaml_strdup(tag)
|
||||||
|
// if (!tag_copy) goto error
|
||||||
|
//
|
||||||
|
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
|
||||||
|
//
|
||||||
|
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
|
||||||
|
// style, mark, mark)
|
||||||
|
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||||
|
//
|
||||||
|
// return document.nodes.top - document.nodes.start
|
||||||
|
//
|
||||||
|
//error:
|
||||||
|
// STACK_DEL(&context, items)
|
||||||
|
// yaml_free(tag_copy)
|
||||||
|
//
|
||||||
|
// return 0
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Add a mapping node to a document.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(int)
|
||||||
|
//yaml_document_add_mapping(document *yaml_document_t,
|
||||||
|
// tag *yaml_char_t, style yaml_mapping_style_t)
|
||||||
|
//{
|
||||||
|
// struct {
|
||||||
|
// error yaml_error_type_t
|
||||||
|
// } context
|
||||||
|
// mark yaml_mark_t = { 0, 0, 0 }
|
||||||
|
// tag_copy *yaml_char_t = NULL
|
||||||
|
// struct {
|
||||||
|
// start *yaml_node_pair_t
|
||||||
|
// end *yaml_node_pair_t
|
||||||
|
// top *yaml_node_pair_t
|
||||||
|
// } pairs = { NULL, NULL, NULL }
|
||||||
|
// node yaml_node_t
|
||||||
|
//
|
||||||
|
// assert(document) // Non-NULL document object is expected.
|
||||||
|
//
|
||||||
|
// if (!tag) {
|
||||||
|
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||||
|
// tag_copy = yaml_strdup(tag)
|
||||||
|
// if (!tag_copy) goto error
|
||||||
|
//
|
||||||
|
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
|
||||||
|
//
|
||||||
|
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
|
||||||
|
// style, mark, mark)
|
||||||
|
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||||
|
//
|
||||||
|
// return document.nodes.top - document.nodes.start
|
||||||
|
//
|
||||||
|
//error:
|
||||||
|
// STACK_DEL(&context, pairs)
|
||||||
|
// yaml_free(tag_copy)
|
||||||
|
//
|
||||||
|
// return 0
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Append an item to a sequence node.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(int)
|
||||||
|
//yaml_document_append_sequence_item(document *yaml_document_t,
|
||||||
|
// sequence int, item int)
|
||||||
|
//{
|
||||||
|
// struct {
|
||||||
|
// error yaml_error_type_t
|
||||||
|
// } context
|
||||||
|
//
|
||||||
|
// assert(document) // Non-NULL document is required.
|
||||||
|
// assert(sequence > 0
|
||||||
|
// && document.nodes.start + sequence <= document.nodes.top)
|
||||||
|
// // Valid sequence id is required.
|
||||||
|
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
|
||||||
|
// // A sequence node is required.
|
||||||
|
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
|
||||||
|
// // Valid item id is required.
|
||||||
|
//
|
||||||
|
// if (!PUSH(&context,
|
||||||
|
// document.nodes.start[sequence-1].data.sequence.items, item))
|
||||||
|
// return 0
|
||||||
|
//
|
||||||
|
// return 1
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Append a pair of a key and a value to a mapping node.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
//YAML_DECLARE(int)
|
||||||
|
//yaml_document_append_mapping_pair(document *yaml_document_t,
|
||||||
|
// mapping int, key int, value int)
|
||||||
|
//{
|
||||||
|
// struct {
|
||||||
|
// error yaml_error_type_t
|
||||||
|
// } context
|
||||||
|
//
|
||||||
|
// pair yaml_node_pair_t
|
||||||
|
//
|
||||||
|
// assert(document) // Non-NULL document is required.
|
||||||
|
// assert(mapping > 0
|
||||||
|
// && document.nodes.start + mapping <= document.nodes.top)
|
||||||
|
// // Valid mapping id is required.
|
||||||
|
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
|
||||||
|
// // A mapping node is required.
|
||||||
|
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
|
||||||
|
// // Valid key id is required.
|
||||||
|
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
|
||||||
|
// // Valid value id is required.
|
||||||
|
//
|
||||||
|
// pair.key = key
|
||||||
|
// pair.value = value
|
||||||
|
//
|
||||||
|
// if (!PUSH(&context,
|
||||||
|
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
|
||||||
|
// return 0
|
||||||
|
//
|
||||||
|
// return 1
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
|
@ -0,0 +1,566 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
documentNode = 1 << iota
|
||||||
|
mappingNode
|
||||||
|
sequenceNode
|
||||||
|
scalarNode
|
||||||
|
aliasNode
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
kind int
|
||||||
|
line, column int
|
||||||
|
tag string
|
||||||
|
value string
|
||||||
|
implicit bool
|
||||||
|
children []*node
|
||||||
|
anchors map[string]*node
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Parser, produces a node tree out of a libyaml event stream.
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
parser yaml_parser_t
|
||||||
|
event yaml_event_t
|
||||||
|
doc *node
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParser(b []byte) *parser {
|
||||||
|
p := parser{}
|
||||||
|
if !yaml_parser_initialize(&p.parser) {
|
||||||
|
panic("Failed to initialize YAML emitter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) == 0 {
|
||||||
|
b = []byte{'\n'}
|
||||||
|
}
|
||||||
|
|
||||||
|
yaml_parser_set_input_string(&p.parser, b)
|
||||||
|
|
||||||
|
p.skip()
|
||||||
|
if p.event.typ != yaml_STREAM_START_EVENT {
|
||||||
|
panic("Expected stream start event, got " + strconv.Itoa(int(p.event.typ)))
|
||||||
|
}
|
||||||
|
p.skip()
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) destroy() {
|
||||||
|
if p.event.typ != yaml_NO_EVENT {
|
||||||
|
yaml_event_delete(&p.event)
|
||||||
|
}
|
||||||
|
yaml_parser_delete(&p.parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) skip() {
|
||||||
|
if p.event.typ != yaml_NO_EVENT {
|
||||||
|
if p.event.typ == yaml_STREAM_END_EVENT {
|
||||||
|
fail("Attempted to go past the end of stream. Corrupted value?")
|
||||||
|
}
|
||||||
|
yaml_event_delete(&p.event)
|
||||||
|
}
|
||||||
|
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||||
|
p.fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) fail() {
|
||||||
|
var where string
|
||||||
|
var line int
|
||||||
|
if p.parser.problem_mark.line != 0 {
|
||||||
|
line = p.parser.problem_mark.line
|
||||||
|
} else if p.parser.context_mark.line != 0 {
|
||||||
|
line = p.parser.context_mark.line
|
||||||
|
}
|
||||||
|
if line != 0 {
|
||||||
|
where = "line " + strconv.Itoa(line) + ": "
|
||||||
|
}
|
||||||
|
var msg string
|
||||||
|
if len(p.parser.problem) > 0 {
|
||||||
|
msg = p.parser.problem
|
||||||
|
} else {
|
||||||
|
msg = "Unknown problem parsing YAML content"
|
||||||
|
}
|
||||||
|
fail(where + msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) anchor(n *node, anchor []byte) {
|
||||||
|
if anchor != nil {
|
||||||
|
p.doc.anchors[string(anchor)] = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parse() *node {
|
||||||
|
switch p.event.typ {
|
||||||
|
case yaml_SCALAR_EVENT:
|
||||||
|
return p.scalar()
|
||||||
|
case yaml_ALIAS_EVENT:
|
||||||
|
return p.alias()
|
||||||
|
case yaml_MAPPING_START_EVENT:
|
||||||
|
return p.mapping()
|
||||||
|
case yaml_SEQUENCE_START_EVENT:
|
||||||
|
return p.sequence()
|
||||||
|
case yaml_DOCUMENT_START_EVENT:
|
||||||
|
return p.document()
|
||||||
|
case yaml_STREAM_END_EVENT:
|
||||||
|
// Happens when attempting to decode an empty buffer.
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
panic("Attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ)))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) node(kind int) *node {
|
||||||
|
return &node{
|
||||||
|
kind: kind,
|
||||||
|
line: p.event.start_mark.line,
|
||||||
|
column: p.event.start_mark.column,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) document() *node {
|
||||||
|
n := p.node(documentNode)
|
||||||
|
n.anchors = make(map[string]*node)
|
||||||
|
p.doc = n
|
||||||
|
p.skip()
|
||||||
|
n.children = append(n.children, p.parse())
|
||||||
|
if p.event.typ != yaml_DOCUMENT_END_EVENT {
|
||||||
|
panic("Expected end of document event but got " + strconv.Itoa(int(p.event.typ)))
|
||||||
|
}
|
||||||
|
p.skip()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) alias() *node {
|
||||||
|
n := p.node(aliasNode)
|
||||||
|
n.value = string(p.event.anchor)
|
||||||
|
p.skip()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) scalar() *node {
|
||||||
|
n := p.node(scalarNode)
|
||||||
|
n.value = string(p.event.value)
|
||||||
|
n.tag = string(p.event.tag)
|
||||||
|
n.implicit = p.event.implicit
|
||||||
|
p.anchor(n, p.event.anchor)
|
||||||
|
p.skip()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) sequence() *node {
|
||||||
|
n := p.node(sequenceNode)
|
||||||
|
p.anchor(n, p.event.anchor)
|
||||||
|
p.skip()
|
||||||
|
for p.event.typ != yaml_SEQUENCE_END_EVENT {
|
||||||
|
n.children = append(n.children, p.parse())
|
||||||
|
}
|
||||||
|
p.skip()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) mapping() *node {
|
||||||
|
n := p.node(mappingNode)
|
||||||
|
p.anchor(n, p.event.anchor)
|
||||||
|
p.skip()
|
||||||
|
for p.event.typ != yaml_MAPPING_END_EVENT {
|
||||||
|
n.children = append(n.children, p.parse(), p.parse())
|
||||||
|
}
|
||||||
|
p.skip()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Decoder, unmarshals a node into a provided value.
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
doc *node
|
||||||
|
aliases map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDecoder() *decoder {
|
||||||
|
d := &decoder{}
|
||||||
|
d.aliases = make(map[string]bool)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// d.setter deals with setters and pointer dereferencing and initialization.
|
||||||
|
//
|
||||||
|
// It's a slightly convoluted case to handle properly:
|
||||||
|
//
|
||||||
|
// - nil pointers should be initialized, unless being set to nil
|
||||||
|
// - we don't know at this point yet what's the value to SetYAML() with.
|
||||||
|
// - we can't separate pointer deref/init and setter checking, because
|
||||||
|
// a setter may be found while going down a pointer chain.
|
||||||
|
//
|
||||||
|
// Thus, here is how it takes care of it:
|
||||||
|
//
|
||||||
|
// - out is provided as a pointer, so that it can be replaced.
|
||||||
|
// - when looking at a non-setter ptr, *out=ptr.Elem(), unless tag=!!null
|
||||||
|
// - when a setter is found, *out=interface{}, and a set() function is
|
||||||
|
// returned to call SetYAML() with the value of *out once it's defined.
|
||||||
|
//
|
||||||
|
func (d *decoder) setter(tag string, out *reflect.Value, good *bool) (set func()) {
|
||||||
|
if (*out).Kind() != reflect.Ptr && (*out).CanAddr() {
|
||||||
|
setter, _ := (*out).Addr().Interface().(Setter)
|
||||||
|
if setter != nil {
|
||||||
|
var arg interface{}
|
||||||
|
*out = reflect.ValueOf(&arg).Elem()
|
||||||
|
return func() {
|
||||||
|
*good = setter.SetYAML(shortTag(tag), arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
again := true
|
||||||
|
for again {
|
||||||
|
again = false
|
||||||
|
setter, _ := (*out).Interface().(Setter)
|
||||||
|
if tag != yaml_NULL_TAG || setter != nil {
|
||||||
|
if pv := (*out); pv.Kind() == reflect.Ptr {
|
||||||
|
if pv.IsNil() {
|
||||||
|
*out = reflect.New(pv.Type().Elem()).Elem()
|
||||||
|
pv.Set((*out).Addr())
|
||||||
|
} else {
|
||||||
|
*out = pv.Elem()
|
||||||
|
}
|
||||||
|
setter, _ = pv.Interface().(Setter)
|
||||||
|
again = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if setter != nil {
|
||||||
|
var arg interface{}
|
||||||
|
*out = reflect.ValueOf(&arg).Elem()
|
||||||
|
return func() {
|
||||||
|
*good = setter.SetYAML(shortTag(tag), arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
|
||||||
|
switch n.kind {
|
||||||
|
case documentNode:
|
||||||
|
good = d.document(n, out)
|
||||||
|
case scalarNode:
|
||||||
|
good = d.scalar(n, out)
|
||||||
|
case aliasNode:
|
||||||
|
good = d.alias(n, out)
|
||||||
|
case mappingNode:
|
||||||
|
good = d.mapping(n, out)
|
||||||
|
case sequenceNode:
|
||||||
|
good = d.sequence(n, out)
|
||||||
|
default:
|
||||||
|
panic("Internal error: unknown node kind: " + strconv.Itoa(n.kind))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) document(n *node, out reflect.Value) (good bool) {
|
||||||
|
if len(n.children) == 1 {
|
||||||
|
d.doc = n
|
||||||
|
d.unmarshal(n.children[0], out)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
||||||
|
an, ok := d.doc.anchors[n.value]
|
||||||
|
if !ok {
|
||||||
|
fail("Unknown anchor '" + n.value + "' referenced")
|
||||||
|
}
|
||||||
|
if d.aliases[n.value] {
|
||||||
|
fail("Anchor '" + n.value + "' value contains itself")
|
||||||
|
}
|
||||||
|
d.aliases[n.value] = true
|
||||||
|
good = d.unmarshal(an, out)
|
||||||
|
delete(d.aliases, n.value)
|
||||||
|
return good
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroValue reflect.Value
|
||||||
|
|
||||||
|
func resetMap(out reflect.Value) {
|
||||||
|
for _, k := range out.MapKeys() {
|
||||||
|
out.SetMapIndex(k, zeroValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationType = reflect.TypeOf(time.Duration(0))
|
||||||
|
|
||||||
|
func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
|
||||||
|
var tag string
|
||||||
|
var resolved interface{}
|
||||||
|
if n.tag == "" && !n.implicit {
|
||||||
|
tag = yaml_STR_TAG
|
||||||
|
resolved = n.value
|
||||||
|
} else {
|
||||||
|
tag, resolved = resolve(n.tag, n.value)
|
||||||
|
if tag == yaml_BINARY_TAG {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(resolved.(string))
|
||||||
|
if err != nil {
|
||||||
|
fail("!!binary value contains invalid base64 data")
|
||||||
|
}
|
||||||
|
resolved = string(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if set := d.setter(tag, &out, &good); set != nil {
|
||||||
|
defer set()
|
||||||
|
}
|
||||||
|
if resolved == nil {
|
||||||
|
if out.Kind() == reflect.Map && !out.CanAddr() {
|
||||||
|
resetMap(out)
|
||||||
|
} else {
|
||||||
|
out.Set(reflect.Zero(out.Type()))
|
||||||
|
}
|
||||||
|
good = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch out.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if tag == yaml_BINARY_TAG {
|
||||||
|
out.SetString(resolved.(string))
|
||||||
|
good = true
|
||||||
|
} else if resolved != nil {
|
||||||
|
out.SetString(n.value)
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case reflect.Interface:
|
||||||
|
if resolved == nil {
|
||||||
|
out.Set(reflect.Zero(out.Type()))
|
||||||
|
} else {
|
||||||
|
out.Set(reflect.ValueOf(resolved))
|
||||||
|
}
|
||||||
|
good = true
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
switch resolved := resolved.(type) {
|
||||||
|
case int:
|
||||||
|
if !out.OverflowInt(int64(resolved)) {
|
||||||
|
out.SetInt(int64(resolved))
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case int64:
|
||||||
|
if !out.OverflowInt(resolved) {
|
||||||
|
out.SetInt(resolved)
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
if resolved < 1<<63-1 && !out.OverflowInt(int64(resolved)) {
|
||||||
|
out.SetInt(int64(resolved))
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
if out.Type() == durationType {
|
||||||
|
d, err := time.ParseDuration(resolved)
|
||||||
|
if err == nil {
|
||||||
|
out.SetInt(int64(d))
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
switch resolved := resolved.(type) {
|
||||||
|
case int:
|
||||||
|
if resolved >= 0 {
|
||||||
|
out.SetUint(uint64(resolved))
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case int64:
|
||||||
|
if resolved >= 0 {
|
||||||
|
out.SetUint(uint64(resolved))
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
if resolved < 1<<64-1 && !out.OverflowUint(uint64(resolved)) {
|
||||||
|
out.SetUint(uint64(resolved))
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Bool:
|
||||||
|
switch resolved := resolved.(type) {
|
||||||
|
case bool:
|
||||||
|
out.SetBool(resolved)
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
switch resolved := resolved.(type) {
|
||||||
|
case int:
|
||||||
|
out.SetFloat(float64(resolved))
|
||||||
|
good = true
|
||||||
|
case int64:
|
||||||
|
out.SetFloat(float64(resolved))
|
||||||
|
good = true
|
||||||
|
case float64:
|
||||||
|
out.SetFloat(resolved)
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if out.Type().Elem() == reflect.TypeOf(resolved) {
|
||||||
|
elem := reflect.New(out.Type().Elem())
|
||||||
|
elem.Elem().Set(reflect.ValueOf(resolved))
|
||||||
|
out.Set(elem)
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return good
|
||||||
|
}
|
||||||
|
|
||||||
|
func settableValueOf(i interface{}) reflect.Value {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
sv := reflect.New(v.Type()).Elem()
|
||||||
|
sv.Set(v)
|
||||||
|
return sv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
||||||
|
if set := d.setter(yaml_SEQ_TAG, &out, &good); set != nil {
|
||||||
|
defer set()
|
||||||
|
}
|
||||||
|
var iface reflect.Value
|
||||||
|
if out.Kind() == reflect.Interface {
|
||||||
|
// No type hints. Will have to use a generic sequence.
|
||||||
|
iface = out
|
||||||
|
out = settableValueOf(make([]interface{}, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Kind() != reflect.Slice {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
et := out.Type().Elem()
|
||||||
|
|
||||||
|
l := len(n.children)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
e := reflect.New(et).Elem()
|
||||||
|
if ok := d.unmarshal(n.children[i], e); ok {
|
||||||
|
out.Set(reflect.Append(out, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iface.IsValid() {
|
||||||
|
iface.Set(out)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
||||||
|
if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
|
||||||
|
defer set()
|
||||||
|
}
|
||||||
|
if out.Kind() == reflect.Struct {
|
||||||
|
return d.mappingStruct(n, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Kind() == reflect.Interface {
|
||||||
|
// No type hints. Will have to use a generic map.
|
||||||
|
iface := out
|
||||||
|
out = settableValueOf(make(map[interface{}]interface{}))
|
||||||
|
iface.Set(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Kind() != reflect.Map {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
outt := out.Type()
|
||||||
|
kt := outt.Key()
|
||||||
|
et := outt.Elem()
|
||||||
|
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.MakeMap(outt))
|
||||||
|
}
|
||||||
|
l := len(n.children)
|
||||||
|
for i := 0; i < l; i += 2 {
|
||||||
|
if isMerge(n.children[i]) {
|
||||||
|
d.merge(n.children[i+1], out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k := reflect.New(kt).Elem()
|
||||||
|
if d.unmarshal(n.children[i], k) {
|
||||||
|
kkind := k.Kind()
|
||||||
|
if kkind == reflect.Interface {
|
||||||
|
kkind = k.Elem().Kind()
|
||||||
|
}
|
||||||
|
if kkind == reflect.Map || kkind == reflect.Slice {
|
||||||
|
fail(fmt.Sprintf("invalid map key: %#v", k.Interface()))
|
||||||
|
}
|
||||||
|
e := reflect.New(et).Elem()
|
||||||
|
if d.unmarshal(n.children[i+1], e) {
|
||||||
|
out.SetMapIndex(k, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
|
||||||
|
sinfo, err := getStructInfo(out.Type())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
name := settableValueOf("")
|
||||||
|
l := len(n.children)
|
||||||
|
for i := 0; i < l; i += 2 {
|
||||||
|
ni := n.children[i]
|
||||||
|
if isMerge(ni) {
|
||||||
|
d.merge(n.children[i+1], out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !d.unmarshal(ni, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||||
|
var field reflect.Value
|
||||||
|
if info.Inline == nil {
|
||||||
|
field = out.Field(info.Num)
|
||||||
|
} else {
|
||||||
|
field = out.FieldByIndex(info.Inline)
|
||||||
|
}
|
||||||
|
d.unmarshal(n.children[i+1], field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) merge(n *node, out reflect.Value) {
|
||||||
|
const wantMap = "map merge requires map or sequence of maps as the value"
|
||||||
|
switch n.kind {
|
||||||
|
case mappingNode:
|
||||||
|
d.unmarshal(n, out)
|
||||||
|
case aliasNode:
|
||||||
|
an, ok := d.doc.anchors[n.value]
|
||||||
|
if ok && an.kind != mappingNode {
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
d.unmarshal(n, out)
|
||||||
|
case sequenceNode:
|
||||||
|
// Step backwards as earlier nodes take precedence.
|
||||||
|
for i := len(n.children) - 1; i >= 0; i-- {
|
||||||
|
ni := n.children[i]
|
||||||
|
if ni.kind == aliasNode {
|
||||||
|
an, ok := d.doc.anchors[ni.value]
|
||||||
|
if ok && an.kind != mappingNode {
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
} else if ni.kind != mappingNode {
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
d.unmarshal(ni, out)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMerge(n *node) bool {
|
||||||
|
return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,265 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type encoder struct {
|
||||||
|
emitter yaml_emitter_t
|
||||||
|
event yaml_event_t
|
||||||
|
out []byte
|
||||||
|
flow bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEncoder() (e *encoder) {
|
||||||
|
e = &encoder{}
|
||||||
|
e.must(yaml_emitter_initialize(&e.emitter))
|
||||||
|
yaml_emitter_set_output_string(&e.emitter, &e.out)
|
||||||
|
e.must(yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING))
|
||||||
|
e.emit()
|
||||||
|
e.must(yaml_document_start_event_initialize(&e.event, nil, nil, true))
|
||||||
|
e.emit()
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) finish() {
|
||||||
|
e.must(yaml_document_end_event_initialize(&e.event, true))
|
||||||
|
e.emit()
|
||||||
|
e.emitter.open_ended = false
|
||||||
|
e.must(yaml_stream_end_event_initialize(&e.event))
|
||||||
|
e.emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) destroy() {
|
||||||
|
yaml_emitter_delete(&e.emitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) emit() {
|
||||||
|
// This will internally delete the e.event value.
|
||||||
|
if !yaml_emitter_emit(&e.emitter, &e.event) && e.event.typ != yaml_DOCUMENT_END_EVENT && e.event.typ != yaml_STREAM_END_EVENT {
|
||||||
|
e.must(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) must(ok bool) {
|
||||||
|
if !ok {
|
||||||
|
msg := e.emitter.problem
|
||||||
|
if msg == "" {
|
||||||
|
msg = "Unknown problem generating YAML content"
|
||||||
|
}
|
||||||
|
fail(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||||
|
if !in.IsValid() {
|
||||||
|
e.nilv()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var value interface{}
|
||||||
|
if getter, ok := in.Interface().(Getter); ok {
|
||||||
|
tag, value = getter.GetYAML()
|
||||||
|
tag = longTag(tag)
|
||||||
|
if value == nil {
|
||||||
|
e.nilv()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
in = reflect.ValueOf(value)
|
||||||
|
}
|
||||||
|
switch in.Kind() {
|
||||||
|
case reflect.Interface:
|
||||||
|
if in.IsNil() {
|
||||||
|
e.nilv()
|
||||||
|
} else {
|
||||||
|
e.marshal(tag, in.Elem())
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
e.mapv(tag, in)
|
||||||
|
case reflect.Ptr:
|
||||||
|
if in.IsNil() {
|
||||||
|
e.nilv()
|
||||||
|
} else {
|
||||||
|
e.marshal(tag, in.Elem())
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
e.structv(tag, in)
|
||||||
|
case reflect.Slice:
|
||||||
|
e.slicev(tag, in)
|
||||||
|
case reflect.String:
|
||||||
|
e.stringv(tag, in)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
if in.Type() == durationType {
|
||||||
|
e.stringv(tag, reflect.ValueOf(in.Interface().(time.Duration).String()))
|
||||||
|
} else {
|
||||||
|
e.intv(tag, in)
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
e.uintv(tag, in)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
e.floatv(tag, in)
|
||||||
|
case reflect.Bool:
|
||||||
|
e.boolv(tag, in)
|
||||||
|
default:
|
||||||
|
panic("Can't marshal type: " + in.Type().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) mapv(tag string, in reflect.Value) {
|
||||||
|
e.mappingv(tag, func() {
|
||||||
|
keys := keyList(in.MapKeys())
|
||||||
|
sort.Sort(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
e.marshal("", k)
|
||||||
|
e.marshal("", in.MapIndex(k))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) structv(tag string, in reflect.Value) {
|
||||||
|
sinfo, err := getStructInfo(in.Type())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
e.mappingv(tag, func() {
|
||||||
|
for _, info := range sinfo.FieldsList {
|
||||||
|
var value reflect.Value
|
||||||
|
if info.Inline == nil {
|
||||||
|
value = in.Field(info.Num)
|
||||||
|
} else {
|
||||||
|
value = in.FieldByIndex(info.Inline)
|
||||||
|
}
|
||||||
|
if info.OmitEmpty && isZero(value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
e.marshal("", reflect.ValueOf(info.Key))
|
||||||
|
e.flow = info.Flow
|
||||||
|
e.marshal("", value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) mappingv(tag string, f func()) {
|
||||||
|
implicit := tag == ""
|
||||||
|
style := yaml_BLOCK_MAPPING_STYLE
|
||||||
|
if e.flow {
|
||||||
|
e.flow = false
|
||||||
|
style = yaml_FLOW_MAPPING_STYLE
|
||||||
|
}
|
||||||
|
e.must(yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
||||||
|
e.emit()
|
||||||
|
f()
|
||||||
|
e.must(yaml_mapping_end_event_initialize(&e.event))
|
||||||
|
e.emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) slicev(tag string, in reflect.Value) {
|
||||||
|
implicit := tag == ""
|
||||||
|
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||||
|
if e.flow {
|
||||||
|
e.flow = false
|
||||||
|
style = yaml_FLOW_SEQUENCE_STYLE
|
||||||
|
}
|
||||||
|
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
||||||
|
e.emit()
|
||||||
|
n := in.Len()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
e.marshal("", in.Index(i))
|
||||||
|
}
|
||||||
|
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||||
|
e.emit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
||||||
|
//
|
||||||
|
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
||||||
|
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
||||||
|
// the time being for compatibility with other parsers.
|
||||||
|
func isBase60Float(s string) (result bool) {
|
||||||
|
// Fast path.
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := s[0]
|
||||||
|
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Do the full match.
|
||||||
|
return base60float.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From http://yaml.org/type/float.html, except the regular expression there
|
||||||
|
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
||||||
|
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
||||||
|
|
||||||
|
func (e *encoder) stringv(tag string, in reflect.Value) {
|
||||||
|
var style yaml_scalar_style_t
|
||||||
|
s := in.String()
|
||||||
|
rtag, rs := resolve("", s)
|
||||||
|
if rtag == yaml_BINARY_TAG {
|
||||||
|
if tag == "" || tag == yaml_STR_TAG {
|
||||||
|
tag = rtag
|
||||||
|
s = rs.(string)
|
||||||
|
} else if tag == yaml_BINARY_TAG {
|
||||||
|
fail("explicitly tagged !!binary data must be base64-encoded")
|
||||||
|
} else {
|
||||||
|
fail("cannot marshal invalid UTF-8 data as " + shortTag(tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) {
|
||||||
|
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||||
|
} else if strings.Contains(s, "\n") {
|
||||||
|
style = yaml_LITERAL_SCALAR_STYLE
|
||||||
|
} else {
|
||||||
|
style = yaml_PLAIN_SCALAR_STYLE
|
||||||
|
}
|
||||||
|
e.emitScalar(s, "", tag, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) boolv(tag string, in reflect.Value) {
|
||||||
|
var s string
|
||||||
|
if in.Bool() {
|
||||||
|
s = "true"
|
||||||
|
} else {
|
||||||
|
s = "false"
|
||||||
|
}
|
||||||
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) intv(tag string, in reflect.Value) {
|
||||||
|
s := strconv.FormatInt(in.Int(), 10)
|
||||||
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) uintv(tag string, in reflect.Value) {
|
||||||
|
s := strconv.FormatUint(in.Uint(), 10)
|
||||||
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) floatv(tag string, in reflect.Value) {
|
||||||
|
// FIXME: Handle 64 bits here.
|
||||||
|
s := strconv.FormatFloat(float64(in.Float()), 'g', -1, 32)
|
||||||
|
switch s {
|
||||||
|
case "+Inf":
|
||||||
|
s = ".inf"
|
||||||
|
case "-Inf":
|
||||||
|
s = "-.inf"
|
||||||
|
case "NaN":
|
||||||
|
s = ".nan"
|
||||||
|
}
|
||||||
|
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) nilv() {
|
||||||
|
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
|
||||||
|
implicit := tag == ""
|
||||||
|
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
||||||
|
e.emit()
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,391 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set the reader error and return 0.
|
||||||
|
func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool {
|
||||||
|
parser.error = yaml_READER_ERROR
|
||||||
|
parser.problem = problem
|
||||||
|
parser.problem_offset = offset
|
||||||
|
parser.problem_value = value
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte order marks.
|
||||||
|
const (
|
||||||
|
bom_UTF8 = "\xef\xbb\xbf"
|
||||||
|
bom_UTF16LE = "\xff\xfe"
|
||||||
|
bom_UTF16BE = "\xfe\xff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Determine the input stream encoding by checking the BOM symbol. If no BOM is
|
||||||
|
// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure.
|
||||||
|
func yaml_parser_determine_encoding(parser *yaml_parser_t) bool {
|
||||||
|
// Ensure that we had enough bytes in the raw buffer.
|
||||||
|
for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 {
|
||||||
|
if !yaml_parser_update_raw_buffer(parser) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the encoding.
|
||||||
|
buf := parser.raw_buffer
|
||||||
|
pos := parser.raw_buffer_pos
|
||||||
|
avail := len(buf) - pos
|
||||||
|
if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] {
|
||||||
|
parser.encoding = yaml_UTF16LE_ENCODING
|
||||||
|
parser.raw_buffer_pos += 2
|
||||||
|
parser.offset += 2
|
||||||
|
} else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] {
|
||||||
|
parser.encoding = yaml_UTF16BE_ENCODING
|
||||||
|
parser.raw_buffer_pos += 2
|
||||||
|
parser.offset += 2
|
||||||
|
} else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] {
|
||||||
|
parser.encoding = yaml_UTF8_ENCODING
|
||||||
|
parser.raw_buffer_pos += 3
|
||||||
|
parser.offset += 3
|
||||||
|
} else {
|
||||||
|
parser.encoding = yaml_UTF8_ENCODING
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the raw buffer.
|
||||||
|
func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool {
|
||||||
|
size_read := 0
|
||||||
|
|
||||||
|
// Return if the raw buffer is full.
|
||||||
|
if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return on EOF.
|
||||||
|
if parser.eof {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the remaining bytes in the raw buffer to the beginning.
|
||||||
|
if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) {
|
||||||
|
copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:])
|
||||||
|
}
|
||||||
|
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos]
|
||||||
|
parser.raw_buffer_pos = 0
|
||||||
|
|
||||||
|
// Call the read handler to fill the buffer.
|
||||||
|
size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)])
|
||||||
|
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read]
|
||||||
|
if err == io.EOF {
|
||||||
|
parser.eof = true
|
||||||
|
} else if err != nil {
|
||||||
|
return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the buffer contains at least `length` characters.
|
||||||
|
// Return true on success, false on failure.
|
||||||
|
//
|
||||||
|
// The length is supposed to be significantly less that the buffer size.
|
||||||
|
func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
|
||||||
|
if parser.read_handler == nil {
|
||||||
|
panic("read handler must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the EOF flag is set and the raw buffer is empty, do nothing.
|
||||||
|
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if the buffer contains enough characters.
|
||||||
|
if parser.unread >= length {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the input encoding if it is not known yet.
|
||||||
|
if parser.encoding == yaml_ANY_ENCODING {
|
||||||
|
if !yaml_parser_determine_encoding(parser) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the unread characters to the beginning of the buffer.
|
||||||
|
buffer_len := len(parser.buffer)
|
||||||
|
if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len {
|
||||||
|
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
|
||||||
|
buffer_len -= parser.buffer_pos
|
||||||
|
parser.buffer_pos = 0
|
||||||
|
} else if parser.buffer_pos == buffer_len {
|
||||||
|
buffer_len = 0
|
||||||
|
parser.buffer_pos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the whole buffer for writing, and cut it before returning.
|
||||||
|
parser.buffer = parser.buffer[:cap(parser.buffer)]
|
||||||
|
|
||||||
|
// Fill the buffer until it has enough characters.
|
||||||
|
first := true
|
||||||
|
for parser.unread < length {
|
||||||
|
|
||||||
|
// Fill the raw buffer if necessary.
|
||||||
|
if !first || parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||||
|
if !yaml_parser_update_raw_buffer(parser) {
|
||||||
|
parser.buffer = parser.buffer[:buffer_len]
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
|
||||||
|
// Decode the raw buffer.
|
||||||
|
inner:
|
||||||
|
for parser.raw_buffer_pos != len(parser.raw_buffer) {
|
||||||
|
var value rune
|
||||||
|
var width int
|
||||||
|
|
||||||
|
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
|
||||||
|
|
||||||
|
// Decode the next character.
|
||||||
|
switch parser.encoding {
|
||||||
|
case yaml_UTF8_ENCODING:
|
||||||
|
// Decode a UTF-8 character. Check RFC 3629
|
||||||
|
// (http://www.ietf.org/rfc/rfc3629.txt) for more details.
|
||||||
|
//
|
||||||
|
// The following table (taken from the RFC) is used for
|
||||||
|
// decoding.
|
||||||
|
//
|
||||||
|
// Char. number range | UTF-8 octet sequence
|
||||||
|
// (hexadecimal) | (binary)
|
||||||
|
// --------------------+------------------------------------
|
||||||
|
// 0000 0000-0000 007F | 0xxxxxxx
|
||||||
|
// 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
|
||||||
|
// 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
|
||||||
|
// 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||||
|
//
|
||||||
|
// Additionally, the characters in the range 0xD800-0xDFFF
|
||||||
|
// are prohibited as they are reserved for use with UTF-16
|
||||||
|
// surrogate pairs.
|
||||||
|
|
||||||
|
// Determine the length of the UTF-8 sequence.
|
||||||
|
octet := parser.raw_buffer[parser.raw_buffer_pos]
|
||||||
|
switch {
|
||||||
|
case octet&0x80 == 0x00:
|
||||||
|
width = 1
|
||||||
|
case octet&0xE0 == 0xC0:
|
||||||
|
width = 2
|
||||||
|
case octet&0xF0 == 0xE0:
|
||||||
|
width = 3
|
||||||
|
case octet&0xF8 == 0xF0:
|
||||||
|
width = 4
|
||||||
|
default:
|
||||||
|
// The leading octet is invalid.
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"invalid leading UTF-8 octet",
|
||||||
|
parser.offset, int(octet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the raw buffer contains an incomplete character.
|
||||||
|
if width > raw_unread {
|
||||||
|
if parser.eof {
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"incomplete UTF-8 octet sequence",
|
||||||
|
parser.offset, -1)
|
||||||
|
}
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the leading octet.
|
||||||
|
switch {
|
||||||
|
case octet&0x80 == 0x00:
|
||||||
|
value = rune(octet & 0x7F)
|
||||||
|
case octet&0xE0 == 0xC0:
|
||||||
|
value = rune(octet & 0x1F)
|
||||||
|
case octet&0xF0 == 0xE0:
|
||||||
|
value = rune(octet & 0x0F)
|
||||||
|
case octet&0xF8 == 0xF0:
|
||||||
|
value = rune(octet & 0x07)
|
||||||
|
default:
|
||||||
|
value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and decode the trailing octets.
|
||||||
|
for k := 1; k < width; k++ {
|
||||||
|
octet = parser.raw_buffer[parser.raw_buffer_pos+k]
|
||||||
|
|
||||||
|
// Check if the octet is valid.
|
||||||
|
if (octet & 0xC0) != 0x80 {
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"invalid trailing UTF-8 octet",
|
||||||
|
parser.offset+k, int(octet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the octet.
|
||||||
|
value = (value << 6) + rune(octet&0x3F)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the length of the sequence against the value.
|
||||||
|
switch {
|
||||||
|
case width == 1:
|
||||||
|
case width == 2 && value >= 0x80:
|
||||||
|
case width == 3 && value >= 0x800:
|
||||||
|
case width == 4 && value >= 0x10000:
|
||||||
|
default:
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"invalid length of a UTF-8 sequence",
|
||||||
|
parser.offset, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the range of the value.
|
||||||
|
if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF {
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"invalid Unicode character",
|
||||||
|
parser.offset, int(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING:
|
||||||
|
var low, high int
|
||||||
|
if parser.encoding == yaml_UTF16LE_ENCODING {
|
||||||
|
low, high = 0, 1
|
||||||
|
} else {
|
||||||
|
high, low = 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// The UTF-16 encoding is not as simple as one might
|
||||||
|
// naively think. Check RFC 2781
|
||||||
|
// (http://www.ietf.org/rfc/rfc2781.txt).
|
||||||
|
//
|
||||||
|
// Normally, two subsequent bytes describe a Unicode
|
||||||
|
// character. However a special technique (called a
|
||||||
|
// surrogate pair) is used for specifying character
|
||||||
|
// values larger than 0xFFFF.
|
||||||
|
//
|
||||||
|
// A surrogate pair consists of two pseudo-characters:
|
||||||
|
// high surrogate area (0xD800-0xDBFF)
|
||||||
|
// low surrogate area (0xDC00-0xDFFF)
|
||||||
|
//
|
||||||
|
// The following formulas are used for decoding
|
||||||
|
// and encoding characters using surrogate pairs:
|
||||||
|
//
|
||||||
|
// U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF)
|
||||||
|
// U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF)
|
||||||
|
// W1 = 110110yyyyyyyyyy
|
||||||
|
// W2 = 110111xxxxxxxxxx
|
||||||
|
//
|
||||||
|
// where U is the character value, W1 is the high surrogate
|
||||||
|
// area, W2 is the low surrogate area.
|
||||||
|
|
||||||
|
// Check for incomplete UTF-16 character.
|
||||||
|
if raw_unread < 2 {
|
||||||
|
if parser.eof {
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"incomplete UTF-16 character",
|
||||||
|
parser.offset, -1)
|
||||||
|
}
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the character.
|
||||||
|
value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) +
|
||||||
|
(rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8)
|
||||||
|
|
||||||
|
// Check for unexpected low surrogate area.
|
||||||
|
if value&0xFC00 == 0xDC00 {
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"unexpected low surrogate area",
|
||||||
|
parser.offset, int(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a high surrogate area.
|
||||||
|
if value&0xFC00 == 0xD800 {
|
||||||
|
width = 4
|
||||||
|
|
||||||
|
// Check for incomplete surrogate pair.
|
||||||
|
if raw_unread < 4 {
|
||||||
|
if parser.eof {
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"incomplete UTF-16 surrogate pair",
|
||||||
|
parser.offset, -1)
|
||||||
|
}
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next character.
|
||||||
|
value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) +
|
||||||
|
(rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8)
|
||||||
|
|
||||||
|
// Check for a low surrogate area.
|
||||||
|
if value2&0xFC00 != 0xDC00 {
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"expected low surrogate area",
|
||||||
|
parser.offset+2, int(value2))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the value of the surrogate pair.
|
||||||
|
value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF)
|
||||||
|
} else {
|
||||||
|
width = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("impossible")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character is in the allowed range:
|
||||||
|
// #x9 | #xA | #xD | [#x20-#x7E] (8 bit)
|
||||||
|
// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit)
|
||||||
|
// | [#x10000-#x10FFFF] (32 bit)
|
||||||
|
switch {
|
||||||
|
case value == 0x09:
|
||||||
|
case value == 0x0A:
|
||||||
|
case value == 0x0D:
|
||||||
|
case value >= 0x20 && value <= 0x7E:
|
||||||
|
case value == 0x85:
|
||||||
|
case value >= 0xA0 && value <= 0xD7FF:
|
||||||
|
case value >= 0xE000 && value <= 0xFFFD:
|
||||||
|
case value >= 0x10000 && value <= 0x10FFFF:
|
||||||
|
default:
|
||||||
|
return yaml_parser_set_reader_error(parser,
|
||||||
|
"control characters are not allowed",
|
||||||
|
parser.offset, int(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the raw pointers.
|
||||||
|
parser.raw_buffer_pos += width
|
||||||
|
parser.offset += width
|
||||||
|
|
||||||
|
// Finally put the character into the buffer.
|
||||||
|
if value <= 0x7F {
|
||||||
|
// 0000 0000-0000 007F . 0xxxxxxx
|
||||||
|
parser.buffer[buffer_len+0] = byte(value)
|
||||||
|
} else if value <= 0x7FF {
|
||||||
|
// 0000 0080-0000 07FF . 110xxxxx 10xxxxxx
|
||||||
|
parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6))
|
||||||
|
parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F))
|
||||||
|
} else if value <= 0xFFFF {
|
||||||
|
// 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx
|
||||||
|
parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12))
|
||||||
|
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||||
|
parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F))
|
||||||
|
} else {
|
||||||
|
// 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||||
|
parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18))
|
||||||
|
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F))
|
||||||
|
parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||||
|
parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F))
|
||||||
|
}
|
||||||
|
buffer_len += width
|
||||||
|
|
||||||
|
parser.unread++
|
||||||
|
}
|
||||||
|
|
||||||
|
// On EOF, put NUL into the buffer and return.
|
||||||
|
if parser.eof {
|
||||||
|
parser.buffer[buffer_len] = 0
|
||||||
|
buffer_len++
|
||||||
|
parser.unread++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parser.buffer = parser.buffer[:buffer_len]
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: merge, timestamps, base 60 floats, omap.
|
||||||
|
|
||||||
|
type resolveMapItem struct {
|
||||||
|
value interface{}
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolveTable = make([]byte, 256)
|
||||||
|
var resolveMap = make(map[string]resolveMapItem)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
t := resolveTable
|
||||||
|
t[int('+')] = 'S' // Sign
|
||||||
|
t[int('-')] = 'S'
|
||||||
|
for _, c := range "0123456789" {
|
||||||
|
t[int(c)] = 'D' // Digit
|
||||||
|
}
|
||||||
|
for _, c := range "yYnNtTfFoO~" {
|
||||||
|
t[int(c)] = 'M' // In map
|
||||||
|
}
|
||||||
|
t[int('.')] = '.' // Float (potentially in map)
|
||||||
|
|
||||||
|
var resolveMapList = []struct {
|
||||||
|
v interface{}
|
||||||
|
tag string
|
||||||
|
l []string
|
||||||
|
}{
|
||||||
|
{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
|
||||||
|
{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
|
||||||
|
{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
|
||||||
|
{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
|
||||||
|
{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
|
||||||
|
{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
|
||||||
|
{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
|
||||||
|
{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
|
||||||
|
{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
|
||||||
|
{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
|
||||||
|
{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
|
||||||
|
{"<<", yaml_MERGE_TAG, []string{"<<"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := resolveMap
|
||||||
|
for _, item := range resolveMapList {
|
||||||
|
for _, s := range item.l {
|
||||||
|
m[s] = resolveMapItem{item.v, item.tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const longTagPrefix = "tag:yaml.org,2002:"
|
||||||
|
|
||||||
|
func shortTag(tag string) string {
|
||||||
|
// TODO This can easily be made faster and produce less garbage.
|
||||||
|
if strings.HasPrefix(tag, longTagPrefix) {
|
||||||
|
return "!!" + tag[len(longTagPrefix):]
|
||||||
|
}
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func longTag(tag string) string {
|
||||||
|
if strings.HasPrefix(tag, "!!") {
|
||||||
|
return longTagPrefix + tag[2:]
|
||||||
|
}
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvableTag(tag string) bool {
|
||||||
|
switch tag {
|
||||||
|
case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||||
|
if !resolvableTag(tag) {
|
||||||
|
return tag, in
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
switch tag {
|
||||||
|
case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fail(fmt.Sprintf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Any data is accepted as a !!str or !!binary.
|
||||||
|
// Otherwise, the prefix is enough of a hint about what it might be.
|
||||||
|
hint := byte('N')
|
||||||
|
if in != "" {
|
||||||
|
hint = resolveTable[in[0]]
|
||||||
|
}
|
||||||
|
if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
|
||||||
|
// Handle things we can lookup in a map.
|
||||||
|
if item, ok := resolveMap[in]; ok {
|
||||||
|
return item.tag, item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
|
||||||
|
// are purposefully unsupported here. They're still quoted on
|
||||||
|
// the way out for compatibility with other parser, though.
|
||||||
|
|
||||||
|
switch hint {
|
||||||
|
case 'M':
|
||||||
|
// We've already checked the map above.
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
// Not in the map, so maybe a normal float.
|
||||||
|
floatv, err := strconv.ParseFloat(in, 64)
|
||||||
|
if err == nil {
|
||||||
|
return yaml_FLOAT_TAG, floatv
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'D', 'S':
|
||||||
|
// Int, float, or timestamp.
|
||||||
|
plain := strings.Replace(in, "_", "", -1)
|
||||||
|
intv, err := strconv.ParseInt(plain, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
if intv == int64(int(intv)) {
|
||||||
|
return yaml_INT_TAG, int(intv)
|
||||||
|
} else {
|
||||||
|
return yaml_INT_TAG, intv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
floatv, err := strconv.ParseFloat(plain, 64)
|
||||||
|
if err == nil {
|
||||||
|
return yaml_FLOAT_TAG, floatv
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(plain, "0b") {
|
||||||
|
intv, err := strconv.ParseInt(plain[2:], 2, 64)
|
||||||
|
if err == nil {
|
||||||
|
return yaml_INT_TAG, int(intv)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(plain, "-0b") {
|
||||||
|
intv, err := strconv.ParseInt(plain[3:], 2, 64)
|
||||||
|
if err == nil {
|
||||||
|
return yaml_INT_TAG, -int(intv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// XXX Handle timestamps here.
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tag == yaml_BINARY_TAG {
|
||||||
|
return yaml_BINARY_TAG, in
|
||||||
|
}
|
||||||
|
if utf8.ValidString(in) {
|
||||||
|
return yaml_STR_TAG, in
|
||||||
|
}
|
||||||
|
return yaml_BINARY_TAG, encodeBase64(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeBase64 encodes s as base64 that is broken up into multiple lines
|
||||||
|
// as appropriate for the resulting length.
|
||||||
|
func encodeBase64(s string) string {
|
||||||
|
const lineLen = 70
|
||||||
|
encLen := base64.StdEncoding.EncodedLen(len(s))
|
||||||
|
lines := encLen/lineLen + 1
|
||||||
|
buf := make([]byte, encLen*2+lines)
|
||||||
|
in := buf[0:encLen]
|
||||||
|
out := buf[encLen:]
|
||||||
|
base64.StdEncoding.Encode(in, []byte(s))
|
||||||
|
k := 0
|
||||||
|
for i := 0; i < len(in); i += lineLen {
|
||||||
|
j := i + lineLen
|
||||||
|
if j > len(in) {
|
||||||
|
j = len(in)
|
||||||
|
}
|
||||||
|
k += copy(out[k:], in[i:j])
|
||||||
|
if lines > 1 {
|
||||||
|
out[k] = '\n'
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(out[:k])
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,104 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type keyList []reflect.Value
|
||||||
|
|
||||||
|
func (l keyList) Len() int { return len(l) }
|
||||||
|
func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||||
|
func (l keyList) Less(i, j int) bool {
|
||||||
|
a := l[i]
|
||||||
|
b := l[j]
|
||||||
|
ak := a.Kind()
|
||||||
|
bk := b.Kind()
|
||||||
|
for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() {
|
||||||
|
a = a.Elem()
|
||||||
|
ak = a.Kind()
|
||||||
|
}
|
||||||
|
for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() {
|
||||||
|
b = b.Elem()
|
||||||
|
bk = b.Kind()
|
||||||
|
}
|
||||||
|
af, aok := keyFloat(a)
|
||||||
|
bf, bok := keyFloat(b)
|
||||||
|
if aok && bok {
|
||||||
|
if af != bf {
|
||||||
|
return af < bf
|
||||||
|
}
|
||||||
|
if ak != bk {
|
||||||
|
return ak < bk
|
||||||
|
}
|
||||||
|
return numLess(a, b)
|
||||||
|
}
|
||||||
|
if ak != reflect.String || bk != reflect.String {
|
||||||
|
return ak < bk
|
||||||
|
}
|
||||||
|
ar, br := []rune(a.String()), []rune(b.String())
|
||||||
|
for i := 0; i < len(ar) && i < len(br); i++ {
|
||||||
|
if ar[i] == br[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
al := unicode.IsLetter(ar[i])
|
||||||
|
bl := unicode.IsLetter(br[i])
|
||||||
|
if al && bl {
|
||||||
|
return ar[i] < br[i]
|
||||||
|
}
|
||||||
|
if al || bl {
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
var ai, bi int
|
||||||
|
var an, bn int64
|
||||||
|
for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ {
|
||||||
|
an = an*10 + int64(ar[ai]-'0')
|
||||||
|
}
|
||||||
|
for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ {
|
||||||
|
bn = bn*10 + int64(br[bi]-'0')
|
||||||
|
}
|
||||||
|
if an != bn {
|
||||||
|
return an < bn
|
||||||
|
}
|
||||||
|
if ai != bi {
|
||||||
|
return ai < bi
|
||||||
|
}
|
||||||
|
return ar[i] < br[i]
|
||||||
|
}
|
||||||
|
return len(ar) < len(br)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyFloat returns a float value for v if it is a number/bool
|
||||||
|
// and whether it is a number/bool or not.
|
||||||
|
func keyFloat(v reflect.Value) (f float64, ok bool) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return float64(v.Int()), true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float(), true
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return float64(v.Uint()), true
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// numLess returns whether a < b.
|
||||||
|
// a and b must necessarily have the same kind.
|
||||||
|
func numLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
}
|
||||||
|
panic("not a number")
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
// Set the writer error and return false.
|
||||||
|
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
|
||||||
|
emitter.error = yaml_WRITER_ERROR
|
||||||
|
emitter.problem = problem
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the output buffer.
|
||||||
|
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
|
||||||
|
if emitter.write_handler == nil {
|
||||||
|
panic("write handler not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the buffer is empty.
|
||||||
|
if emitter.buffer_pos == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the output encoding is UTF-8, we don't need to recode the buffer.
|
||||||
|
if emitter.encoding == yaml_UTF8_ENCODING {
|
||||||
|
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
|
||||||
|
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
|
||||||
|
}
|
||||||
|
emitter.buffer_pos = 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recode the buffer into the raw buffer.
|
||||||
|
var low, high int
|
||||||
|
if emitter.encoding == yaml_UTF16LE_ENCODING {
|
||||||
|
low, high = 0, 1
|
||||||
|
} else {
|
||||||
|
high, low = 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := 0
|
||||||
|
for pos < emitter.buffer_pos {
|
||||||
|
// See the "reader.c" code for more details on UTF-8 encoding. Note
|
||||||
|
// that we assume that the buffer contains a valid UTF-8 sequence.
|
||||||
|
|
||||||
|
// Read the next UTF-8 character.
|
||||||
|
octet := emitter.buffer[pos]
|
||||||
|
|
||||||
|
var w int
|
||||||
|
var value rune
|
||||||
|
switch {
|
||||||
|
case octet&0x80 == 0x00:
|
||||||
|
w, value = 1, rune(octet&0x7F)
|
||||||
|
case octet&0xE0 == 0xC0:
|
||||||
|
w, value = 2, rune(octet&0x1F)
|
||||||
|
case octet&0xF0 == 0xE0:
|
||||||
|
w, value = 3, rune(octet&0x0F)
|
||||||
|
case octet&0xF8 == 0xF0:
|
||||||
|
w, value = 4, rune(octet&0x07)
|
||||||
|
}
|
||||||
|
for k := 1; k < w; k++ {
|
||||||
|
octet = emitter.buffer[pos+k]
|
||||||
|
value = (value << 6) + (rune(octet) & 0x3F)
|
||||||
|
}
|
||||||
|
pos += w
|
||||||
|
|
||||||
|
// Write the character.
|
||||||
|
if value < 0x10000 {
|
||||||
|
var b [2]byte
|
||||||
|
b[high] = byte(value >> 8)
|
||||||
|
b[low] = byte(value & 0xFF)
|
||||||
|
emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1])
|
||||||
|
} else {
|
||||||
|
// Write the character using a surrogate pair (check "reader.c").
|
||||||
|
var b [4]byte
|
||||||
|
value -= 0x10000
|
||||||
|
b[high] = byte(0xD8 + (value >> 18))
|
||||||
|
b[low] = byte((value >> 10) & 0xFF)
|
||||||
|
b[high+2] = byte(0xDC + ((value >> 8) & 0xFF))
|
||||||
|
b[low+2] = byte(value & 0xFF)
|
||||||
|
emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1], b[2], b[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the raw buffer.
|
||||||
|
if err := emitter.write_handler(emitter, emitter.raw_buffer); err != nil {
|
||||||
|
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
|
||||||
|
}
|
||||||
|
emitter.buffer_pos = 0
|
||||||
|
emitter.raw_buffer = emitter.raw_buffer[:0]
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,301 @@
|
||||||
|
// Package yaml implements YAML support for the Go language.
|
||||||
|
//
|
||||||
|
// Source code and other details for the project are available at GitHub:
|
||||||
|
//
|
||||||
|
// https://github.com/go-yaml/yaml
|
||||||
|
//
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type yamlError string
|
||||||
|
|
||||||
|
func fail(msg string) {
|
||||||
|
panic(yamlError(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleErr(err *error) {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if e, ok := r.(yamlError); ok {
|
||||||
|
*err = errors.New("YAML error: " + string(e))
|
||||||
|
} else {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Setter interface may be implemented by types to do their own custom
|
||||||
|
// unmarshalling of YAML values, rather than being implicitly assigned by
|
||||||
|
// the yaml package machinery. If setting the value works, the method should
|
||||||
|
// return true. If it returns false, the value is considered unsupported
|
||||||
|
// and is omitted from maps and slices.
|
||||||
|
type Setter interface {
|
||||||
|
SetYAML(tag string, value interface{}) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Getter interface is implemented by types to do their own custom
|
||||||
|
// marshalling into a YAML tag and value.
|
||||||
|
type Getter interface {
|
||||||
|
GetYAML() (tag string, value interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes the first document found within the in byte slice
|
||||||
|
// and assigns decoded values into the out value.
|
||||||
|
//
|
||||||
|
// Maps and pointers (to a struct, string, int, etc) are accepted as out
|
||||||
|
// values. If an internal pointer within a struct is not initialized,
|
||||||
|
// the yaml package will initialize it if necessary for unmarshalling
|
||||||
|
// the provided data. The out parameter must not be nil.
|
||||||
|
//
|
||||||
|
// The type of the decoded values and the type of out will be considered,
|
||||||
|
// and Unmarshal will do the best possible job to unmarshal values
|
||||||
|
// appropriately. It is NOT considered an error, though, to skip values
|
||||||
|
// because they are not available in the decoded YAML, or if they are not
|
||||||
|
// compatible with the out value. To ensure something was properly
|
||||||
|
// unmarshaled use a map or compare against the previous value for the
|
||||||
|
// field (usually the zero value).
|
||||||
|
//
|
||||||
|
// Struct fields are only unmarshalled if they are exported (have an
|
||||||
|
// upper case first letter), and are unmarshalled using the field name
|
||||||
|
// lowercased as the default key. Custom keys may be defined via the
|
||||||
|
// "yaml" name in the field tag: the content preceding the first comma
|
||||||
|
// is used as the key, and the following comma-separated options are
|
||||||
|
// used to tweak the marshalling process (see Marshal).
|
||||||
|
// Conflicting names result in a runtime error.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// type T struct {
|
||||||
|
// F int `yaml:"a,omitempty"`
|
||||||
|
// B int
|
||||||
|
// }
|
||||||
|
// var t T
|
||||||
|
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||||
|
//
|
||||||
|
// See the documentation of Marshal for the format of tags and a list of
|
||||||
|
// supported tag options.
|
||||||
|
//
|
||||||
|
func Unmarshal(in []byte, out interface{}) (err error) {
|
||||||
|
defer handleErr(&err)
|
||||||
|
d := newDecoder()
|
||||||
|
p := newParser(in)
|
||||||
|
defer p.destroy()
|
||||||
|
node := p.parse()
|
||||||
|
if node != nil {
|
||||||
|
v := reflect.ValueOf(out)
|
||||||
|
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
d.unmarshal(node, v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal serializes the value provided into a YAML document. The structure
|
||||||
|
// of the generated document will reflect the structure of the value itself.
|
||||||
|
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||||
|
//
|
||||||
|
// Struct fields are only unmarshalled if they are exported (have an upper case
|
||||||
|
// first letter), and are unmarshalled using the field name lowercased as the
|
||||||
|
// default key. Custom keys may be defined via the "yaml" name in the field
|
||||||
|
// tag: the content preceding the first comma is used as the key, and the
|
||||||
|
// following comma-separated options are used to tweak the marshalling process.
|
||||||
|
// Conflicting names result in a runtime error.
|
||||||
|
//
|
||||||
|
// The field tag format accepted is:
|
||||||
|
//
|
||||||
|
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||||
|
//
|
||||||
|
// The following flags are currently supported:
|
||||||
|
//
|
||||||
|
// omitempty Only include the field if it's not set to the zero
|
||||||
|
// value for the type or to empty slices or maps.
|
||||||
|
// Does not apply to zero valued structs.
|
||||||
|
//
|
||||||
|
// flow Marshal using a flow style (useful for structs,
|
||||||
|
// sequences and maps.
|
||||||
|
//
|
||||||
|
// inline Inline the struct it's applied to, so its fields
|
||||||
|
// are processed as if they were part of the outer
|
||||||
|
// struct.
|
||||||
|
//
|
||||||
|
// In addition, if the key is "-", the field is ignored.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// type T struct {
|
||||||
|
// F int "a,omitempty"
|
||||||
|
// B int
|
||||||
|
// }
|
||||||
|
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||||
|
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
||||||
|
//
|
||||||
|
func Marshal(in interface{}) (out []byte, err error) {
|
||||||
|
defer handleErr(&err)
|
||||||
|
e := newEncoder()
|
||||||
|
defer e.destroy()
|
||||||
|
e.marshal("", reflect.ValueOf(in))
|
||||||
|
e.finish()
|
||||||
|
out = e.out
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Maintain a mapping of keys to structure field indexes
|
||||||
|
|
||||||
|
// The code in this section was copied from mgo/bson.
|
||||||
|
|
||||||
|
// structInfo holds details for the serialization of fields of
|
||||||
|
// a given struct.
|
||||||
|
type structInfo struct {
|
||||||
|
FieldsMap map[string]fieldInfo
|
||||||
|
FieldsList []fieldInfo
|
||||||
|
|
||||||
|
// InlineMap is the number of the field in the struct that
|
||||||
|
// contains an ,inline map, or -1 if there's none.
|
||||||
|
InlineMap int
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldInfo struct {
|
||||||
|
Key string
|
||||||
|
Num int
|
||||||
|
OmitEmpty bool
|
||||||
|
Flow bool
|
||||||
|
|
||||||
|
// Inline holds the field index if the field is part of an inlined struct.
|
||||||
|
Inline []int
|
||||||
|
}
|
||||||
|
|
||||||
|
var structMap = make(map[reflect.Type]*structInfo)
|
||||||
|
var fieldMapMutex sync.RWMutex
|
||||||
|
|
||||||
|
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
||||||
|
fieldMapMutex.RLock()
|
||||||
|
sinfo, found := structMap[st]
|
||||||
|
fieldMapMutex.RUnlock()
|
||||||
|
if found {
|
||||||
|
return sinfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := st.NumField()
|
||||||
|
fieldsMap := make(map[string]fieldInfo)
|
||||||
|
fieldsList := make([]fieldInfo, 0, n)
|
||||||
|
inlineMap := -1
|
||||||
|
for i := 0; i != n; i++ {
|
||||||
|
field := st.Field(i)
|
||||||
|
if field.PkgPath != "" {
|
||||||
|
continue // Private field
|
||||||
|
}
|
||||||
|
|
||||||
|
info := fieldInfo{Num: i}
|
||||||
|
|
||||||
|
tag := field.Tag.Get("yaml")
|
||||||
|
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
|
||||||
|
tag = string(field.Tag)
|
||||||
|
}
|
||||||
|
if tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
inline := false
|
||||||
|
fields := strings.Split(tag, ",")
|
||||||
|
if len(fields) > 1 {
|
||||||
|
for _, flag := range fields[1:] {
|
||||||
|
switch flag {
|
||||||
|
case "omitempty":
|
||||||
|
info.OmitEmpty = true
|
||||||
|
case "flow":
|
||||||
|
info.Flow = true
|
||||||
|
case "inline":
|
||||||
|
inline = true
|
||||||
|
default:
|
||||||
|
return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag = fields[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if inline {
|
||||||
|
switch field.Type.Kind() {
|
||||||
|
// TODO: Implement support for inline maps.
|
||||||
|
//case reflect.Map:
|
||||||
|
// if inlineMap >= 0 {
|
||||||
|
// return nil, errors.New("Multiple ,inline maps in struct " + st.String())
|
||||||
|
// }
|
||||||
|
// if field.Type.Key() != reflect.TypeOf("") {
|
||||||
|
// return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
|
||||||
|
// }
|
||||||
|
// inlineMap = info.Num
|
||||||
|
case reflect.Struct:
|
||||||
|
sinfo, err := getStructInfo(field.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, finfo := range sinfo.FieldsList {
|
||||||
|
if _, found := fieldsMap[finfo.Key]; found {
|
||||||
|
msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
|
||||||
|
return nil, errors.New(msg)
|
||||||
|
}
|
||||||
|
if finfo.Inline == nil {
|
||||||
|
finfo.Inline = []int{i, finfo.Num}
|
||||||
|
} else {
|
||||||
|
finfo.Inline = append([]int{i}, finfo.Inline...)
|
||||||
|
}
|
||||||
|
fieldsMap[finfo.Key] = finfo
|
||||||
|
fieldsList = append(fieldsList, finfo)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
//return nil, errors.New("Option ,inline needs a struct value or map field")
|
||||||
|
return nil, errors.New("Option ,inline needs a struct value field")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag != "" {
|
||||||
|
info.Key = tag
|
||||||
|
} else {
|
||||||
|
info.Key = strings.ToLower(field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found = fieldsMap[info.Key]; found {
|
||||||
|
msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
|
||||||
|
return nil, errors.New(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsList = append(fieldsList, info)
|
||||||
|
fieldsMap[info.Key] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
sinfo = &structInfo{fieldsMap, fieldsList, inlineMap}
|
||||||
|
|
||||||
|
fieldMapMutex.Lock()
|
||||||
|
structMap[st] = sinfo
|
||||||
|
fieldMapMutex.Unlock()
|
||||||
|
return sinfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isZero(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return len(v.String()) == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Slice:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Map:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,716 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The version directive data.
|
||||||
|
type yaml_version_directive_t struct {
|
||||||
|
major int8 // The major version number.
|
||||||
|
minor int8 // The minor version number.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tag directive data.
|
||||||
|
type yaml_tag_directive_t struct {
|
||||||
|
handle []byte // The tag handle.
|
||||||
|
prefix []byte // The tag prefix.
|
||||||
|
}
|
||||||
|
|
||||||
|
type yaml_encoding_t int
|
||||||
|
|
||||||
|
// The stream encoding.
|
||||||
|
const (
|
||||||
|
// Let the parser choose the encoding.
|
||||||
|
yaml_ANY_ENCODING yaml_encoding_t = iota
|
||||||
|
|
||||||
|
yaml_UTF8_ENCODING // The default UTF-8 encoding.
|
||||||
|
yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM.
|
||||||
|
yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM.
|
||||||
|
)
|
||||||
|
|
||||||
|
type yaml_break_t int
|
||||||
|
|
||||||
|
// Line break types.
|
||||||
|
const (
|
||||||
|
// Let the parser choose the break type.
|
||||||
|
yaml_ANY_BREAK yaml_break_t = iota
|
||||||
|
|
||||||
|
yaml_CR_BREAK // Use CR for line breaks (Mac style).
|
||||||
|
yaml_LN_BREAK // Use LN for line breaks (Unix style).
|
||||||
|
yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style).
|
||||||
|
)
|
||||||
|
|
||||||
|
type yaml_error_type_t int
|
||||||
|
|
||||||
|
// Many bad things could happen with the parser and emitter.
|
||||||
|
const (
|
||||||
|
// No error is produced.
|
||||||
|
yaml_NO_ERROR yaml_error_type_t = iota
|
||||||
|
|
||||||
|
yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory.
|
||||||
|
yaml_READER_ERROR // Cannot read or decode the input stream.
|
||||||
|
yaml_SCANNER_ERROR // Cannot scan the input stream.
|
||||||
|
yaml_PARSER_ERROR // Cannot parse the input stream.
|
||||||
|
yaml_COMPOSER_ERROR // Cannot compose a YAML document.
|
||||||
|
yaml_WRITER_ERROR // Cannot write to the output stream.
|
||||||
|
yaml_EMITTER_ERROR // Cannot emit a YAML stream.
|
||||||
|
)
|
||||||
|
|
||||||
|
// The pointer position.
|
||||||
|
type yaml_mark_t struct {
|
||||||
|
index int // The position index.
|
||||||
|
line int // The position line.
|
||||||
|
column int // The position column.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node Styles
|
||||||
|
|
||||||
|
type yaml_style_t int8
|
||||||
|
|
||||||
|
type yaml_scalar_style_t yaml_style_t
|
||||||
|
|
||||||
|
// Scalar styles.
|
||||||
|
const (
|
||||||
|
// Let the emitter choose the style.
|
||||||
|
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota
|
||||||
|
|
||||||
|
yaml_PLAIN_SCALAR_STYLE // The plain scalar style.
|
||||||
|
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
|
||||||
|
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
|
||||||
|
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
|
||||||
|
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
|
||||||
|
)
|
||||||
|
|
||||||
|
type yaml_sequence_style_t yaml_style_t
|
||||||
|
|
||||||
|
// Sequence styles.
|
||||||
|
const (
|
||||||
|
// Let the emitter choose the style.
|
||||||
|
yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota
|
||||||
|
|
||||||
|
yaml_BLOCK_SEQUENCE_STYLE // The block sequence style.
|
||||||
|
yaml_FLOW_SEQUENCE_STYLE // The flow sequence style.
|
||||||
|
)
|
||||||
|
|
||||||
|
type yaml_mapping_style_t yaml_style_t
|
||||||
|
|
||||||
|
// Mapping styles.
|
||||||
|
const (
|
||||||
|
// Let the emitter choose the style.
|
||||||
|
yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota
|
||||||
|
|
||||||
|
yaml_BLOCK_MAPPING_STYLE // The block mapping style.
|
||||||
|
yaml_FLOW_MAPPING_STYLE // The flow mapping style.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tokens
|
||||||
|
|
||||||
|
type yaml_token_type_t int
|
||||||
|
|
||||||
|
// Token types.
|
||||||
|
const (
|
||||||
|
// An empty token.
|
||||||
|
yaml_NO_TOKEN yaml_token_type_t = iota
|
||||||
|
|
||||||
|
yaml_STREAM_START_TOKEN // A STREAM-START token.
|
||||||
|
yaml_STREAM_END_TOKEN // A STREAM-END token.
|
||||||
|
|
||||||
|
yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token.
|
||||||
|
yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token.
|
||||||
|
yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token.
|
||||||
|
yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token.
|
||||||
|
|
||||||
|
yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token.
|
||||||
|
yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token.
|
||||||
|
yaml_BLOCK_END_TOKEN // A BLOCK-END token.
|
||||||
|
|
||||||
|
yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token.
|
||||||
|
yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token.
|
||||||
|
yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token.
|
||||||
|
yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token.
|
||||||
|
|
||||||
|
yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token.
|
||||||
|
yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token.
|
||||||
|
yaml_KEY_TOKEN // A KEY token.
|
||||||
|
yaml_VALUE_TOKEN // A VALUE token.
|
||||||
|
|
||||||
|
yaml_ALIAS_TOKEN // An ALIAS token.
|
||||||
|
yaml_ANCHOR_TOKEN // An ANCHOR token.
|
||||||
|
yaml_TAG_TOKEN // A TAG token.
|
||||||
|
yaml_SCALAR_TOKEN // A SCALAR token.
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tt yaml_token_type_t) String() string {
|
||||||
|
switch tt {
|
||||||
|
case yaml_NO_TOKEN:
|
||||||
|
return "yaml_NO_TOKEN"
|
||||||
|
case yaml_STREAM_START_TOKEN:
|
||||||
|
return "yaml_STREAM_START_TOKEN"
|
||||||
|
case yaml_STREAM_END_TOKEN:
|
||||||
|
return "yaml_STREAM_END_TOKEN"
|
||||||
|
case yaml_VERSION_DIRECTIVE_TOKEN:
|
||||||
|
return "yaml_VERSION_DIRECTIVE_TOKEN"
|
||||||
|
case yaml_TAG_DIRECTIVE_TOKEN:
|
||||||
|
return "yaml_TAG_DIRECTIVE_TOKEN"
|
||||||
|
case yaml_DOCUMENT_START_TOKEN:
|
||||||
|
return "yaml_DOCUMENT_START_TOKEN"
|
||||||
|
case yaml_DOCUMENT_END_TOKEN:
|
||||||
|
return "yaml_DOCUMENT_END_TOKEN"
|
||||||
|
case yaml_BLOCK_SEQUENCE_START_TOKEN:
|
||||||
|
return "yaml_BLOCK_SEQUENCE_START_TOKEN"
|
||||||
|
case yaml_BLOCK_MAPPING_START_TOKEN:
|
||||||
|
return "yaml_BLOCK_MAPPING_START_TOKEN"
|
||||||
|
case yaml_BLOCK_END_TOKEN:
|
||||||
|
return "yaml_BLOCK_END_TOKEN"
|
||||||
|
case yaml_FLOW_SEQUENCE_START_TOKEN:
|
||||||
|
return "yaml_FLOW_SEQUENCE_START_TOKEN"
|
||||||
|
case yaml_FLOW_SEQUENCE_END_TOKEN:
|
||||||
|
return "yaml_FLOW_SEQUENCE_END_TOKEN"
|
||||||
|
case yaml_FLOW_MAPPING_START_TOKEN:
|
||||||
|
return "yaml_FLOW_MAPPING_START_TOKEN"
|
||||||
|
case yaml_FLOW_MAPPING_END_TOKEN:
|
||||||
|
return "yaml_FLOW_MAPPING_END_TOKEN"
|
||||||
|
case yaml_BLOCK_ENTRY_TOKEN:
|
||||||
|
return "yaml_BLOCK_ENTRY_TOKEN"
|
||||||
|
case yaml_FLOW_ENTRY_TOKEN:
|
||||||
|
return "yaml_FLOW_ENTRY_TOKEN"
|
||||||
|
case yaml_KEY_TOKEN:
|
||||||
|
return "yaml_KEY_TOKEN"
|
||||||
|
case yaml_VALUE_TOKEN:
|
||||||
|
return "yaml_VALUE_TOKEN"
|
||||||
|
case yaml_ALIAS_TOKEN:
|
||||||
|
return "yaml_ALIAS_TOKEN"
|
||||||
|
case yaml_ANCHOR_TOKEN:
|
||||||
|
return "yaml_ANCHOR_TOKEN"
|
||||||
|
case yaml_TAG_TOKEN:
|
||||||
|
return "yaml_TAG_TOKEN"
|
||||||
|
case yaml_SCALAR_TOKEN:
|
||||||
|
return "yaml_SCALAR_TOKEN"
|
||||||
|
}
|
||||||
|
return "<unknown token>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The token structure.
|
||||||
|
type yaml_token_t struct {
|
||||||
|
// The token type.
|
||||||
|
typ yaml_token_type_t
|
||||||
|
|
||||||
|
// The start/end of the token.
|
||||||
|
start_mark, end_mark yaml_mark_t
|
||||||
|
|
||||||
|
// The stream encoding (for yaml_STREAM_START_TOKEN).
|
||||||
|
encoding yaml_encoding_t
|
||||||
|
|
||||||
|
// The alias/anchor/scalar value or tag/tag directive handle
|
||||||
|
// (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN).
|
||||||
|
value []byte
|
||||||
|
|
||||||
|
// The tag suffix (for yaml_TAG_TOKEN).
|
||||||
|
suffix []byte
|
||||||
|
|
||||||
|
// The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN).
|
||||||
|
prefix []byte
|
||||||
|
|
||||||
|
// The scalar style (for yaml_SCALAR_TOKEN).
|
||||||
|
style yaml_scalar_style_t
|
||||||
|
|
||||||
|
// The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN).
|
||||||
|
major, minor int8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events
|
||||||
|
|
||||||
|
type yaml_event_type_t int8
|
||||||
|
|
||||||
|
// Event types.
|
||||||
|
const (
|
||||||
|
// An empty event.
|
||||||
|
yaml_NO_EVENT yaml_event_type_t = iota
|
||||||
|
|
||||||
|
yaml_STREAM_START_EVENT // A STREAM-START event.
|
||||||
|
yaml_STREAM_END_EVENT // A STREAM-END event.
|
||||||
|
yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event.
|
||||||
|
yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event.
|
||||||
|
yaml_ALIAS_EVENT // An ALIAS event.
|
||||||
|
yaml_SCALAR_EVENT // A SCALAR event.
|
||||||
|
yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event.
|
||||||
|
yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event.
|
||||||
|
yaml_MAPPING_START_EVENT // A MAPPING-START event.
|
||||||
|
yaml_MAPPING_END_EVENT // A MAPPING-END event.
|
||||||
|
)
|
||||||
|
|
||||||
|
// The event structure.
|
||||||
|
type yaml_event_t struct {
|
||||||
|
|
||||||
|
// The event type.
|
||||||
|
typ yaml_event_type_t
|
||||||
|
|
||||||
|
// The start and end of the event.
|
||||||
|
start_mark, end_mark yaml_mark_t
|
||||||
|
|
||||||
|
// The document encoding (for yaml_STREAM_START_EVENT).
|
||||||
|
encoding yaml_encoding_t
|
||||||
|
|
||||||
|
// The version directive (for yaml_DOCUMENT_START_EVENT).
|
||||||
|
version_directive *yaml_version_directive_t
|
||||||
|
|
||||||
|
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
|
||||||
|
tag_directives []yaml_tag_directive_t
|
||||||
|
|
||||||
|
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
|
||||||
|
anchor []byte
|
||||||
|
|
||||||
|
// The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||||
|
tag []byte
|
||||||
|
|
||||||
|
// The scalar value (for yaml_SCALAR_EVENT).
|
||||||
|
value []byte
|
||||||
|
|
||||||
|
// Is the document start/end indicator implicit, or the tag optional?
|
||||||
|
// (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT).
|
||||||
|
implicit bool
|
||||||
|
|
||||||
|
// Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT).
|
||||||
|
quoted_implicit bool
|
||||||
|
|
||||||
|
// The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||||
|
style yaml_style_t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) }
|
||||||
|
func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) }
|
||||||
|
func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) }
|
||||||
|
|
||||||
|
// Nodes
|
||||||
|
|
||||||
|
const (
|
||||||
|
yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null.
|
||||||
|
yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false.
|
||||||
|
yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values.
|
||||||
|
yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values.
|
||||||
|
yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values.
|
||||||
|
yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values.
|
||||||
|
|
||||||
|
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
|
||||||
|
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
|
||||||
|
|
||||||
|
// Not in original libyaml.
|
||||||
|
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
|
||||||
|
yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
|
||||||
|
|
||||||
|
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
|
||||||
|
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
|
||||||
|
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
|
||||||
|
)
|
||||||
|
|
||||||
|
type yaml_node_type_t int
|
||||||
|
|
||||||
|
// Node types.
|
||||||
|
const (
|
||||||
|
// An empty node.
|
||||||
|
yaml_NO_NODE yaml_node_type_t = iota
|
||||||
|
|
||||||
|
yaml_SCALAR_NODE // A scalar node.
|
||||||
|
yaml_SEQUENCE_NODE // A sequence node.
|
||||||
|
yaml_MAPPING_NODE // A mapping node.
|
||||||
|
)
|
||||||
|
|
||||||
|
// An element of a sequence node.
|
||||||
|
type yaml_node_item_t int
|
||||||
|
|
||||||
|
// An element of a mapping node.
|
||||||
|
type yaml_node_pair_t struct {
|
||||||
|
key int // The key of the element.
|
||||||
|
value int // The value of the element.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The node structure.
|
||||||
|
type yaml_node_t struct {
|
||||||
|
typ yaml_node_type_t // The node type.
|
||||||
|
tag []byte // The node tag.
|
||||||
|
|
||||||
|
// The node data.
|
||||||
|
|
||||||
|
// The scalar parameters (for yaml_SCALAR_NODE).
|
||||||
|
scalar struct {
|
||||||
|
value []byte // The scalar value.
|
||||||
|
length int // The length of the scalar value.
|
||||||
|
style yaml_scalar_style_t // The scalar style.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sequence parameters (for YAML_SEQUENCE_NODE).
|
||||||
|
sequence struct {
|
||||||
|
items_data []yaml_node_item_t // The stack of sequence items.
|
||||||
|
style yaml_sequence_style_t // The sequence style.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mapping parameters (for yaml_MAPPING_NODE).
|
||||||
|
mapping struct {
|
||||||
|
pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value).
|
||||||
|
pairs_start *yaml_node_pair_t // The beginning of the stack.
|
||||||
|
pairs_end *yaml_node_pair_t // The end of the stack.
|
||||||
|
pairs_top *yaml_node_pair_t // The top of the stack.
|
||||||
|
style yaml_mapping_style_t // The mapping style.
|
||||||
|
}
|
||||||
|
|
||||||
|
start_mark yaml_mark_t // The beginning of the node.
|
||||||
|
end_mark yaml_mark_t // The end of the node.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// The document structure.
|
||||||
|
type yaml_document_t struct {
|
||||||
|
|
||||||
|
// The document nodes.
|
||||||
|
nodes []yaml_node_t
|
||||||
|
|
||||||
|
// The version directive.
|
||||||
|
version_directive *yaml_version_directive_t
|
||||||
|
|
||||||
|
// The list of tag directives.
|
||||||
|
tag_directives_data []yaml_tag_directive_t
|
||||||
|
tag_directives_start int // The beginning of the tag directives list.
|
||||||
|
tag_directives_end int // The end of the tag directives list.
|
||||||
|
|
||||||
|
start_implicit int // Is the document start indicator implicit?
|
||||||
|
end_implicit int // Is the document end indicator implicit?
|
||||||
|
|
||||||
|
// The start/end of the document.
|
||||||
|
start_mark, end_mark yaml_mark_t
|
||||||
|
}
|
||||||
|
|
||||||
|
// The prototype of a read handler.
|
||||||
|
//
|
||||||
|
// The read handler is called when the parser needs to read more bytes from the
|
||||||
|
// source. The handler should write not more than size bytes to the buffer.
|
||||||
|
// The number of written bytes should be set to the size_read variable.
|
||||||
|
//
|
||||||
|
// [in,out] data A pointer to an application data specified by
|
||||||
|
// yaml_parser_set_input().
|
||||||
|
// [out] buffer The buffer to write the data from the source.
|
||||||
|
// [in] size The size of the buffer.
|
||||||
|
// [out] size_read The actual number of bytes read from the source.
|
||||||
|
//
|
||||||
|
// On success, the handler should return 1. If the handler failed,
|
||||||
|
// the returned value should be 0. On EOF, the handler should set the
|
||||||
|
// size_read to 0 and return 1.
|
||||||
|
type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error)
|
||||||
|
|
||||||
|
// This structure holds information about a potential simple key.
|
||||||
|
type yaml_simple_key_t struct {
|
||||||
|
possible bool // Is a simple key possible?
|
||||||
|
required bool // Is a simple key required?
|
||||||
|
token_number int // The number of the token.
|
||||||
|
mark yaml_mark_t // The position mark.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The states of the parser.
|
||||||
|
type yaml_parser_state_t int
|
||||||
|
|
||||||
|
const (
|
||||||
|
yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota
|
||||||
|
|
||||||
|
yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document.
|
||||||
|
yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START.
|
||||||
|
yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||||
|
yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||||
|
yaml_PARSE_BLOCK_NODE_STATE // Expect a block node.
|
||||||
|
yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence.
|
||||||
|
yaml_PARSE_FLOW_NODE_STATE // Expect a flow node.
|
||||||
|
yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence.
|
||||||
|
yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence.
|
||||||
|
yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence.
|
||||||
|
yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||||
|
yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key.
|
||||||
|
yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value.
|
||||||
|
yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence.
|
||||||
|
yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence.
|
||||||
|
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping.
|
||||||
|
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping.
|
||||||
|
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry.
|
||||||
|
yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||||
|
yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||||
|
yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||||
|
yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping.
|
||||||
|
yaml_PARSE_END_STATE // Expect nothing.
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ps yaml_parser_state_t) String() string {
|
||||||
|
switch ps {
|
||||||
|
case yaml_PARSE_STREAM_START_STATE:
|
||||||
|
return "yaml_PARSE_STREAM_START_STATE"
|
||||||
|
case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE:
|
||||||
|
return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE"
|
||||||
|
case yaml_PARSE_DOCUMENT_START_STATE:
|
||||||
|
return "yaml_PARSE_DOCUMENT_START_STATE"
|
||||||
|
case yaml_PARSE_DOCUMENT_CONTENT_STATE:
|
||||||
|
return "yaml_PARSE_DOCUMENT_CONTENT_STATE"
|
||||||
|
case yaml_PARSE_DOCUMENT_END_STATE:
|
||||||
|
return "yaml_PARSE_DOCUMENT_END_STATE"
|
||||||
|
case yaml_PARSE_BLOCK_NODE_STATE:
|
||||||
|
return "yaml_PARSE_BLOCK_NODE_STATE"
|
||||||
|
case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE:
|
||||||
|
return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE"
|
||||||
|
case yaml_PARSE_FLOW_NODE_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_NODE_STATE"
|
||||||
|
case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE:
|
||||||
|
return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE"
|
||||||
|
case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE:
|
||||||
|
return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE"
|
||||||
|
case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE:
|
||||||
|
return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE"
|
||||||
|
case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE:
|
||||||
|
return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE"
|
||||||
|
case yaml_PARSE_BLOCK_MAPPING_KEY_STATE:
|
||||||
|
return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE"
|
||||||
|
case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE:
|
||||||
|
return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE"
|
||||||
|
case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE"
|
||||||
|
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE"
|
||||||
|
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE"
|
||||||
|
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE"
|
||||||
|
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE"
|
||||||
|
case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE"
|
||||||
|
case yaml_PARSE_FLOW_MAPPING_KEY_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_MAPPING_KEY_STATE"
|
||||||
|
case yaml_PARSE_FLOW_MAPPING_VALUE_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE"
|
||||||
|
case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE:
|
||||||
|
return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE"
|
||||||
|
case yaml_PARSE_END_STATE:
|
||||||
|
return "yaml_PARSE_END_STATE"
|
||||||
|
}
|
||||||
|
return "<unknown parser state>"
|
||||||
|
}
|
||||||
|
|
||||||
|
// This structure holds aliases data.
|
||||||
|
type yaml_alias_data_t struct {
|
||||||
|
anchor []byte // The anchor.
|
||||||
|
index int // The node id.
|
||||||
|
mark yaml_mark_t // The anchor mark.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The parser structure.
|
||||||
|
//
|
||||||
|
// All members are internal. Manage the structure using the
|
||||||
|
// yaml_parser_ family of functions.
|
||||||
|
type yaml_parser_t struct {
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
|
||||||
|
error yaml_error_type_t // Error type.
|
||||||
|
|
||||||
|
problem string // Error description.
|
||||||
|
|
||||||
|
// The byte about which the problem occured.
|
||||||
|
problem_offset int
|
||||||
|
problem_value int
|
||||||
|
problem_mark yaml_mark_t
|
||||||
|
|
||||||
|
// The error context.
|
||||||
|
context string
|
||||||
|
context_mark yaml_mark_t
|
||||||
|
|
||||||
|
// Reader stuff
|
||||||
|
|
||||||
|
read_handler yaml_read_handler_t // Read handler.
|
||||||
|
|
||||||
|
input_file io.Reader // File input data.
|
||||||
|
input []byte // String input data.
|
||||||
|
input_pos int
|
||||||
|
|
||||||
|
eof bool // EOF flag
|
||||||
|
|
||||||
|
buffer []byte // The working buffer.
|
||||||
|
buffer_pos int // The current position of the buffer.
|
||||||
|
|
||||||
|
unread int // The number of unread characters in the buffer.
|
||||||
|
|
||||||
|
raw_buffer []byte // The raw buffer.
|
||||||
|
raw_buffer_pos int // The current position of the buffer.
|
||||||
|
|
||||||
|
encoding yaml_encoding_t // The input encoding.
|
||||||
|
|
||||||
|
offset int // The offset of the current position (in bytes).
|
||||||
|
mark yaml_mark_t // The mark of the current position.
|
||||||
|
|
||||||
|
// Scanner stuff
|
||||||
|
|
||||||
|
stream_start_produced bool // Have we started to scan the input stream?
|
||||||
|
stream_end_produced bool // Have we reached the end of the input stream?
|
||||||
|
|
||||||
|
flow_level int // The number of unclosed '[' and '{' indicators.
|
||||||
|
|
||||||
|
tokens []yaml_token_t // The tokens queue.
|
||||||
|
tokens_head int // The head of the tokens queue.
|
||||||
|
tokens_parsed int // The number of tokens fetched from the queue.
|
||||||
|
token_available bool // Does the tokens queue contain a token ready for dequeueing.
|
||||||
|
|
||||||
|
indent int // The current indentation level.
|
||||||
|
indents []int // The indentation levels stack.
|
||||||
|
|
||||||
|
simple_key_allowed bool // May a simple key occur at the current position?
|
||||||
|
simple_keys []yaml_simple_key_t // The stack of simple keys.
|
||||||
|
|
||||||
|
// Parser stuff
|
||||||
|
|
||||||
|
state yaml_parser_state_t // The current parser state.
|
||||||
|
states []yaml_parser_state_t // The parser states stack.
|
||||||
|
marks []yaml_mark_t // The stack of marks.
|
||||||
|
tag_directives []yaml_tag_directive_t // The list of TAG directives.
|
||||||
|
|
||||||
|
// Dumper stuff
|
||||||
|
|
||||||
|
aliases []yaml_alias_data_t // The alias data.
|
||||||
|
|
||||||
|
document *yaml_document_t // The currently parsed document.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitter Definitions
|
||||||
|
|
||||||
|
// The prototype of a write handler.
|
||||||
|
//
|
||||||
|
// The write handler is called when the emitter needs to flush the accumulated
|
||||||
|
// characters to the output. The handler should write @a size bytes of the
|
||||||
|
// @a buffer to the output.
|
||||||
|
//
|
||||||
|
// @param[in,out] data A pointer to an application data specified by
|
||||||
|
// yaml_emitter_set_output().
|
||||||
|
// @param[in] buffer The buffer with bytes to be written.
|
||||||
|
// @param[in] size The size of the buffer.
|
||||||
|
//
|
||||||
|
// @returns On success, the handler should return @c 1. If the handler failed,
|
||||||
|
// the returned value should be @c 0.
|
||||||
|
//
|
||||||
|
type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error
|
||||||
|
|
||||||
|
type yaml_emitter_state_t int
|
||||||
|
|
||||||
|
// The emitter states.
|
||||||
|
const (
|
||||||
|
// Expect STREAM-START.
|
||||||
|
yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota
|
||||||
|
|
||||||
|
yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END.
|
||||||
|
yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END.
|
||||||
|
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||||
|
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||||
|
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
|
||||||
|
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
|
||||||
|
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||||
|
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||||
|
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
|
||||||
|
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||||
|
yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence.
|
||||||
|
yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence.
|
||||||
|
yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||||
|
yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping.
|
||||||
|
yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping.
|
||||||
|
yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping.
|
||||||
|
yaml_EMIT_END_STATE // Expect nothing.
|
||||||
|
)
|
||||||
|
|
||||||
|
// The emitter structure.
|
||||||
|
//
|
||||||
|
// All members are internal. Manage the structure using the @c yaml_emitter_
|
||||||
|
// family of functions.
|
||||||
|
type yaml_emitter_t struct {
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
|
||||||
|
error yaml_error_type_t // Error type.
|
||||||
|
problem string // Error description.
|
||||||
|
|
||||||
|
// Writer stuff
|
||||||
|
|
||||||
|
write_handler yaml_write_handler_t // Write handler.
|
||||||
|
|
||||||
|
output_buffer *[]byte // String output data.
|
||||||
|
output_file io.Writer // File output data.
|
||||||
|
|
||||||
|
buffer []byte // The working buffer.
|
||||||
|
buffer_pos int // The current position of the buffer.
|
||||||
|
|
||||||
|
raw_buffer []byte // The raw buffer.
|
||||||
|
raw_buffer_pos int // The current position of the buffer.
|
||||||
|
|
||||||
|
encoding yaml_encoding_t // The stream encoding.
|
||||||
|
|
||||||
|
// Emitter stuff
|
||||||
|
|
||||||
|
canonical bool // If the output is in the canonical style?
|
||||||
|
best_indent int // The number of indentation spaces.
|
||||||
|
best_width int // The preferred width of the output lines.
|
||||||
|
unicode bool // Allow unescaped non-ASCII characters?
|
||||||
|
line_break yaml_break_t // The preferred line break.
|
||||||
|
|
||||||
|
state yaml_emitter_state_t // The current emitter state.
|
||||||
|
states []yaml_emitter_state_t // The stack of states.
|
||||||
|
|
||||||
|
events []yaml_event_t // The event queue.
|
||||||
|
events_head int // The head of the event queue.
|
||||||
|
|
||||||
|
indents []int // The stack of indentation levels.
|
||||||
|
|
||||||
|
tag_directives []yaml_tag_directive_t // The list of tag directives.
|
||||||
|
|
||||||
|
indent int // The current indentation level.
|
||||||
|
|
||||||
|
flow_level int // The current flow level.
|
||||||
|
|
||||||
|
root_context bool // Is it the document root context?
|
||||||
|
sequence_context bool // Is it a sequence context?
|
||||||
|
mapping_context bool // Is it a mapping context?
|
||||||
|
simple_key_context bool // Is it a simple mapping key context?
|
||||||
|
|
||||||
|
line int // The current line.
|
||||||
|
column int // The current column.
|
||||||
|
whitespace bool // If the last character was a whitespace?
|
||||||
|
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
|
||||||
|
open_ended bool // If an explicit document end is required?
|
||||||
|
|
||||||
|
// Anchor analysis.
|
||||||
|
anchor_data struct {
|
||||||
|
anchor []byte // The anchor value.
|
||||||
|
alias bool // Is it an alias?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag analysis.
|
||||||
|
tag_data struct {
|
||||||
|
handle []byte // The tag handle.
|
||||||
|
suffix []byte // The tag suffix.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalar analysis.
|
||||||
|
scalar_data struct {
|
||||||
|
value []byte // The scalar value.
|
||||||
|
multiline bool // Does the scalar contain line breaks?
|
||||||
|
flow_plain_allowed bool // Can the scalar be expessed in the flow plain style?
|
||||||
|
block_plain_allowed bool // Can the scalar be expressed in the block plain style?
|
||||||
|
single_quoted_allowed bool // Can the scalar be expressed in the single quoted style?
|
||||||
|
block_allowed bool // Can the scalar be expressed in the literal or folded styles?
|
||||||
|
style yaml_scalar_style_t // The output style.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dumper stuff
|
||||||
|
|
||||||
|
opened bool // If the stream was already opened?
|
||||||
|
closed bool // If the stream was already closed?
|
||||||
|
|
||||||
|
// The information associated with the document nodes.
|
||||||
|
anchors *struct {
|
||||||
|
references int // The number of references.
|
||||||
|
anchor int // The anchor id.
|
||||||
|
serialized bool // If the node has been emitted?
|
||||||
|
}
|
||||||
|
|
||||||
|
last_anchor_id int // The last assigned anchor id.
|
||||||
|
|
||||||
|
document *yaml_document_t // The currently emitted document.
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The size of the input raw buffer.
|
||||||
|
input_raw_buffer_size = 512
|
||||||
|
|
||||||
|
// The size of the input buffer.
|
||||||
|
// It should be possible to decode the whole raw buffer.
|
||||||
|
input_buffer_size = input_raw_buffer_size * 3
|
||||||
|
|
||||||
|
// The size of the output buffer.
|
||||||
|
output_buffer_size = 128
|
||||||
|
|
||||||
|
// The size of the output raw buffer.
|
||||||
|
// It should be possible to encode the whole output buffer.
|
||||||
|
output_raw_buffer_size = (output_buffer_size*2 + 2)
|
||||||
|
|
||||||
|
// The size of other stacks and queues.
|
||||||
|
initial_stack_size = 16
|
||||||
|
initial_queue_size = 16
|
||||||
|
initial_string_size = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if the character at the specified position is an alphabetical
|
||||||
|
// character, a digit, '_', or '-'.
|
||||||
|
func is_alpha(b []byte, i int) bool {
|
||||||
|
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the specified position is a digit.
|
||||||
|
func is_digit(b []byte, i int) bool {
|
||||||
|
return b[i] >= '0' && b[i] <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the value of a digit.
|
||||||
|
func as_digit(b []byte, i int) int {
|
||||||
|
return int(b[i]) - '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the specified position is a hex-digit.
|
||||||
|
func is_hex(b []byte, i int) bool {
|
||||||
|
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the value of a hex-digit.
|
||||||
|
func as_hex(b []byte, i int) int {
|
||||||
|
bi := b[i]
|
||||||
|
if bi >= 'A' && bi <= 'F' {
|
||||||
|
return int(bi) - 'A' + 10
|
||||||
|
}
|
||||||
|
if bi >= 'a' && bi <= 'f' {
|
||||||
|
return int(bi) - 'a' + 10
|
||||||
|
}
|
||||||
|
return int(bi) - '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character is ASCII.
|
||||||
|
func is_ascii(b []byte, i int) bool {
|
||||||
|
return b[i] <= 0x7F
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the start of the buffer can be printed unescaped.
|
||||||
|
func is_printable(b []byte, i int) bool {
|
||||||
|
return ((b[i] == 0x0A) || // . == #x0A
|
||||||
|
(b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E
|
||||||
|
(b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF
|
||||||
|
(b[i] > 0xC2 && b[i] < 0xED) ||
|
||||||
|
(b[i] == 0xED && b[i+1] < 0xA0) ||
|
||||||
|
(b[i] == 0xEE) ||
|
||||||
|
(b[i] == 0xEF && // #xE000 <= . <= #xFFFD
|
||||||
|
!(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF
|
||||||
|
!(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the specified position is NUL.
|
||||||
|
func is_z(b []byte, i int) bool {
|
||||||
|
return b[i] == 0x00
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the beginning of the buffer is a BOM.
|
||||||
|
func is_bom(b []byte, i int) bool {
|
||||||
|
return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the specified position is space.
|
||||||
|
func is_space(b []byte, i int) bool {
|
||||||
|
return b[i] == ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the specified position is tab.
|
||||||
|
func is_tab(b []byte, i int) bool {
|
||||||
|
return b[i] == '\t'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the specified position is blank (space or tab).
|
||||||
|
func is_blank(b []byte, i int) bool {
|
||||||
|
//return is_space(b, i) || is_tab(b, i)
|
||||||
|
return b[i] == ' ' || b[i] == '\t'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character at the specified position is a line break.
|
||||||
|
func is_break(b []byte, i int) bool {
|
||||||
|
return (b[i] == '\r' || // CR (#xD)
|
||||||
|
b[i] == '\n' || // LF (#xA)
|
||||||
|
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029)
|
||||||
|
}
|
||||||
|
|
||||||
|
func is_crlf(b []byte, i int) bool {
|
||||||
|
return b[i] == '\r' && b[i+1] == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character is a line break or NUL.
|
||||||
|
func is_breakz(b []byte, i int) bool {
|
||||||
|
//return is_break(b, i) || is_z(b, i)
|
||||||
|
return ( // is_break:
|
||||||
|
b[i] == '\r' || // CR (#xD)
|
||||||
|
b[i] == '\n' || // LF (#xA)
|
||||||
|
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||||
|
// is_z:
|
||||||
|
b[i] == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character is a line break, space, or NUL.
|
||||||
|
func is_spacez(b []byte, i int) bool {
|
||||||
|
//return is_space(b, i) || is_breakz(b, i)
|
||||||
|
return ( // is_space:
|
||||||
|
b[i] == ' ' ||
|
||||||
|
// is_breakz:
|
||||||
|
b[i] == '\r' || // CR (#xD)
|
||||||
|
b[i] == '\n' || // LF (#xA)
|
||||||
|
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||||
|
b[i] == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the character is a line break, space, tab, or NUL.
|
||||||
|
func is_blankz(b []byte, i int) bool {
|
||||||
|
//return is_blank(b, i) || is_breakz(b, i)
|
||||||
|
return ( // is_blank:
|
||||||
|
b[i] == ' ' || b[i] == '\t' ||
|
||||||
|
// is_breakz:
|
||||||
|
b[i] == '\r' || // CR (#xD)
|
||||||
|
b[i] == '\n' || // LF (#xA)
|
||||||
|
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||||
|
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||||
|
b[i] == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the width of the character.
|
||||||
|
func width(b byte) int {
|
||||||
|
// Don't replace these by a switch without first
|
||||||
|
// confirming that it is being inlined.
|
||||||
|
if b&0x80 == 0x00 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if b&0xE0 == 0xC0 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if b&0xF0 == 0xE0 {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
if b&0xF8 == 0xF0 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue