Merge pull request #1576 from bradrydzewski/master

0.5 yaml parser and runner. [ci skip] to avoid new image
This commit is contained in:
Brad Rydzewski 2016-04-16 13:02:30 -07:00
commit 078597261f
94 changed files with 17688 additions and 0 deletions

View file

@ -0,0 +1,29 @@
package builtin
import (
"fmt"
"github.com/drone/drone/engine/compiler/parse"
)
type aliasOp struct {
visitor
index map[string]string
prefix string
suffix int
}
func NewAliasOp(prefix string) Visitor {
return &aliasOp{
index: map[string]string{},
prefix: prefix,
}
}
func (v *aliasOp) VisitContainer(node *parse.ContainerNode) error {
v.suffix++
node.Container.Alias = node.Container.Name
node.Container.Name = fmt.Sprintf("%s_%d", v.prefix, v.suffix)
return nil
}

View file

@ -0,0 +1,90 @@
package builtin
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/drone/drone/engine/compiler/parse"
json "github.com/ghodss/yaml"
"gopkg.in/yaml.v2"
)
type argsOps struct {
visitor
}
// NewArgsOp returns a transformer that provides the plugin node
// with the custom arguments from the Yaml file.
func NewArgsOp() Visitor {
return &argsOps{}
}
func (v *argsOps) VisitContainer(node *parse.ContainerNode) error {
switch node.NodeType {
case parse.NodePlugin, parse.NodeCache, parse.NodeClone:
break // no-op
default:
return nil
}
if node.Container.Environment == nil {
node.Container.Environment = map[string]string{}
}
return argsToEnv(node.Vargs, node.Container.Environment)
}
// argsToEnv uses reflection to convert a map[string]interface to a list
// of environment variables.
func argsToEnv(from map[string]interface{}, to map[string]string) error {
for k, v := range from {
t := reflect.TypeOf(v)
vv := reflect.ValueOf(v)
k = "PLUGIN_" + strings.ToUpper(k)
switch t.Kind() {
case reflect.Bool:
to[k] = strconv.FormatBool(vv.Bool())
case reflect.String:
to[k] = vv.String()
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
to[k] = fmt.Sprintf("%v", vv.Int())
case reflect.Float32, reflect.Float64:
to[k] = fmt.Sprintf("%v", vv.Float())
// case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
// to[k] = strconv.FormatInt(vv.Int(), 16)
// case reflect.Float32, reflect.Float64:
// to[k] = strconv.FormatFloat(vv.Float(), 'E', -1, 64)
case reflect.Map:
yml, _ := yaml.Marshal(vv.Interface())
out, _ := json.YAMLToJSON(yml)
to[k] = string(out)
case reflect.Slice:
out, _ := yaml.Marshal(vv.Interface())
in := []string{}
err := yaml.Unmarshal(out, &in)
if err == nil {
to[k] = strings.Join(in, ",")
} else {
out, err = json.YAMLToJSON(out)
if err != nil {
println(err.Error())
}
to[k] = string(out)
}
}
}
return nil
}

View file

@ -0,0 +1,46 @@
package builtin
import (
"testing"
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
"github.com/franela/goblin"
)
func Test_args(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("plugins arguments", func() {
g.It("should ignore non-plugin containers", func() {
root := parse.NewRootNode()
c := root.NewShellNode()
c.Container = runner.Container{}
c.Vargs = map[string]interface{}{
"depth": 50,
}
ops := NewArgsOp()
ops.VisitContainer(c)
g.Assert(c.Container.Environment["PLUGIN_DEPTH"]).Equal("")
})
g.It("should include args as environment variable", func() {
root := parse.NewRootNode()
c := root.NewPluginNode()
c.Container = runner.Container{}
c.Vargs = map[string]interface{}{
"depth": 50,
}
ops := NewArgsOp()
ops.VisitContainer(c)
g.Assert(c.Container.Environment["PLUGIN_DEPTH"]).Equal("50")
})
})
}

View file

@ -0,0 +1,40 @@
package builtin
import (
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
)
// BuildOp is a transform operation that converts the build section of the Yaml
// to a step in the pipeline responsible for building the Docker image.
func BuildOp(node parse.Node) error {
build, ok := node.(*parse.BuildNode)
if !ok {
return nil
}
if build.Context == "" {
return nil
}
root := node.Root()
builder := root.NewContainerNode()
command := []string{
"build",
"--force-rm",
"-f", build.Dockerfile,
"-t", root.Image,
build.Context,
}
builder.Container = runner.Container{
Image: "docker:apline",
Volumes: []string{"/var/run/docker.sock:/var/run/docker.sock"},
Entrypoint: []string{"/usr/local/bin/docker"},
Command: command,
WorkingDir: root.Path,
}
root.Services = append(root.Services, builder)
return nil
}

View file

@ -0,0 +1,61 @@
package builtin
import (
"github.com/drone/drone/engine/runner"
"github.com/drone/drone/engine/compiler/parse"
)
type cacheOp struct {
visitor
enable bool
plugin string
mount string
}
// NewCacheOp returns a transformer that configures the default cache plugin.
func NewCacheOp(plugin, mount string, enable bool) Visitor {
return &cacheOp{
mount: mount,
enable: enable,
plugin: plugin,
}
}
func (v *cacheOp) VisitContainer(node *parse.ContainerNode) error {
if node.Type() != parse.NodeCache {
return nil
}
if len(node.Vargs) == 0 || v.enable == false {
node.Disabled = true
return nil
}
if node.Container.Name == "" {
node.Container.Name = "cache"
}
if node.Container.Image == "" {
node.Container.Image = v.plugin
}
// discard any other cache properties except the image name.
// everything else is discard for security reasons.
node.Container = runner.Container{
Name: node.Container.Name,
Alias: node.Container.Alias,
Image: node.Container.Image,
Volumes: []string{
v.mount + ":/cache",
},
}
// this is a hack until I can come up with a better solution.
// this copies the clone name, and appends at the end of the
// build. When it is executed a second time the build should
// have a completed status, so it knows to cache instead
// of restore.
cache := node.Root().NewCacheNode()
cache.Vargs = node.Vargs
cache.Container = node.Container
node.Root().Script = append(node.Root().Script, cache)
return nil
}

View file

@ -0,0 +1,37 @@
package builtin
// import (
// "testing"
// "github.com/libcd/libcd"
// "github.com/libcd/libyaml/parse"
// "github.com/franela/goblin"
// )
// func Test_cache(t *testing.T) {
// root := parse.NewRootNode()
// g := goblin.Goblin(t)
// g.Describe("cache", func() {
// g.It("should use default when nil", func() {
// op := NewCacheOp("plugins/cache:latest", "/tmp/cache")
// op.VisitRoot(root)
// g.Assert(root.Cache.(*parse.ContainerNode).Container.Image).Equal("plugins/cache:latest")
// g.Assert(root.Cache.(*parse.ContainerNode).Container.Volumes[0]).Equal("/tmp/cache:/cache")
// })
// g.It("should use user-defined cache plugin", func() {
// op := NewCacheOp("plugins/cache:latest", "/tmp/cache")
// cache := root.NewCacheNode()
// cache.Container = libcd.Container{}
// cache.Container.Image = "custom/cacher:latest"
// root.Cache = cache
// op.VisitRoot(root)
// g.Assert(cache.Container.Image).Equal("custom/cacher:latest")
// })
// })
// }

View file

@ -0,0 +1,45 @@
package builtin
import (
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
)
type cloneOp struct {
visitor
plugin string
enable bool
}
// NewCloneOp returns a transformer that configures the default clone plugin.
func NewCloneOp(plugin string, enable bool) Visitor {
return &cloneOp{
enable: enable,
plugin: plugin,
}
}
func (v *cloneOp) VisitContainer(node *parse.ContainerNode) error {
if node.Type() != parse.NodeClone {
return nil
}
if v.enable == false {
node.Disabled = true
return nil
}
if node.Container.Name == "" {
node.Container.Name = "clone"
}
if node.Container.Image == "" {
node.Container.Image = v.plugin
}
// discard any other cache properties except the image name.
// everything else is discard for security reasons.
node.Container = runner.Container{
Name: node.Container.Name,
Image: node.Container.Image,
}
return nil
}

View file

@ -0,0 +1,36 @@
package builtin
// import (
// "testing"
// "github.com/libcd/libcd"
// "github.com/libcd/libyaml/parse"
// "github.com/franela/goblin"
// )
// func Test_clone(t *testing.T) {
// root := parse.NewRootNode()
// g := goblin.Goblin(t)
// g.Describe("clone", func() {
// g.It("should use default when nil", func() {
// op := NewCloneOp("plugins/git:latest")
// op.VisitRoot(root)
// g.Assert(root.Clone.(*parse.ContainerNode).Container.Image).Equal("plugins/git:latest")
// })
// g.It("should use user-defined clone plugin", func() {
// op := NewCloneOp("plugins/git:latest")
// clone := root.NewCloneNode()
// clone.Container = libcd.Container{}
// clone.Container.Image = "custom/hg:latest"
// root.Clone = clone
// op.VisitRoot(root)
// g.Assert(clone.Container.Image).Equal("custom/hg:latest")
// })
// })
// }

View file

@ -0,0 +1,57 @@
package builtin
import (
"os"
"strings"
"github.com/drone/drone/engine/compiler/parse"
)
var (
httpProxy = os.Getenv("HTTP_PROXY")
httpsProxy = os.Getenv("HTTPS_PROXY")
noProxy = os.Getenv("NO_PROXY")
)
type envOp struct {
visitor
envs map[string]string
}
// NewEnvOp returns a transformer that sets default environment variables
// for each container, service and plugin.
func NewEnvOp(envs map[string]string) Visitor {
return &envOp{
envs: envs,
}
}
func (v *envOp) VisitContainer(node *parse.ContainerNode) error {
if node.Container.Environment == nil {
node.Container.Environment = map[string]string{}
}
v.defaultEnv(node)
v.defaultEnvProxy(node)
return nil
}
func (v *envOp) defaultEnv(node *parse.ContainerNode) {
for k, v := range v.envs {
node.Container.Environment[k] = v
}
}
func (v *envOp) defaultEnvProxy(node *parse.ContainerNode) {
if httpProxy != "" {
node.Container.Environment["HTTP_PROXY"] = httpProxy
node.Container.Environment["http_proxy"] = strings.ToUpper(httpProxy)
}
if httpsProxy != "" {
node.Container.Environment["HTTPS_PROXY"] = httpsProxy
node.Container.Environment["https_proxy"] = strings.ToUpper(httpsProxy)
}
if noProxy != "" {
node.Container.Environment["NO_PROXY"] = noProxy
node.Container.Environment["no_proxy"] = strings.ToUpper(noProxy)
}
}

View file

@ -0,0 +1,45 @@
package builtin
import (
"testing"
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
"github.com/franela/goblin"
)
func Test_env(t *testing.T) {
root := parse.NewRootNode()
g := goblin.Goblin(t)
g.Describe("environment variables", func() {
g.It("should be copied", func() {
envs := map[string]string{"CI": "drone"}
c := root.NewContainerNode()
c.Container = runner.Container{}
op := NewEnvOp(envs)
op.VisitContainer(c)
g.Assert(c.Container.Environment["CI"]).Equal("drone")
})
g.It("should include http proxy variables", func() {
httpProxy = "foo"
httpsProxy = "bar"
noProxy = "baz"
c := root.NewContainerNode()
c.Container = runner.Container{}
op := NewEnvOp(map[string]string{})
op.VisitContainer(c)
g.Assert(c.Container.Environment["HTTP_PROXY"]).Equal("foo")
g.Assert(c.Container.Environment["HTTPS_PROXY"]).Equal("bar")
g.Assert(c.Container.Environment["NO_PROXY"]).Equal("baz")
})
})
}

View file

@ -0,0 +1,128 @@
package builtin
import (
"path/filepath"
"github.com/drone/drone/engine/compiler/parse"
)
type filterOp struct {
visitor
status string
branch string
event string
environ string
platform string
matrix map[string]string
}
// NewFilterOp returns a transformer that filters (ie removes) steps
// from the process based on conditional logic in the yaml.
func NewFilterOp(status, branch, event, env string, matrix map[string]string) Visitor {
return &filterOp{
status: status,
branch: branch,
event: event,
environ: env,
matrix: matrix,
}
}
func (v *filterOp) VisitContainer(node *parse.ContainerNode) error {
v.visitStatus(node)
v.visitBranch(node)
v.visitEvent(node)
v.visitMatrix(node)
v.visitPlatform(node)
return nil
}
// visitStatus is a helpfer function that converts an on_change status
// filter to either success or failure based on the prior build status.
func (v *filterOp) visitStatus(node *parse.ContainerNode) {
if len(node.Conditions.Status) == 0 {
node.Conditions.Status = []string{"success"}
return
}
for _, status := range node.Conditions.Status {
if status != "change" && status != "changed" && status != "changes" {
continue
}
var want []string
switch v.status {
case "success":
want = append(want, "failure")
case "failure", "error", "killed":
want = append(want, "success")
default:
want = []string{"success", "failure"}
}
node.Conditions.Status = append(node.Conditions.Status, want...)
break
}
}
// visitBranch is a helper function that disables container steps when
// the branch conditions are not satisfied.
func (v *filterOp) visitBranch(node *parse.ContainerNode) {
if len(node.Conditions.Branch) == 0 {
return
}
for _, pattern := range node.Conditions.Branch {
if ok, _ := filepath.Match(pattern, v.branch); ok {
return
}
}
node.Disabled = true
}
// visitEnvironment is a helper function that disables container steps
// when the deployment environment conditions are not satisfied.
func (v *filterOp) visitEnvironment(node *parse.ContainerNode) {
if len(node.Conditions.Environment) == 0 {
return
}
for _, pattern := range node.Conditions.Environment {
if ok, _ := filepath.Match(pattern, v.environ); ok {
return
}
}
node.Disabled = true
}
// visitEvent is a helper function that disables container steps
// when the build event conditions are not satisfied.
func (v *filterOp) visitEvent(node *parse.ContainerNode) {
if len(node.Conditions.Event) == 0 {
return
}
for _, pattern := range node.Conditions.Event {
if ok, _ := filepath.Match(pattern, v.event); ok {
return
}
}
node.Disabled = true
}
func (v *filterOp) visitMatrix(node *parse.ContainerNode) {
for key, val := range node.Conditions.Matrix {
if v.matrix[key] != val {
node.Disabled = true
break
}
}
}
// visitPlatform is a helper function that disables container steps
// when the build event conditions are not satisfied.
func (v *filterOp) visitPlatform(node *parse.ContainerNode) {
if len(node.Conditions.Platform) == 0 {
return
}
for _, pattern := range node.Conditions.Platform {
if ok, _ := filepath.Match(pattern, v.platform); ok {
return
}
}
node.Disabled = true
}

View file

@ -0,0 +1,130 @@
package builtin
// import (
// "testing"
// "github.com/franela/goblin"
// )
// func TestFilter(t *testing.T) {
// g := goblin.Goblin(t)
// g.Describe("Filters", func() {
// g.It("Should match no branch filter", func() {
// c := &Container{}
// FilterBranch("feature/foo")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should match branch", func() {
// c := &Container{}
// c.Conditions.Branch.parts = []string{"feature/*"}
// FilterBranch("feature/foo")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should match branch wildcard", func() {
// c := &Container{}
// c.Conditions.Branch.parts = []string{"feature/*"}
// FilterBranch("feature/foo")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should disable when branch filter doesn't match", func() {
// c := &Container{}
// c.Conditions.Branch.parts = []string{"feature/*", "develop"}
// FilterBranch("master")(nil, c)
// g.Assert(c.Disabled).IsTrue()
// })
// g.It("Should match no platform filter", func() {
// c := &Container{}
// FilterPlatform("linux_amd64")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should match platform", func() {
// c := &Container{}
// c.Conditions.Platform.parts = []string{"linux_amd64"}
// FilterPlatform("linux_amd64")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should disable when platform filter doesn't match", func() {
// c := &Container{}
// c.Conditions.Platform.parts = []string{"linux_arm", "linux_arm64"}
// FilterPlatform("linux_amd64")(nil, c)
// g.Assert(c.Disabled).IsTrue()
// })
// g.It("Should match no environment filter", func() {
// c := &Container{}
// FilterEnvironment("production")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should match environment", func() {
// c := &Container{}
// c.Conditions.Environment.parts = []string{"production"}
// FilterEnvironment("production")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should disable when environment filter doesn't match", func() {
// c := &Container{}
// c.Conditions.Environment.parts = []string{"develop", "staging"}
// FilterEnvironment("production")(nil, c)
// g.Assert(c.Disabled).IsTrue()
// })
// g.It("Should match no event filter", func() {
// c := &Container{}
// FilterEvent("push")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should match event", func() {
// c := &Container{}
// c.Conditions.Event.parts = []string{"push"}
// FilterEvent("push")(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should disable when event filter doesn't match", func() {
// c := &Container{}
// c.Conditions.Event.parts = []string{"push", "tag"}
// FilterEvent("pull_request")(nil, c)
// g.Assert(c.Disabled).IsTrue()
// })
// g.It("Should match matrix", func() {
// c := &Container{}
// c.Conditions.Matrix = map[string]string{
// "go": "1.5",
// "redis": "3.0",
// }
// matrix := map[string]string{
// "go": "1.5",
// "redis": "3.0",
// "node": "5.0.0",
// }
// FilterMatrix(matrix)(nil, c)
// g.Assert(c.Disabled).IsFalse()
// })
// g.It("Should disable when event filter doesn't match", func() {
// c := &Container{}
// c.Conditions.Matrix = map[string]string{
// "go": "1.5",
// "redis": "3.0",
// }
// matrix := map[string]string{
// "go": "1.4.2",
// "redis": "3.0",
// "node": "5.0.0",
// }
// FilterMatrix(matrix)(nil, c)
// g.Assert(c.Disabled).IsTrue()
// })
// })
// }

View file

@ -0,0 +1,63 @@
package builtin
import (
"path/filepath"
"strings"
"github.com/drone/drone/engine/compiler/parse"
)
type normalizeOp struct {
visitor
namespace string
}
// NewNormalizeOp returns a transformer that normalizes the container image
// names and plugin names to their fully qualified values.
func NewNormalizeOp(namespace string) Visitor {
return &normalizeOp{
namespace: namespace,
}
}
func (v *normalizeOp) VisitContainer(node *parse.ContainerNode) error {
v.normalizeName(node)
v.normalizeImage(node)
switch node.NodeType {
case parse.NodePlugin, parse.NodeCache, parse.NodeClone:
v.normalizePlugin(node)
}
return nil
}
// normalize the container image to the fully qualified name.
func (v *normalizeOp) normalizeImage(node *parse.ContainerNode) {
if strings.Contains(node.Container.Image, ":") {
return
}
node.Container.Image = node.Container.Image + ":latest"
}
// normalize the plugin entrypoint and command values.
func (v *normalizeOp) normalizePlugin(node *parse.ContainerNode) {
if strings.Contains(node.Container.Image, "/") {
return
}
node.Container.Image = filepath.Join(v.namespace, node.Container.Image)
}
// normalize the container name to ensrue a value is set.
func (v *normalizeOp) normalizeName(node *parse.ContainerNode) {
if node.Container.Name != "" {
return
}
parts := strings.Split(node.Container.Image, "/")
if len(parts) != 0 {
node.Container.Name = parts[len(parts)-1]
}
parts = strings.Split(node.Container.Image, ":")
if len(parts) != 0 {
node.Container.Name = parts[0]
}
}

View file

@ -0,0 +1,69 @@
package builtin
import (
"testing"
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
"github.com/franela/goblin"
)
func Test_normalize(t *testing.T) {
root := parse.NewRootNode()
g := goblin.Goblin(t)
g.Describe("normalizing", func() {
g.Describe("images", func() {
g.It("should append tag if empty", func() {
c := root.NewContainerNode()
c.Container = runner.Container{Image: "golang"}
op := NewNormalizeOp("")
op.VisitContainer(c)
g.Assert(c.Container.Image).Equal("golang:latest")
})
g.It("should not override existing tag", func() {
c := root.NewContainerNode()
c.Container = runner.Container{Image: "golang:1.5"}
op := NewNormalizeOp("")
op.VisitContainer(c)
g.Assert(c.Container.Image).Equal("golang:1.5")
})
})
g.Describe("plugins", func() {
g.It("should prepend namespace", func() {
c := root.NewPluginNode()
c.Container = runner.Container{Image: "git"}
op := NewNormalizeOp("plugins")
op.VisitContainer(c)
g.Assert(c.Container.Image).Equal("plugins/git:latest")
})
g.It("should not override existing namespace", func() {
c := root.NewPluginNode()
c.Container = runner.Container{Image: "index.docker.io/drone/git"}
op := NewNormalizeOp("plugins")
op.VisitContainer(c)
g.Assert(c.Container.Image).Equal("index.docker.io/drone/git:latest")
})
g.It("should ignore shell or service types", func() {
c := root.NewShellNode()
c.Container = runner.Container{Image: "golang"}
op := NewNormalizeOp("plugins")
op.VisitContainer(c)
g.Assert(c.Container.Image).Equal("golang:latest")
})
})
})
}

View file

@ -0,0 +1,50 @@
package builtin
import (
"fmt"
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
)
type podOp struct {
visitor
name string
}
// NewPodOp returns a transformer that configures an ambassador container
// providing shared networking and container volumes.
func NewPodOp(name string) Visitor {
return &podOp{
name: name,
}
}
func (v *podOp) VisitContainer(node *parse.ContainerNode) error {
if node.Container.Network == "" {
parent := fmt.Sprintf("container:%s", v.name)
node.Container.Network = parent
}
node.Container.VolumesFrom = append(node.Container.VolumesFrom, v.name)
return nil
}
func (v *podOp) VisitRoot(node *parse.RootNode) error {
service := node.NewServiceNode()
service.Container = runner.Container{
Name: v.name,
Alias: "ambassador",
Image: "busybox",
Entrypoint: []string{"/bin/sleep"},
Command: []string{"86400"},
Volumes: []string{node.Path, node.Base},
// Entrypoint: []string{"/bin/sh", "-c"},
// Volumes: []string{node.Base},
// Command: []string{
// fmt.Sprintf("mkdir -p %s; sleep 86400", node.Path),
// },
}
node.Pod = service
return nil
}

View file

@ -0,0 +1,26 @@
package builtin
import (
"github.com/drone/drone/engine/compiler/parse"
)
type pullOp struct {
visitor
pull bool
}
// NewPullOp returns a transformer that configures plugins to automatically
// pull the latest images at runtime.
func NewPullOp(pull bool) Visitor {
return &pullOp{
pull: pull,
}
}
func (v *pullOp) VisitContainer(node *parse.ContainerNode) error {
switch node.NodeType {
case parse.NodePlugin, parse.NodeCache, parse.NodeClone:
node.Container.Pull = v.pull
}
return nil
}

View file

@ -0,0 +1,45 @@
package builtin
import (
"testing"
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
"github.com/franela/goblin"
)
func Test_pull(t *testing.T) {
root := parse.NewRootNode()
g := goblin.Goblin(t)
g.Describe("pull image", func() {
g.It("should be enabled for plugins", func() {
c := root.NewPluginNode()
c.Container = runner.Container{}
op := NewPullOp(true)
op.VisitContainer(c)
g.Assert(c.Container.Pull).IsTrue()
})
g.It("should be disabled for plugins", func() {
c := root.NewPluginNode()
c.Container = runner.Container{}
op := NewPullOp(false)
op.VisitContainer(c)
g.Assert(c.Container.Pull).IsFalse()
})
g.It("should be disabled for non-plugins", func() {
c := root.NewShellNode()
c.Container = runner.Container{}
op := NewPullOp(true)
op.VisitContainer(c)
g.Assert(c.Container.Pull).IsFalse()
})
})
}

View file

@ -0,0 +1,85 @@
package builtin
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/drone/drone/engine/compiler/parse"
)
const (
Freebsd_amd64 = "freebsd_amd64"
Linux_adm64 = "linux_amd64"
Windows_amd64 = "windows_amd64"
)
type shellOp struct {
visitor
platform string
}
// NewShellOp returns a transformer that converts the shell node to
// a runnable container.
func NewShellOp(platform string) Visitor {
return &shellOp{
platform: platform,
}
}
func (v *shellOp) VisitContainer(node *parse.ContainerNode) error {
if node.NodeType != parse.NodeShell {
return nil
}
node.Container.Entrypoint = []string{
"/bin/sh", "-c",
}
node.Container.Command = []string{
"echo $CI_CMDS | base64 -d | /bin/sh -e",
}
if node.Container.Environment == nil {
node.Container.Environment = map[string]string{}
}
node.Container.Environment["HOME"] = "/root"
node.Container.Environment["SHELL"] = "/bin/sh"
node.Container.Environment["CI_CMDS"] = toScript(
node.Root().Path,
node.Commands,
)
return nil
}
func toScript(base string, commands []string) string {
var buf bytes.Buffer
for _, command := range commands {
buf.WriteString(fmt.Sprintf(
traceScript,
"<command>"+command+"</command>",
command,
))
}
script := fmt.Sprintf(
setupScript,
buf.String(),
)
return base64.StdEncoding.EncodeToString([]byte(script))
}
// setupScript is a helper script this is added to the build to ensure
// a minimum set of environment variables are set correctly.
const setupScript = `
echo $DRONE_NETRC > $HOME/.netrc
%s
`
// traceScript is a helper script that is added to the build script
// to trace a command.
const traceScript = `
echo %q
%s
`

View file

@ -0,0 +1,44 @@
package builtin
import (
"testing"
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
"github.com/franela/goblin"
)
func Test_shell(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("shell containers", func() {
g.It("should ignore plugin steps", func() {
root := parse.NewRootNode()
c := root.NewPluginNode()
c.Container = runner.Container{}
ops := NewShellOp(Linux_adm64)
ops.VisitContainer(c)
g.Assert(len(c.Container.Entrypoint)).Equal(0)
g.Assert(len(c.Container.Command)).Equal(0)
g.Assert(c.Container.Environment["CI_CMDS"]).Equal("")
})
g.It("should set entrypoint, command and environment variables", func() {
root := parse.NewRootNode()
root.Base = "/go"
root.Path = "/go/src/github.com/octocat/hello-world"
c := root.NewShellNode()
c.Commands = []string{"go build"}
ops := NewShellOp(Linux_adm64)
ops.VisitContainer(c)
g.Assert(c.Container.Entrypoint).Equal([]string{"/bin/sh", "-c"})
g.Assert(c.Container.Command).Equal([]string{"echo $CI_CMDS | base64 -d | /bin/sh -e"})
g.Assert(c.Container.Environment["CI_CMDS"] != "").IsTrue()
})
})
}

View file

@ -0,0 +1,105 @@
package builtin
import (
"fmt"
"path/filepath"
"github.com/drone/drone/engine/compiler/parse"
)
type validateOp struct {
visitor
plugins []string
trusted bool
}
// NewValidateOp returns a linter that checks container configuration.
func NewValidateOp(trusted bool, plugins []string) Visitor {
return &validateOp{
trusted: trusted,
plugins: plugins,
}
}
func (v *validateOp) VisitContainer(node *parse.ContainerNode) error {
switch node.NodeType {
case parse.NodePlugin, parse.NodeCache, parse.NodeClone:
if err := v.validatePlugins(node); err != nil {
return err
}
}
if node.NodeType == parse.NodePlugin {
if err := v.validatePluginConfig(node); err != nil {
return err
}
}
return v.validateConfig(node)
}
// validate the plugin image and return an error if the plugin
// image does not match the whitelist.
func (v *validateOp) validatePlugins(node *parse.ContainerNode) error {
match := false
for _, pattern := range v.plugins {
ok, err := filepath.Match(pattern, node.Container.Image)
if ok && err == nil {
match = true
break
}
}
if !match {
return fmt.Errorf(
"Plugin %s is not in the whitelist",
node.Container.Image,
)
}
return nil
}
// validate the plugin command and entrypoint and return an error
// the user attempts to set or override these values.
func (v *validateOp) validatePluginConfig(node *parse.ContainerNode) error {
if len(node.Container.Entrypoint) != 0 {
return fmt.Errorf("Cannot set plugin Entrypoint")
}
if len(node.Container.Command) != 0 {
return fmt.Errorf("Cannot set plugin Command")
}
return nil
}
// validate the container configuration and return an error if
// restricted configurations are used.
func (v *validateOp) validateConfig(node *parse.ContainerNode) error {
if v.trusted {
return nil
}
if node.Container.Privileged {
return fmt.Errorf("Insufficient privileges to use privileged mode")
}
if len(node.Container.DNS) != 0 {
return fmt.Errorf("Insufficient privileges to use custom dns")
}
if len(node.Container.DNSSearch) != 0 {
return fmt.Errorf("Insufficient privileges to use dns_search")
}
if len(node.Container.Devices) != 0 {
return fmt.Errorf("Insufficient privileges to use devices")
}
if len(node.Container.ExtraHosts) != 0 {
return fmt.Errorf("Insufficient privileges to use extra_hosts")
}
if len(node.Container.Network) != 0 {
return fmt.Errorf("Insufficient privileges to override the network")
}
if node.Container.OomKillDisable {
return fmt.Errorf("Insufficient privileges to disable oom_kill")
}
if len(node.Container.Volumes) != 0 && node.Type() != parse.NodeCache {
return fmt.Errorf("Insufficient privileges to use volumes")
}
if len(node.Container.VolumesFrom) != 0 {
return fmt.Errorf("Insufficient privileges to use volumes_from")
}
return nil
}

View file

@ -0,0 +1,199 @@
package builtin
import (
"testing"
"github.com/drone/drone/engine/compiler/parse"
"github.com/drone/drone/engine/runner"
"github.com/franela/goblin"
)
func Test_validate(t *testing.T) {
root := parse.NewRootNode()
g := goblin.Goblin(t)
g.Describe("validating", func() {
g.Describe("privileged attributes", func() {
g.It("should not error when trusted build", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
ops := NewValidateOp(true, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err == nil).IsTrue("error should be nil")
})
g.It("should error when privleged mode", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.Privileged = true
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to use privileged mode")
})
g.It("should error when dns configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.DNS = []string{"8.8.8.8"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to use custom dns")
})
g.It("should error when dns_search configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.DNSSearch = []string{"8.8.8.8"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to use dns_search")
})
g.It("should error when devices configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.Devices = []string{"/dev/foo"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to use devices")
})
g.It("should error when extra_hosts configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.ExtraHosts = []string{"1.2.3.4 foo.com"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to use extra_hosts")
})
g.It("should error when network configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.Network = "host"
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to override the network")
})
g.It("should error when oom_kill_disabled configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.OomKillDisable = true
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to disable oom_kill")
})
g.It("should error when volumes configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.Volumes = []string{"/:/tmp"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to use volumes")
})
g.It("should error when volumes_from configured", func() {
c := root.NewContainerNode()
c.Container = runner.Container{}
c.Container.VolumesFrom = []string{"drone"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Insufficient privileges to use volumes_from")
})
})
g.Describe("plugin configuration", func() {
g.It("should error when entrypoint is configured", func() {
c := root.NewPluginNode()
c.Container = runner.Container{Image: "plugins/git"}
c.Container.Entrypoint = []string{"/bin/sh"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Cannot set plugin Entrypoint")
})
g.It("should error when command is configured", func() {
c := root.NewPluginNode()
c.Container = runner.Container{Image: "plugins/git"}
c.Container.Command = []string{"cat", "/proc/1/status"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should not be nil")
g.Assert(err.Error()).Equal("Cannot set plugin Command")
})
g.It("should not error when empty entrypoint, command", func() {
c := root.NewPluginNode()
c.Container = runner.Container{Image: "plugins/git"}
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err == nil).IsTrue("error should be nil")
})
})
g.Describe("plugin whitelist", func() {
g.It("should error when no match found", func() {
c := root.NewPluginNode()
c.Container = runner.Container{}
c.Container.Image = "custom/git"
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err != nil).IsTrue("error should be nil")
g.Assert(err.Error()).Equal("Plugin custom/git is not in the whitelist")
})
g.It("should not error when match found", func() {
c := root.NewPluginNode()
c.Container = runner.Container{}
c.Container.Image = "plugins/git"
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err == nil).IsTrue("error should be nil")
})
g.It("should ignore build images", func() {
c := root.NewShellNode()
c.Container = runner.Container{}
c.Container.Image = "google/golang"
ops := NewValidateOp(false, []string{"plugins/*"})
err := ops.VisitContainer(c)
g.Assert(err == nil).IsTrue("error should be nil")
})
})
})
}

View file

@ -0,0 +1,23 @@
package builtin
import "github.com/drone/drone/engine/compiler/parse"
// Visitor interface for walking the Yaml file.
type Visitor interface {
VisitRoot(*parse.RootNode) error
VisitVolume(*parse.VolumeNode) error
VisitNetwork(*parse.NetworkNode) error
VisitBuild(*parse.BuildNode) error
VisitContainer(*parse.ContainerNode) error
}
// visitor provides an easy default implementation of a Visitor interface with
// stubbed methods. This can be embedded in transforms to meet the basic
// requirements.
type visitor struct{}
func (visitor) VisitRoot(*parse.RootNode) error { return nil }
func (visitor) VisitVolume(*parse.VolumeNode) error { return nil }
func (visitor) VisitNetwork(*parse.NetworkNode) error { return nil }
func (visitor) VisitBuild(*parse.BuildNode) error { return nil }
func (visitor) VisitContainer(*parse.ContainerNode) error { return nil }

View file

@ -0,0 +1,50 @@
package builtin
import (
"path/filepath"
"github.com/drone/drone/engine/compiler/parse"
)
type workspaceOp struct {
visitor
base string
path string
}
// NewWorkspaceOp returns a transformer that provides a default workspace paths,
// including the base path (mounted as a volume) and absolute path where the
// code is cloned.
func NewWorkspaceOp(base, path string) Visitor {
return &workspaceOp{
base: base,
path: path,
}
}
func (v *workspaceOp) VisitRoot(node *parse.RootNode) error {
if node.Base == "" {
node.Base = v.base
}
if node.Path == "" {
node.Path = v.path
}
if !filepath.IsAbs(node.Path) {
node.Path = filepath.Join(
node.Base,
node.Path,
)
}
return nil
}
func (v *workspaceOp) VisitContainer(node *parse.ContainerNode) error {
if node.NodeType == parse.NodeService {
// we must not override the default working
// directory of service containers. All other
// container should launch in the workspace
return nil
}
node.Container.WorkingDir = node.Root().Path
return nil
}

View file

@ -0,0 +1,89 @@
package builtin
import (
"testing"
"github.com/franela/goblin"
"github.com/drone/drone/engine/compiler/parse"
)
func Test_workspace(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("workspace", func() {
var defaultBase = "/go"
var defaultPath = "src/github.com/octocat/hello-world"
g.It("should not override user paths", func() {
var base = "/drone"
var path = "/drone/src/github.com/octocat/hello-world"
op := NewWorkspaceOp(defaultBase, defaultPath)
root := parse.NewRootNode()
root.Base = base
root.Path = path
op.VisitRoot(root)
g.Assert(root.Base).Equal(base)
g.Assert(root.Path).Equal(path)
})
g.It("should convert user paths to absolute", func() {
var base = "/drone"
var path = "src/github.com/octocat/hello-world"
var abs = "/drone/src/github.com/octocat/hello-world"
op := NewWorkspaceOp(defaultBase, defaultPath)
root := parse.NewRootNode()
root.Base = base
root.Path = path
op.VisitRoot(root)
g.Assert(root.Base).Equal(base)
g.Assert(root.Path).Equal(abs)
})
g.It("should set the default path", func() {
var base = "/go"
var path = "/go/src/github.com/octocat/hello-world"
op := NewWorkspaceOp(defaultBase, defaultPath)
root := parse.NewRootNode()
op.VisitRoot(root)
g.Assert(root.Base).Equal(base)
g.Assert(root.Path).Equal(path)
})
g.It("should use workspace as working_dir", func() {
var base = "/drone"
var path = "/drone/src/github.com/octocat/hello-world"
root := parse.NewRootNode()
root.Base = base
root.Path = path
c := root.NewContainerNode()
op := NewWorkspaceOp(defaultBase, defaultPath)
op.VisitContainer(c)
g.Assert(c.Container.WorkingDir).Equal(root.Path)
})
g.It("should not use workspace as working_dir for services", func() {
var base = "/drone"
var path = "/drone/src/github.com/octocat/hello-world"
root := parse.NewRootNode()
root.Base = base
root.Path = path
c := root.NewServiceNode()
op := NewWorkspaceOp(defaultBase, defaultPath)
op.VisitContainer(c)
g.Assert(c.Container.WorkingDir).Equal("")
})
})
}

146
engine/compiler/compile.go Normal file
View file

@ -0,0 +1,146 @@
package libyaml
import (
"github.com/drone/drone/engine/runner"
"github.com/drone/drone/engine/runner/parse"
yaml "github.com/drone/drone/engine/compiler/parse"
)
// Compiler compiles the Yaml file to the intermediate representation.
type Compiler struct {
trans []Transform
}
func New() *Compiler {
return &Compiler{}
}
// Transforms sets the compiler transforms use to transform the intermediate
// representation during compilation.
func (c *Compiler) Transforms(trans []Transform) *Compiler {
c.trans = append(c.trans, trans...)
return c
}
// CompileString compiles the Yaml configuration string and returns
// the intermediate representation for the interpreter.
func (c *Compiler) CompileString(in string) (*runner.Spec, error) {
return c.Compile([]byte(in))
}
// CompileString compiles the Yaml configuration file and returns
// the intermediate representation for the interpreter.
func (c *Compiler) Compile(in []byte) (*runner.Spec, error) {
root, err := yaml.Parse(in)
if err != nil {
return nil, err
}
if err := root.Walk(c.walk); err != nil {
return nil, err
}
config := &runner.Spec{}
tree := parse.NewTree()
// pod section
if root.Pod != nil {
node, ok := root.Pod.(*yaml.ContainerNode)
if ok {
config.Containers = append(config.Containers, &node.Container)
tree.Append(parse.NewRunNode().SetName(node.Container.Name).SetDetach(true))
}
}
// cache section
if root.Cache != nil {
node, ok := root.Cache.(*yaml.ContainerNode)
if ok && !node.Disabled {
config.Containers = append(config.Containers, &node.Container)
tree.Append(parse.NewRunNode().SetName(node.Container.Name))
}
}
// clone section
if root.Clone != nil {
node, ok := root.Clone.(*yaml.ContainerNode)
if ok && !node.Disabled {
config.Containers = append(config.Containers, &node.Container)
tree.Append(parse.NewRunNode().SetName(node.Container.Name))
}
}
// services section
for _, container := range root.Services {
node, ok := container.(*yaml.ContainerNode)
if !ok || node.Disabled {
continue
}
config.Containers = append(config.Containers, &node.Container)
tree.Append(parse.NewRunNode().SetName(node.Container.Name).SetDetach(true))
}
// pipeline section
for i, container := range root.Script {
node, ok := container.(*yaml.ContainerNode)
if !ok || node.Disabled {
continue
}
config.Containers = append(config.Containers, &node.Container)
// step 1: lookahead to see if any status=failure exist
list := parse.NewListNode()
for ii, next := range root.Script {
if i >= ii {
continue
}
node, ok := next.(*yaml.ContainerNode)
if !ok || node.Disabled || !node.OnFailure() {
continue
}
list.Append(
parse.NewRecoverNode().SetBody(
parse.NewRunNode().SetName(
node.Container.Name,
),
),
)
}
// step 2: if yes, collect these and append to "error" node
if len(list.Body) == 0 {
tree.Append(parse.NewRunNode().SetName(node.Container.Name))
} else {
errorNode := parse.NewErrorNode()
errorNode.SetBody(parse.NewRunNode().SetName(node.Container.Name))
errorNode.SetDefer(list)
tree.Append(errorNode)
}
}
config.Nodes = tree
return config, nil
}
func (c *Compiler) walk(node yaml.Node) (err error) {
for _, trans := range c.trans {
switch v := node.(type) {
case *yaml.BuildNode:
err = trans.VisitBuild(v)
case *yaml.ContainerNode:
err = trans.VisitContainer(v)
case *yaml.NetworkNode:
err = trans.VisitNetwork(v)
case *yaml.VolumeNode:
err = trans.VisitVolume(v)
case *yaml.RootNode:
err = trans.VisitRoot(v)
}
if err != nil {
break
}
}
return err
}

View file

@ -0,0 +1 @@
package libyaml

View file

@ -0,0 +1,34 @@
package parse
const (
NodeBuild = "build"
NodeCache = "cache"
NodeClone = "clone"
NodeContainer = "container"
NodeNetwork = "network"
NodePlugin = "plugin"
NodeRoot = "root"
NodeService = "service"
NodeShell = "shell"
NodeVolume = "volume"
)
// NodeType identifies the type of parse tree node.
type NodeType string
// Type returns itself an provides an easy default implementation.
// for embedding in a Node. Embedded in all non-trivial Nodes.
func (t NodeType) Type() NodeType {
return t
}
// String returns the string value of the Node type.
func (t NodeType) String() string {
return string(t)
}
// A Node is an element in the parse tree.
type Node interface {
Type() NodeType
Root() *RootNode
}

View file

@ -0,0 +1,42 @@
package parse
// BuildNode represents Docker image build instructions.
type BuildNode struct {
NodeType
Context string
Dockerfile string
Args map[string]string
root *RootNode
}
// Root returns the root node.
func (n *BuildNode) Root() *RootNode { return n.root }
//
// intermediate types for yaml decoding.
//
type build struct {
Context string
Dockerfile string
Args map[string]string
}
func (b *build) UnmarshalYAML(unmarshal func(interface{}) error) error {
err := unmarshal(&b.Context)
if err == nil {
return nil
}
out := struct {
Context string
Dockerfile string
Args map[string]string
}{}
err = unmarshal(&out)
b.Context = out.Context
b.Args = out.Args
b.Dockerfile = out.Dockerfile
return err
}

View file

@ -0,0 +1,38 @@
package parse
import (
"testing"
"github.com/franela/goblin"
"gopkg.in/yaml.v2"
)
func TestBuildNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Build", func() {
g.Describe("given a yaml file", func() {
g.It("should unmarshal", func() {
in := []byte(".")
out := build{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(out.Context).Equal(".")
})
g.It("should unmarshal shorthand", func() {
in := []byte("{ context: ., dockerfile: Dockerfile }")
out := build{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(out.Context).Equal(".")
g.Assert(out.Dockerfile).Equal("Dockerfile")
})
})
})
}

View file

@ -0,0 +1,180 @@
package parse
import (
"fmt"
"github.com/drone/drone/engine/runner"
"gopkg.in/yaml.v2"
)
type Conditions struct {
Platform []string
Environment []string
Event []string
Branch []string
Status []string
Matrix map[string]string
}
// ContainerNode represents a Docker container.
type ContainerNode struct {
NodeType
// Container represents the container configuration.
Container runner.Container
Conditions Conditions
Disabled bool
Commands []string
Vargs map[string]interface{}
root *RootNode
}
// Root returns the root node.
func (n *ContainerNode) Root() *RootNode { return n.root }
// OnSuccess returns true if the container should be executed
// when the exit code of the previous step is 0.
func (n *ContainerNode) OnSuccess() bool {
for _, status := range n.Conditions.Status {
if status == "success" {
return true
}
}
return false
}
// OnFailure returns true if the container should be executed
// even when the exit code of the previous step != 0.
func (n *ContainerNode) OnFailure() bool {
for _, status := range n.Conditions.Status {
if status == "failure" {
return true
}
}
return false
}
//
// intermediate types for yaml decoding.
//
type container struct {
Name string `yaml:"name"`
Image string `yaml:"image"`
Build string `yaml:"build"`
Pull bool `yaml:"pull"`
Privileged bool `yaml:"privileged"`
Environment mapEqualSlice `yaml:"environment"`
Entrypoint stringOrSlice `yaml:"entrypoint"`
Command stringOrSlice `yaml:"command"`
Commands stringOrSlice `yaml:"commands"`
ExtraHosts stringOrSlice `yaml:"extra_hosts"`
Volumes stringOrSlice `yaml:"volumes"`
VolumesFrom stringOrSlice `yaml:"volumes_from"`
Devices stringOrSlice `yaml:"devices"`
Network string `yaml:"network_mode"`
DNS stringOrSlice `yaml:"dns"`
DNSSearch stringOrSlice `yaml:"dns_search"`
MemSwapLimit int64 `yaml:"memswap_limit"`
MemLimit int64 `yaml:"mem_limit"`
CPUQuota int64 `yaml:"cpu_quota"`
CPUShares int64 `yaml:"cpu_shares"`
CPUSet string `yaml:"cpuset"`
OomKillDisable bool `yaml:"oom_kill_disable"`
AuthConfig struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
Email string `yaml:"email"`
Token string `yaml:"registry_token"`
} `yaml:"auth_config"`
Conditions struct {
Platform stringOrSlice `yaml:"platform"`
Environment stringOrSlice `yaml:"environment"`
Event stringOrSlice `yaml:"event"`
Branch stringOrSlice `yaml:"branch"`
Status stringOrSlice `yaml:"status"`
Matrix map[string]string `yaml:"matrix"`
} `yaml:"when"`
Vargs map[string]interface{} `yaml:",inline"`
}
func (c *container) ToContainer() runner.Container {
return runner.Container{
Name: c.Name,
Image: c.Image,
Pull: c.Pull,
Privileged: c.Privileged,
Environment: c.Environment.parts,
Entrypoint: c.Entrypoint.parts,
Command: c.Command.parts,
ExtraHosts: c.ExtraHosts.parts,
Volumes: c.Volumes.parts,
VolumesFrom: c.VolumesFrom.parts,
Devices: c.Devices.parts,
Network: c.Network,
DNS: c.DNS.parts,
DNSSearch: c.DNSSearch.parts,
MemSwapLimit: c.MemSwapLimit,
MemLimit: c.MemLimit,
CPUQuota: c.CPUQuota,
CPUShares: c.CPUShares,
CPUSet: c.CPUSet,
OomKillDisable: c.OomKillDisable,
AuthConfig: runner.Auth{
Username: c.AuthConfig.Username,
Password: c.AuthConfig.Password,
Email: c.AuthConfig.Email,
Token: c.AuthConfig.Token,
},
}
}
func (c *container) ToConditions() Conditions {
return Conditions{
Platform: c.Conditions.Platform.parts,
Environment: c.Conditions.Environment.parts,
Event: c.Conditions.Event.parts,
Branch: c.Conditions.Branch.parts,
Status: c.Conditions.Status.parts,
Matrix: c.Conditions.Matrix,
}
}
type containerList struct {
containers []*container
}
func (c *containerList) UnmarshalYAML(unmarshal func(interface{}) error) error {
slice := yaml.MapSlice{}
err := unmarshal(&slice)
if err != nil {
return err
}
for _, s := range slice {
cc := container{}
out, err := yaml.Marshal(s.Value)
if err != nil {
return err
}
err = yaml.Unmarshal(out, &cc)
if err != nil {
return err
}
if cc.Name == "" {
cc.Name = fmt.Sprintf("%v", s.Key)
}
if cc.Image == "" {
cc.Image = fmt.Sprintf("%v", s.Key)
}
c.containers = append(c.containers, &cc)
}
return err
}

View file

@ -0,0 +1,97 @@
package parse
import (
"testing"
"github.com/franela/goblin"
"gopkg.in/yaml.v2"
)
func TestContainerNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Containers", func() {
g.Describe("given a yaml file", func() {
g.It("should unmarshal", func() {
in := []byte(sampleContainer)
out := containerList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.containers)).Equal(1)
c := out.containers[0]
g.Assert(c.Name).Equal("foo")
g.Assert(c.Image).Equal("golang")
g.Assert(c.Build).Equal(".")
g.Assert(c.Pull).Equal(true)
g.Assert(c.Privileged).Equal(true)
g.Assert(c.Entrypoint.parts).Equal([]string{"/bin/sh"})
g.Assert(c.Command.parts).Equal([]string{"yes"})
g.Assert(c.Commands.parts).Equal([]string{"whoami"})
g.Assert(c.ExtraHosts.parts).Equal([]string{"foo.com"})
g.Assert(c.Volumes.parts).Equal([]string{"/foo:/bar"})
g.Assert(c.VolumesFrom.parts).Equal([]string{"foo"})
g.Assert(c.Devices.parts).Equal([]string{"/dev/tty0"})
g.Assert(c.Network).Equal("bridge")
g.Assert(c.DNS.parts).Equal([]string{"8.8.8.8"})
g.Assert(c.MemSwapLimit).Equal(int64(1))
g.Assert(c.MemLimit).Equal(int64(2))
g.Assert(c.CPUQuota).Equal(int64(3))
g.Assert(c.CPUSet).Equal("1,2")
g.Assert(c.OomKillDisable).Equal(true)
g.Assert(c.AuthConfig.Username).Equal("octocat")
g.Assert(c.AuthConfig.Password).Equal("password")
g.Assert(c.AuthConfig.Email).Equal("octocat@github.com")
g.Assert(c.Vargs["access_key"]).Equal("970d28f4dd477bc184fbd10b376de753")
g.Assert(c.Vargs["secret_key"]).Equal("9c5785d3ece6a9cdefa42eb99b58986f9095ff1c")
})
g.It("should unmarshal named", func() {
in := []byte("foo: { name: bar }")
out := containerList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.containers)).Equal(1)
g.Assert(out.containers[0].Name).Equal("bar")
})
})
})
}
var sampleContainer = `
foo:
image: golang
build: .
pull: true
privileged: true
environment:
FOO: BAR
entrypoint: /bin/sh
command: "yes"
commands: whoami
extra_hosts: foo.com
volumes: /foo:/bar
volumes_from: foo
devices: /dev/tty0
network_mode: bridge
dns: 8.8.8.8
memswap_limit: 1
mem_limit: 2
cpu_quota: 3
cpuset: 1,2
oom_kill_disable: true
auth_config:
username: octocat
password: password
email: octocat@github.com
access_key: 970d28f4dd477bc184fbd10b376de753
secret_key: 9c5785d3ece6a9cdefa42eb99b58986f9095ff1c
`

View file

@ -0,0 +1,68 @@
package parse
import (
"fmt"
"gopkg.in/yaml.v2"
)
// NetworkNode represents a Docker network.
type NetworkNode struct {
NodeType
root *RootNode
Name string
Driver string
DriverOpts map[string]string
}
// Root returns the root node.
func (n *NetworkNode) Root() *RootNode { return n.root }
//
// intermediate types for yaml decoding.
//
// network is an intermediate type used for decoding a networks in a format
// compatible with docker-compose.yml
type network struct {
Name string
Driver string
DriverOpts map[string]string `yaml:"driver_opts"`
}
// networkList is an intermediate type used for decoding a slice of networks
// in a format compatible with docker-compose.yml
type networkList struct {
networks []*network
}
func (n *networkList) UnmarshalYAML(unmarshal func(interface{}) error) error {
slice := yaml.MapSlice{}
err := unmarshal(&slice)
if err != nil {
return err
}
for _, s := range slice {
nn := network{}
out, err := yaml.Marshal(s.Value)
if err != nil {
return err
}
err = yaml.Unmarshal(out, &nn)
if err != nil {
return err
}
if nn.Name == "" {
nn.Name = fmt.Sprintf("%v", s.Key)
}
if nn.Driver == "" {
nn.Driver = "bridge"
}
n.networks = append(n.networks, &nn)
}
return err
}

View file

@ -0,0 +1,51 @@
package parse
import (
"testing"
"github.com/franela/goblin"
"gopkg.in/yaml.v2"
)
func TestNetworkNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Networks", func() {
g.Describe("given a yaml file", func() {
g.It("should unmarshal", func() {
in := []byte("foo: { driver: overlay }")
out := networkList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.networks)).Equal(1)
g.Assert(out.networks[0].Name).Equal("foo")
g.Assert(out.networks[0].Driver).Equal("overlay")
})
g.It("should unmarshal named", func() {
in := []byte("foo: { name: bar }")
out := networkList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.networks)).Equal(1)
g.Assert(out.networks[0].Name).Equal("bar")
})
g.It("should unmarshal and use default driver", func() {
in := []byte("foo: { name: bar }")
out := volumeList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.volumes)).Equal(1)
g.Assert(out.volumes[0].Driver).Equal("local")
})
})
})
}

View file

@ -0,0 +1,148 @@
package parse
// RootNode is the root node in the parsed Yaml file.
type RootNode struct {
NodeType
Platform string
Base string
Path string
Image string
Pod Node
Build Node
Cache Node
Clone Node
Script []Node
Volumes []Node
Networks []Node
Services []Node
}
// NewRootNode returns a new root node.
func NewRootNode() *RootNode {
return &RootNode{
NodeType: NodeRoot,
}
}
// Root returns the root node.
func (n *RootNode) Root() *RootNode { return n }
// Returns a new Volume Node.
func (n *RootNode) NewVolumeNode(name string) *VolumeNode {
return &VolumeNode{
NodeType: NodeVolume,
Name: name,
root: n,
}
}
// Returns a new Network Node.
func (n *RootNode) NewNetworkNode(name string) *NetworkNode {
return &NetworkNode{
NodeType: NodeNetwork,
Name: name,
root: n,
}
}
// Returns a new Network Node.
func (n *RootNode) NewBuildNode(context string) *BuildNode {
return &BuildNode{
NodeType: NodeBuild,
Context: context,
root: n,
}
}
// Returns a new Container Plugin Node.
func (n *RootNode) NewPluginNode() *ContainerNode {
return &ContainerNode{
NodeType: NodePlugin,
root: n,
}
}
// Returns a new Container Shell Node.
func (n *RootNode) NewShellNode() *ContainerNode {
return &ContainerNode{
NodeType: NodeShell,
root: n,
}
}
// Returns a new Container Service Node.
func (n *RootNode) NewServiceNode() *ContainerNode {
return &ContainerNode{
NodeType: NodeService,
root: n,
}
}
// Returns a new Container Clone Node.
func (n *RootNode) NewCloneNode() *ContainerNode {
return &ContainerNode{
NodeType: NodeClone,
root: n,
}
}
// Returns a new Container Cache Node.
func (n *RootNode) NewCacheNode() *ContainerNode {
return &ContainerNode{
NodeType: NodeCache,
root: n,
}
}
// Returns a new Container Node.
func (n *RootNode) NewContainerNode() *ContainerNode {
return &ContainerNode{
NodeType: NodeContainer,
root: n,
}
}
// Walk is a function that walk through all child nodes of the RootNode
// and invokes the Walk callback function for each Node.
func (n *RootNode) Walk(fn WalkFunc) (err error) {
var nodes []Node
nodes = append(nodes, n)
nodes = append(nodes, n.Build)
nodes = append(nodes, n.Cache)
nodes = append(nodes, n.Clone)
nodes = append(nodes, n.Script...)
nodes = append(nodes, n.Volumes...)
nodes = append(nodes, n.Networks...)
nodes = append(nodes, n.Services...)
for _, node := range nodes {
err = fn(node)
if err != nil {
return
}
}
return
}
type WalkFunc func(Node) error
//
// intermediate types for yaml decoding.
//
type root struct {
Workspace struct {
Path string
Base string
}
Image string
Platform string
Volumes volumeList
Networks networkList
Services containerList
Script containerList
Cache container
Clone container
Build build
}

View file

@ -0,0 +1,85 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestRootNode(t *testing.T) {
g := goblin.Goblin(t)
r := &RootNode{}
g.Describe("Root Node", func() {
g.It("should return self as root", func() {
g.Assert(r).Equal(r.Root())
})
g.It("should create a Volume Node", func() {
n := r.NewVolumeNode("foo")
g.Assert(n.Root()).Equal(r)
g.Assert(n.Name).Equal("foo")
g.Assert(n.String()).Equal(NodeVolume)
g.Assert(n.Type()).Equal(NodeType(NodeVolume))
})
g.It("should create a Network Node", func() {
n := r.NewNetworkNode("foo")
g.Assert(n.Root()).Equal(r)
g.Assert(n.Name).Equal("foo")
g.Assert(n.String()).Equal(NodeNetwork)
g.Assert(n.Type()).Equal(NodeType(NodeNetwork))
})
g.It("should create a Plugin Node", func() {
n := r.NewPluginNode()
g.Assert(n.Root()).Equal(r)
g.Assert(n.String()).Equal(NodePlugin)
g.Assert(n.Type()).Equal(NodeType(NodePlugin))
})
g.It("should create a Shell Node", func() {
n := r.NewShellNode()
g.Assert(n.Root()).Equal(r)
g.Assert(n.String()).Equal(NodeShell)
g.Assert(n.Type()).Equal(NodeType(NodeShell))
})
g.It("should create a Service Node", func() {
n := r.NewServiceNode()
g.Assert(n.Root()).Equal(r)
g.Assert(n.String()).Equal(NodeService)
g.Assert(n.Type()).Equal(NodeType(NodeService))
})
g.It("should create a Build Node", func() {
n := r.NewBuildNode(".")
g.Assert(n.Root()).Equal(r)
g.Assert(n.Context).Equal(".")
g.Assert(n.String()).Equal(NodeBuild)
g.Assert(n.Type()).Equal(NodeType(NodeBuild))
})
g.It("should create a Cache Node", func() {
n := r.NewCacheNode()
g.Assert(n.Root()).Equal(r)
g.Assert(n.String()).Equal(NodeCache)
g.Assert(n.Type()).Equal(NodeType(NodeCache))
})
g.It("should create a Clone Node", func() {
n := r.NewCloneNode()
g.Assert(n.Root()).Equal(r)
g.Assert(n.String()).Equal(NodeClone)
g.Assert(n.Type()).Equal(NodeType(NodeClone))
})
g.It("should create a Container Node", func() {
n := r.NewContainerNode()
g.Assert(n.Root()).Equal(r)
g.Assert(n.String()).Equal(NodeContainer)
g.Assert(n.Type()).Equal(NodeType(NodeContainer))
})
})
}

View file

@ -0,0 +1,69 @@
package parse
import (
"fmt"
"gopkg.in/yaml.v2"
)
// VolumeNode represents a Docker volume.
type VolumeNode struct {
NodeType
root *RootNode
Name string
Driver string
DriverOpts map[string]string
External bool
}
// Root returns the root node.
func (n *VolumeNode) Root() *RootNode { return n.root }
//
// intermediate types for yaml decoding.
//
// volume is an intermediate type used for decoding a volumes in a format
// compatible with docker-compose.yml
type volume struct {
Name string
Driver string
DriverOpts map[string]string `yaml:"driver_opts"`
}
// volumeList is an intermediate type used for decoding a slice of volumes
// in a format compatible with docker-compose.yml
type volumeList struct {
volumes []*volume
}
func (v *volumeList) UnmarshalYAML(unmarshal func(interface{}) error) error {
slice := yaml.MapSlice{}
err := unmarshal(&slice)
if err != nil {
return err
}
for _, s := range slice {
vv := volume{}
out, err := yaml.Marshal(s.Value)
if err != nil {
return err
}
err = yaml.Unmarshal(out, &vv)
if err != nil {
return err
}
if vv.Name == "" {
vv.Name = fmt.Sprintf("%v", s.Key)
}
if vv.Driver == "" {
vv.Driver = "local"
}
v.volumes = append(v.volumes, &vv)
}
return err
}

View file

@ -0,0 +1,51 @@
package parse
import (
"testing"
"github.com/franela/goblin"
"gopkg.in/yaml.v2"
)
func TestVolumeNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Volumes", func() {
g.Describe("given a yaml file", func() {
g.It("should unmarshal", func() {
in := []byte("foo: { driver: blockbridge }")
out := volumeList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.volumes)).Equal(1)
g.Assert(out.volumes[0].Name).Equal("foo")
g.Assert(out.volumes[0].Driver).Equal("blockbridge")
})
g.It("should unmarshal named", func() {
in := []byte("foo: { name: bar }")
out := volumeList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.volumes)).Equal(1)
g.Assert(out.volumes[0].Name).Equal("bar")
})
g.It("should unmarshal and use default driver", func() {
in := []byte("foo: { name: bar }")
out := volumeList{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.volumes)).Equal(1)
g.Assert(out.volumes[0].Driver).Equal("local")
})
})
})
}

View file

@ -0,0 +1,100 @@
package parse
import (
"gopkg.in/yaml.v2"
)
// Parse parses a Yaml file and returns a Tree structure.
func Parse(in []byte) (*RootNode, error) {
out := root{}
err := yaml.Unmarshal(in, &out)
if err != nil {
return nil, err
}
root := NewRootNode()
root.Platform = out.Platform
root.Path = out.Workspace.Path
root.Base = out.Workspace.Base
root.Image = out.Image
// append volume nodes to tree
for _, v := range out.Volumes.volumes {
vv := root.NewVolumeNode(v.Name)
vv.Driver = v.Driver
vv.DriverOpts = v.DriverOpts
root.Volumes = append(root.Volumes, vv)
}
// append network nodes to tree
for _, n := range out.Networks.networks {
nn := root.NewNetworkNode(n.Name)
nn.Driver = n.Driver
nn.DriverOpts = n.DriverOpts
root.Networks = append(root.Networks, nn)
}
// add the build section
if out.Build.Context != "" {
root.Build = &BuildNode{
NodeType: NodeBuild,
Context: out.Build.Context,
Dockerfile: out.Build.Dockerfile,
Args: out.Build.Args,
root: root,
}
}
// add the cache section
{
cc := root.NewCacheNode()
cc.Container = out.Cache.ToContainer()
cc.Conditions = out.Cache.ToConditions()
cc.Container.Name = "cache"
cc.Vargs = out.Cache.Vargs
root.Cache = cc
}
// add the clone section
{
cc := root.NewCloneNode()
cc.Conditions = out.Clone.ToConditions()
cc.Container = out.Clone.ToContainer()
cc.Container.Name = "clone"
cc.Vargs = out.Clone.Vargs
root.Clone = cc
}
// append services
for _, c := range out.Services.containers {
if c.Build != "" {
continue
}
cc := root.NewServiceNode()
cc.Conditions = c.ToConditions()
cc.Container = c.ToContainer()
root.Services = append(root.Services, cc)
}
// append scripts
for _, c := range out.Script.containers {
var cc *ContainerNode
if len(c.Commands.parts) == 0 {
cc = root.NewPluginNode()
} else {
cc = root.NewShellNode()
}
cc.Commands = c.Commands.parts
cc.Vargs = c.Vargs
cc.Container = c.ToContainer()
cc.Conditions = c.ToConditions()
root.Script = append(root.Script, cc)
}
return root, nil
}
// ParseString parses a Yaml string and returns a Tree structure.
func ParseString(in string) (*RootNode, error) {
return Parse([]byte(in))
}

View file

@ -0,0 +1,96 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestParse(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Parser", func() {
g.Describe("given a yaml file", func() {
g.It("should unmarshal a string", func() {
out, err := ParseString(sampleYaml)
if err != nil {
g.Fail(err)
}
g.Assert(out.Image).Equal("hello-world")
g.Assert(out.Base).Equal("/go")
g.Assert(out.Path).Equal("src/github.com/octocat/hello-world")
g.Assert(out.Build.(*BuildNode).Context).Equal(".")
g.Assert(out.Build.(*BuildNode).Dockerfile).Equal("Dockerfile")
g.Assert(out.Cache.(*ContainerNode).Vargs["mount"]).Equal("node_modules")
g.Assert(out.Clone.(*ContainerNode).Container.Image).Equal("git")
g.Assert(out.Clone.(*ContainerNode).Vargs["depth"]).Equal(1)
g.Assert(out.Volumes[0].(*VolumeNode).Name).Equal("custom")
g.Assert(out.Volumes[0].(*VolumeNode).Driver).Equal("blockbridge")
g.Assert(out.Networks[0].(*NetworkNode).Name).Equal("custom")
g.Assert(out.Networks[0].(*NetworkNode).Driver).Equal("overlay")
g.Assert(out.Services[0].(*ContainerNode).Container.Name).Equal("database")
g.Assert(out.Services[0].(*ContainerNode).Container.Image).Equal("mysql")
g.Assert(out.Script[0].(*ContainerNode).Container.Name).Equal("test")
g.Assert(out.Script[0].(*ContainerNode).Container.Image).Equal("golang")
g.Assert(out.Script[0].(*ContainerNode).Commands).Equal([]string{"go install", "go test"})
g.Assert(out.Script[0].(*ContainerNode).String()).Equal(NodeShell)
g.Assert(out.Script[1].(*ContainerNode).Container.Name).Equal("build")
g.Assert(out.Script[1].(*ContainerNode).Container.Image).Equal("golang")
g.Assert(out.Script[1].(*ContainerNode).Commands).Equal([]string{"go build"})
g.Assert(out.Script[1].(*ContainerNode).String()).Equal(NodeShell)
g.Assert(out.Script[2].(*ContainerNode).Container.Name).Equal("notify")
g.Assert(out.Script[2].(*ContainerNode).Container.Image).Equal("slack")
g.Assert(out.Script[2].(*ContainerNode).String()).Equal(NodePlugin)
})
})
})
}
var sampleYaml = `
image: hello-world
build:
context: .
dockerfile: Dockerfile
workspace:
path: src/github.com/octocat/hello-world
base: /go
clone:
image: git
depth: 1
cache:
mount: node_modules
script:
test:
image: golang
commands:
- go install
- go test
build:
image: golang
commands:
- go build
when:
event: push
notify:
image: slack
channel: dev
when:
event: failure
services:
database:
image: mysql
networks:
custom:
driver: overlay
volumes:
custom:
driver: blockbridge
`

View file

@ -0,0 +1,55 @@
package parse
import "strings"
// mapEqualSlice represents a map[string]string or a slice of
// strings in key=value format.
type mapEqualSlice struct {
parts map[string]string
}
func (s *mapEqualSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
s.parts = map[string]string{}
err := unmarshal(&s.parts)
if err == nil {
return nil
}
var slice []string
err = unmarshal(&slice)
if err != nil {
return err
}
for _, v := range slice {
parts := strings.SplitN(v, "=", 2)
if len(parts) == 2 {
key := parts[0]
val := parts[1]
s.parts[key] = val
}
}
return nil
}
// stringOrSlice represents a string or an array of strings.
type stringOrSlice struct {
parts []string
}
func (s *stringOrSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
var sliceType []string
err := unmarshal(&sliceType)
if err == nil {
s.parts = sliceType
return nil
}
var stringType string
err = unmarshal(&stringType)
if err == nil {
sliceType = make([]string, 0, 1)
s.parts = append(sliceType, string(stringType))
return nil
}
return err
}

View file

@ -0,0 +1,75 @@
package parse
import (
"testing"
"github.com/franela/goblin"
"gopkg.in/yaml.v2"
)
func TestTypes(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Yaml types", func() {
g.Describe("given a yaml file", func() {
g.It("should unmarshal a string", func() {
in := []byte("foo")
out := stringOrSlice{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.parts)).Equal(1)
g.Assert(out.parts[0]).Equal("foo")
})
g.It("should unmarshal a string slice", func() {
in := []byte("[ foo ]")
out := stringOrSlice{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.parts)).Equal(1)
g.Assert(out.parts[0]).Equal("foo")
})
g.It("should throw error when invalid string slice", func() {
in := []byte("{ }") // string value should fail parse
out := stringOrSlice{}
err := yaml.Unmarshal(in, &out)
g.Assert(err != nil).IsTrue("expects error")
})
g.It("should unmarshal a map", func() {
in := []byte("foo: bar")
out := mapEqualSlice{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.parts)).Equal(1)
g.Assert(out.parts["foo"]).Equal("bar")
})
g.It("should unmarshal a map equal slice", func() {
in := []byte("[ foo=bar ]")
out := mapEqualSlice{}
err := yaml.Unmarshal(in, &out)
if err != nil {
g.Fail(err)
}
g.Assert(len(out.parts)).Equal(1)
g.Assert(out.parts["foo"]).Equal("bar")
})
g.It("should throw error when invalid map equal slice", func() {
in := []byte("foo") // string value should fail parse
out := mapEqualSlice{}
err := yaml.Unmarshal(in, &out)
g.Assert(err != nil).IsTrue("expects error")
})
})
})
}

View file

@ -0,0 +1,13 @@
package libyaml
import "github.com/drone/drone/engine/compiler/parse"
// Transform is used to transform nodes from the parsed Yaml file during the
// compilation process. A Transform may be used to add, disable or alter nodes.
type Transform interface {
VisitRoot(*parse.RootNode) error
VisitVolume(*parse.VolumeNode) error
VisitNetwork(*parse.NetworkNode) error
VisitBuild(*parse.BuildNode) error
VisitContainer(*parse.ContainerNode) error
}

View file

@ -0,0 +1,72 @@
package runner
import "fmt"
// Container defines the container configuration.
type Container struct {
Name string `json:"name"`
Alias string `json:"alias"`
Image string `json:"image"`
Pull bool `json:"pull,omitempty"`
AuthConfig Auth `json:"auth_config,omitempty"`
Privileged bool `json:"privileged,omitempty"`
WorkingDir string `json:"working_dir,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
Entrypoint []string `json:"entrypoint,omitempty"`
Command []string `json:"command,omitempty"`
ExtraHosts []string `json:"extra_hosts,omitempty"`
Volumes []string `json:"volumes,omitempty"`
VolumesFrom []string `json:"volumes_from,omitempty"`
Devices []string `json:"devices,omitempty"`
Network string `json:"network_mode,omitempty"`
DNS []string `json:"dns,omitempty"`
DNSSearch []string `json:"dns_search,omitempty"`
MemSwapLimit int64 `json:"memswap_limit,omitempty"`
MemLimit int64 `json:"mem_limit,omitempty"`
CPUQuota int64 `json:"cpu_quota,omitempty"`
CPUShares int64 `json:"cpu_shares,omitempty"`
CPUSet string `json:"cpuset,omitempty"`
OomKillDisable bool `json:"oom_kill_disable,omitempty"`
}
// Validate validates the container configuration details and returns an error
// if the validation fails.
func (c *Container) Validate() error {
switch {
case c.Name == "":
return fmt.Errorf("Missing container name")
case c.Image == "":
return fmt.Errorf("Missing container image")
default:
return nil
}
}
// Auth provides authentication parameters to authenticate to a remote
// container registry for image download.
type Auth struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Email string `json:"email,omitempty"`
Token string `json:"registry_token,omitempty"`
}
// Volume defines a container volume.
type Volume struct {
Name string `json:"name,omitempty"`
Alias string `json:"alias,omitempty"`
Driver string `json:"driver,omitempty"`
DriverOpts map[string]string `json:"driver_opts,omitempty"`
External bool `json:"external,omitempty"`
}
// Network defines a container network.
type Network struct {
Name string `json:"name,omitempty"`
Alias string `json:"alias,omitempty"`
Driver string `json:"driver,omitempty"`
DriverOpts map[string]string `json:"driver_opts,omitempty"`
External bool `json:"external,omitempty"`
}

View file

@ -0,0 +1,40 @@
package runner
import (
"testing"
"github.com/franela/goblin"
)
func TestContainer(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Container validation", func() {
g.It("fails with an invalid name", func() {
c := Container{
Image: "golang:1.5",
}
err := c.Validate()
g.Assert(err != nil).IsTrue()
g.Assert(err.Error()).Equal("Missing container name")
})
g.It("fails with an invalid image", func() {
c := Container{
Name: "container_0",
}
err := c.Validate()
g.Assert(err != nil).IsTrue()
g.Assert(err.Error()).Equal("Missing container image")
})
g.It("passes with valid attributes", func() {
c := Container{
Name: "container_0",
Image: "golang:1.5",
}
g.Assert(c.Validate() == nil).IsTrue()
})
})
}

22
engine/runner/engine.go Normal file
View file

@ -0,0 +1,22 @@
package runner
//go:generate mockery -name Engine -output mock -case=underscore
import "io"
// Engine defines the container runtime engine.
type Engine interface {
// VolumeCreate(*Volume) (string, error)
// VolumeRemove(string) error
ContainerStart(*Container) (string, error)
ContainerStop(string) error
ContainerRemove(string) error
ContainerWait(string) (*State, error)
ContainerLogs(string) (io.ReadCloser, error)
}
// State defines the state of the container.
type State struct {
ExitCode int // container exit code
OOMKilled bool // container exited due to oom error
}

37
engine/runner/error.go Normal file
View file

@ -0,0 +1,37 @@
package runner
import (
"errors"
"fmt"
)
var (
// ErrSkip is used as a return value when container execution should be
// skipped at runtime. It is not returned as an error by any function.
ErrSkip = errors.New("Skip")
// ErrTerm is used as a return value when the runner should terminate
// execution and exit. It is not returned as an error by any function.
ErrTerm = errors.New("Terminate")
)
// An ExitError reports an unsuccessful exit.
type ExitError struct {
Name string
Code int
}
// Error reteurns the error message in string format.
func (e *ExitError) Error() string {
return fmt.Sprintf("%s : exit code %d", e.Name, e.Code)
}
// An OomError reports the process received an OOMKill from the kernel.
type OomError struct {
Name string
}
// Error reteurns the error message in string format.
func (e *OomError) Error() string {
return fmt.Sprintf("%s : received oom kill", e.Name)
}

View file

@ -0,0 +1,26 @@
package runner
import (
"testing"
"github.com/franela/goblin"
)
func TestErrors(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Error messages", func() {
g.It("should include OOM details", func() {
err := OomError{Name: "golang"}
got, want := err.Error(), "golang : received oom kill"
g.Assert(got).Equal(want)
})
g.It("should include Exit code", func() {
err := ExitError{Name: "golang", Code: 255}
got, want := err.Error(), "golang : exit code 255"
g.Assert(got).Equal(want)
})
})
}

24
engine/runner/helper.go Normal file
View file

@ -0,0 +1,24 @@
package runner
import (
"encoding/json"
"io/ioutil"
)
// Parse parses a raw file containing a JSON encoded format of an intermediate
// representation of the pipeline.
func Parse(data []byte) (*Spec, error) {
v := &Spec{}
err := json.Unmarshal(data, v)
return v, err
}
// ParseFile parses a file containing a JSON encoded format of an intermediate
// representation of the pipeline.
func ParseFile(filename string) (*Spec, error) {
out, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return Parse(out)
}

View file

@ -0,0 +1,97 @@
package runner
import (
"io/ioutil"
"os"
"testing"
"github.com/franela/goblin"
)
func TestHelper(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Parsing", func() {
g.It("should unmarhsal file []byte", func() {
res, err := Parse(sample)
if err != nil {
t.Error(err)
return
}
g.Assert(err == nil).IsTrue("expect file parsed")
g.Assert(len(res.Containers)).Equal(2)
g.Assert(len(res.Volumes)).Equal(1)
})
g.It("should unmarshal from file", func() {
temp, _ := ioutil.TempFile("", "spec_")
defer os.Remove(temp.Name())
ioutil.WriteFile(temp.Name(), sample, 0700)
_, err := ParseFile(temp.Name())
if err != nil {
t.Error(err)
return
}
g.Assert(err == nil).IsTrue("expect file parsed")
})
g.It("should error when file not found", func() {
_, err := ParseFile("/tmp/foo/bar/dummy/file.json")
g.Assert(err == nil).IsFalse("expect file not found error")
})
})
}
// invalid json representation, simulate parsing error
var invalid = []byte(`[]`)
// valid json representation, verify parsing
var sample = []byte(`{
"containers": [
{
"name": "container_0",
"image": "node:latest"
},
{
"name": "container_1",
"image": "golang:latest"
}
],
"volumes": [
{
"name": "volume_0"
}
],
"program": {
"type": "list",
"body": [
{
"type": "defer",
"body": {
"type": "recover",
"body": {
"type": "run",
"name": "container_0"
}
},
"defer": {
"type": "parallel",
"body": [
{
"type": "run",
"name": "container_1"
},
{
"type": "run",
"name": "container_1"
}
],
"limit": 2
}
}
]
}
}`)

View file

@ -0,0 +1,30 @@
package parse
const (
NodeList = "list"
NodeDefer = "defer"
NodeError = "error"
NodeRecover = "recover"
NodeParallel = "parallel"
NodeRun = "run"
)
// NodeType identifies the type of a parse tree node.
type NodeType string
// Type returns itself and provides an easy default implementation
// for embedding in a Node. Embedded in all non-trivial Nodes.
func (t NodeType) Type() NodeType {
return t
}
// String returns the string value of the Node type.
func (t NodeType) String() string {
return string(t)
}
// A Node is an element in the parse tree.
type Node interface {
Type() NodeType
Validate() error
}

View file

@ -0,0 +1,40 @@
package parse
import "fmt"
// DeferNode executes the child node, and then executes the deffered node.
// The deffered node is guaranteed to execute, even when the child node fails.
type DeferNode struct {
NodeType `json:"type"`
Body Node `json:"body"` // evaluate node
Defer Node `json:"defer"` // defer evaluation of node.
}
// NewDeferNode returns a new DeferNode.
func NewDeferNode() *DeferNode {
return &DeferNode{NodeType: NodeDefer}
}
func (n *DeferNode) SetBody(node Node) *DeferNode {
n.Body = node
return n
}
func (n *DeferNode) SetDefer(node Node) *DeferNode {
n.Defer = node
return n
}
func (n *DeferNode) Validate() error {
switch {
case n.NodeType != NodeDefer:
return fmt.Errorf("Defer Node uses an invalid type")
case n.Body == nil:
return fmt.Errorf("Defer Node body is empty")
case n.Defer == nil:
return fmt.Errorf("Defer Node defer is empty")
default:
return nil
}
}

View file

@ -0,0 +1,56 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestDeferNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("DeferNode", func() {
g.It("should set body and defer node", func() {
node0 := NewRunNode()
node1 := NewRunNode()
defer0 := NewDeferNode()
defer1 := defer0.SetBody(node0)
defer2 := defer0.SetDefer(node1)
g.Assert(defer0.Type().String()).Equal(NodeDefer)
g.Assert(defer0.Body).Equal(node0)
g.Assert(defer0.Defer).Equal(node1)
g.Assert(defer0).Equal(defer1)
g.Assert(defer0).Equal(defer2)
})
g.It("should fail validation when invalid type", func() {
defer0 := DeferNode{}
err := defer0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Defer Node uses an invalid type")
})
g.It("should fail validation when empty body", func() {
defer0 := NewDeferNode()
err := defer0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Defer Node body is empty")
})
g.It("should fail validation when empty defer", func() {
defer0 := NewDeferNode()
defer0.SetBody(NewRunNode())
err := defer0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Defer Node defer is empty")
})
g.It("should pass validation", func() {
defer0 := NewDeferNode()
defer0.SetBody(NewRunNode())
defer0.SetDefer(NewRunNode())
g.Assert(defer0.Validate() == nil).IsTrue()
})
})
}

View file

@ -0,0 +1,40 @@
package parse
import "fmt"
// ErrorNode executes the body node, and then executes the error node if
// the body node errors. This is similar to defer but only executes on error.
type ErrorNode struct {
NodeType `json:"type"`
Body Node `json:"body"` // evaluate node
Defer Node `json:"defer"` // defer evaluation of node on error.
}
// NewErrorNode returns a new ErrorNode.
func NewErrorNode() *ErrorNode {
return &ErrorNode{NodeType: NodeError}
}
func (n *ErrorNode) SetBody(node Node) *ErrorNode {
n.Body = node
return n
}
func (n *ErrorNode) SetDefer(node Node) *ErrorNode {
n.Defer = node
return n
}
func (n *ErrorNode) Validate() error {
switch {
case n.NodeType != NodeError:
return fmt.Errorf("Error Node uses an invalid type")
case n.Body == nil:
return fmt.Errorf("Error Node body is empty")
case n.Defer == nil:
return fmt.Errorf("Error Node defer is empty")
default:
return nil
}
}

View file

@ -0,0 +1,56 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestErrorNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("ErrorNode", func() {
g.It("should set body and error node", func() {
node0 := NewRunNode()
node1 := NewRunNode()
error0 := NewErrorNode()
error1 := error0.SetBody(node0)
error2 := error0.SetDefer(node1)
g.Assert(error0.Type().String()).Equal(NodeError)
g.Assert(error0.Body).Equal(node0)
g.Assert(error0.Defer).Equal(node1)
g.Assert(error0).Equal(error1)
g.Assert(error0).Equal(error2)
})
g.It("should fail validation when invalid type", func() {
error0 := ErrorNode{}
err := error0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Error Node uses an invalid type")
})
g.It("should fail validation when empty body", func() {
error0 := NewErrorNode()
err := error0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Error Node body is empty")
})
g.It("should fail validation when empty error", func() {
error0 := NewErrorNode()
error0.SetBody(NewRunNode())
err := error0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Error Node defer is empty")
})
g.It("should pass validation", func() {
error0 := NewErrorNode()
error0.SetBody(NewRunNode())
error0.SetDefer(NewRunNode())
g.Assert(error0.Validate() == nil).IsTrue()
})
})
}

View file

@ -0,0 +1,33 @@
package parse
import "fmt"
// ListNode serially executes a list of child nodes.
type ListNode struct {
NodeType `json:"type"`
// Body is the list of child nodes
Body []Node `json:"body"`
}
// NewListNode returns a new ListNode.
func NewListNode() *ListNode {
return &ListNode{NodeType: NodeList}
}
// Append appens a child node to the list.
func (n *ListNode) Append(node Node) *ListNode {
n.Body = append(n.Body, node)
return n
}
func (n *ListNode) Validate() error {
switch {
case n.NodeType != NodeList:
return fmt.Errorf("List Node uses an invalid type")
case len(n.Body) == 0:
return fmt.Errorf("List Node body is empty")
default:
return nil
}
}

View file

@ -0,0 +1,44 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestListNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("ListNode", func() {
g.It("should append nodes", func() {
node := NewRunNode()
list0 := NewListNode()
list1 := list0.Append(node)
g.Assert(list0.Type().String()).Equal(NodeList)
g.Assert(list0.Body[0]).Equal(node)
g.Assert(list0).Equal(list1)
})
g.It("should fail validation when invalid type", func() {
list := ListNode{}
err := list.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("List Node uses an invalid type")
})
g.It("should fail validation when empty body", func() {
list := NewListNode()
err := list.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("List Node body is empty")
})
g.It("should pass validation", func() {
node := NewRunNode()
list := NewListNode()
list.Append(node)
g.Assert(list.Validate() == nil).IsTrue()
})
})
}

View file

@ -0,0 +1,36 @@
package parse
import "fmt"
// ParallelNode executes a list of child nodes in parallel.
type ParallelNode struct {
NodeType `json:"type"`
Body []Node `json:"body"` // nodes for parallel evaluation.
Limit int `json:"limit"` // limit for parallel evaluation.
}
func NewParallelNode() *ParallelNode {
return &ParallelNode{NodeType: NodeParallel}
}
func (n *ParallelNode) Append(node Node) *ParallelNode {
n.Body = append(n.Body, node)
return n
}
func (n *ParallelNode) SetLimit(limit int) *ParallelNode {
n.Limit = limit
return n
}
func (n *ParallelNode) Validate() error {
switch {
case n.NodeType != NodeParallel:
return fmt.Errorf("Parallel Node uses an invalid type")
case len(n.Body) == 0:
return fmt.Errorf("Parallel Node body is empty")
default:
return nil
}
}

View file

@ -0,0 +1,42 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestParallelNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("ParallelNode", func() {
g.It("should append nodes", func() {
node := NewRunNode()
parallel0 := NewParallelNode()
parallel1 := parallel0.Append(node)
g.Assert(parallel0.Type().String()).Equal(NodeParallel)
g.Assert(parallel0.Body[0]).Equal(node)
g.Assert(parallel0).Equal(parallel1)
})
g.It("should fail validation when invalid type", func() {
node := ParallelNode{}
err := node.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Parallel Node uses an invalid type")
})
g.It("should fail validation when empty body", func() {
node := NewParallelNode()
err := node.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Parallel Node body is empty")
})
g.It("should pass validation", func() {
node := NewParallelNode().Append(NewRunNode())
g.Assert(node.Validate() == nil).IsTrue()
})
})
}

View file

@ -0,0 +1,29 @@
package parse
import "fmt"
type RecoverNode struct {
NodeType `json:"type"`
Body Node `json:"body"` // evaluate node and catch all errors.
}
func NewRecoverNode() *RecoverNode {
return &RecoverNode{NodeType: NodeRecover}
}
func (n *RecoverNode) SetBody(node Node) *RecoverNode {
n.Body = node
return n
}
func (n *RecoverNode) Validate() error {
switch {
case n.NodeType != NodeRecover:
return fmt.Errorf("Recover Node uses an invalid type")
case n.Body == nil:
return fmt.Errorf("Recover Node body is empty")
default:
return nil
}
}

View file

@ -0,0 +1,43 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestRecoverNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("RecoverNode", func() {
g.It("should set body", func() {
node0 := NewRunNode()
recover0 := NewRecoverNode()
recover1 := recover0.SetBody(node0)
g.Assert(recover0.Type().String()).Equal(NodeRecover)
g.Assert(recover0.Body).Equal(node0)
g.Assert(recover0).Equal(recover1)
})
g.It("should fail validation when invalid type", func() {
recover0 := RecoverNode{}
err := recover0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Recover Node uses an invalid type")
})
g.It("should fail validation when empty body", func() {
recover0 := NewRecoverNode()
err := recover0.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Recover Node body is empty")
})
g.It("should pass validation", func() {
recover0 := NewRecoverNode()
recover0.SetBody(NewRunNode())
g.Assert(recover0.Validate() == nil).IsTrue()
})
})
}

View file

@ -0,0 +1,41 @@
package parse
import "fmt"
type RunNode struct {
NodeType `json:"type"`
Name string `json:"name"`
Detach bool `json:"detach,omitempty"`
Silent bool `json:"silent,omitempty"`
}
func (n *RunNode) SetName(name string) *RunNode {
n.Name = name
return n
}
func (n *RunNode) SetDetach(detach bool) *RunNode {
n.Detach = detach
return n
}
func (n *RunNode) SetSilent(silent bool) *RunNode {
n.Silent = silent
return n
}
func NewRunNode() *RunNode {
return &RunNode{NodeType: NodeRun}
}
func (n *RunNode) Validate() error {
switch {
case n.NodeType != NodeRun:
return fmt.Errorf("Run Node uses an invalid type")
case n.Name == "":
return fmt.Errorf("Run Node has an invalid name")
default:
return nil
}
}

View file

@ -0,0 +1,41 @@
package parse
import (
"testing"
"github.com/franela/goblin"
)
func TestRunNode(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("RunNode", func() {
g.It("should set container name for lookup", func() {
node0 := NewRunNode()
node1 := node0.SetName("foo")
g.Assert(node0.Type().String()).Equal(NodeRun)
g.Assert(node0.Name).Equal("foo")
g.Assert(node0).Equal(node1)
})
g.It("should fail validation when invalid type", func() {
node := RunNode{}
err := node.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Run Node uses an invalid type")
})
g.It("should fail validation when invalid name", func() {
node := NewRunNode()
err := node.Validate()
g.Assert(err == nil).IsFalse()
g.Assert(err.Error()).Equal("Run Node has an invalid name")
})
g.It("should pass validation", func() {
node := NewRunNode().SetName("foo")
g.Assert(node.Validate() == nil).IsTrue()
})
})
}

View file

@ -0,0 +1,221 @@
package parse
import "encoding/json"
// Tree is the intermediate representation of a pipeline.
type Tree struct {
*ListNode // top-level Tree node
}
// New allocates a new Tree.
func NewTree() *Tree {
return &Tree{
NewListNode(),
}
}
// Parse parses a JSON encoded Tree.
func Parse(data []byte) (*Tree, error) {
tree := &Tree{}
err := tree.UnmarshalJSON(data)
return tree, err
}
// MarshalJSON implements the Marshaler interface and returns
// a JSON encoded representation of the Tree.
func (t *Tree) MarshalJSON() ([]byte, error) {
return json.Marshal(t.ListNode)
}
// UnmarshalJSON implements the Unmarshaler interface and returns
// a Tree from a JSON representation.
func (t *Tree) UnmarshalJSON(data []byte) error {
block, err := decodeList(data)
if err != nil {
return nil
}
t.ListNode = block.(*ListNode)
return nil
}
//
// below are custom decoding functions. We cannot use the default json
// decoder because the tree structure uses interfaces and the json decoder
// has difficulty ascertaining the interface type when decoding.
//
func decodeNode(data []byte) (Node, error) {
node := &nodeType{}
err := json.Unmarshal(data, node)
if err != nil {
return nil, err
}
switch node.Type {
case NodeList:
return decodeList(data)
case NodeDefer:
return decodeDefer(data)
case NodeError:
return decodeError(data)
case NodeRecover:
return decodeRecover(data)
case NodeParallel:
return decodeParallel(data)
case NodeRun:
return decodeRun(data)
}
return nil, nil
}
func decodeNodes(data []json.RawMessage) ([]Node, error) {
var nodes []Node
for _, d := range data {
node, err := decodeNode(d)
if err != nil {
return nil, err
}
nodes = append(nodes, node)
}
return nodes, nil
}
func decodeList(data []byte) (Node, error) {
v := &nodeList{}
err := json.Unmarshal(data, v)
if err != nil {
return nil, err
}
b, err := decodeNodes(v.Body)
if err != nil {
return nil, err
}
n := NewListNode()
n.Body = b
return n, nil
}
func decodeDefer(data []byte) (Node, error) {
v := &nodeDefer{}
err := json.Unmarshal(data, v)
if err != nil {
return nil, err
}
b, err := decodeNode(v.Body)
if err != nil {
return nil, err
}
d, err := decodeNode(v.Defer)
if err != nil {
return nil, err
}
n := NewDeferNode()
n.Body = b
n.Defer = d
return n, nil
}
func decodeError(data []byte) (Node, error) {
v := &nodeError{}
err := json.Unmarshal(data, v)
if err != nil {
return nil, err
}
b, err := decodeNode(v.Body)
if err != nil {
return nil, err
}
d, err := decodeNode(v.Defer)
if err != nil {
return nil, err
}
n := NewErrorNode()
n.Body = b
n.Defer = d
return n, nil
}
func decodeRecover(data []byte) (Node, error) {
v := &nodeRecover{}
err := json.Unmarshal(data, v)
if err != nil {
return nil, err
}
b, err := decodeNode(v.Body)
if err != nil {
return nil, err
}
n := NewRecoverNode()
n.Body = b
return n, nil
}
func decodeParallel(data []byte) (Node, error) {
v := &nodeParallel{}
err := json.Unmarshal(data, v)
if err != nil {
return nil, err
}
b, err := decodeNodes(v.Body)
if err != nil {
return nil, err
}
n := NewParallelNode()
n.Body = b
n.Limit = v.Limit
return n, nil
}
func decodeRun(data []byte) (Node, error) {
v := &nodeRun{}
err := json.Unmarshal(data, v)
if err != nil {
return nil, err
}
return &RunNode{NodeRun, v.Name, v.Detach, v.Silent}, nil
}
//
// below are intermediate representations of the node structures
// since we cannot simply encode / decode using the built-in json
// encoding and decoder.
//
type nodeType struct {
Type NodeType `json:"type"`
}
type nodeDefer struct {
Type NodeType `json:"type"`
Body json.RawMessage `json:"body"`
Defer json.RawMessage `json:"defer"`
}
type nodeError struct {
Type NodeType `json:"type"`
Body json.RawMessage `json:"body"`
Defer json.RawMessage `json:"defer"`
}
type nodeList struct {
Type NodeType `json:"type"`
Body []json.RawMessage `json:"body"`
}
type nodeRecover struct {
Type NodeType `json:"type"`
Body json.RawMessage `json:"body"`
}
type nodeParallel struct {
Type NodeType `json:"type"`
Body []json.RawMessage `json:"body"`
Limit int `json:"limit"`
}
type nodeRun struct {
Type NodeType `json:"type"`
Name string `json:"name"`
Detach bool `json:"detach,omitempty"`
Silent bool `json:"silent,omitempty"`
}

View file

@ -0,0 +1,80 @@
package parse
import (
"bytes"
"encoding/json"
"reflect"
"testing"
)
func TestUnmarshal(t *testing.T) {
node1 := NewRunNode().SetName("foo")
node2 := NewRecoverNode().SetBody(node1)
node3 := NewRunNode().SetName("bar")
node4 := NewRunNode().SetName("bar")
node5 := NewParallelNode().
Append(node3).
Append(node4).
SetLimit(2)
node6 := NewDeferNode().
SetBody(node2).
SetDefer(node5)
tree := NewTree()
tree.Append(node6)
encoded, err := json.MarshalIndent(tree, "", "\t")
if err != nil {
t.Error(err)
}
if !bytes.Equal(encoded, sample) {
t.Errorf("Want to marshal Tree to %s, got %s",
string(sample),
string(encoded),
)
}
parsed, err := Parse(encoded)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(tree, parsed) {
t.Errorf("Want to marsnal and then unmarshal Tree")
}
}
var sample = []byte(`{
"type": "list",
"body": [
{
"type": "defer",
"body": {
"type": "recover",
"body": {
"type": "run",
"name": "foo"
}
},
"defer": {
"type": "parallel",
"body": [
{
"type": "run",
"name": "bar"
},
{
"type": "run",
"name": "bar"
}
],
"limit": 2
}
}
]
}`)

49
engine/runner/pipe.go Normal file
View file

@ -0,0 +1,49 @@
package runner
import "fmt"
// Pipe returns a buffered pipe that is connected to the console output.
type Pipe struct {
lines chan *Line
eof chan bool
}
// Next returns the next Line of console output.
func (p *Pipe) Next() *Line {
select {
case line := <-p.lines:
return line
case <-p.eof:
return nil
}
}
// Close closes the pipe of console output.
func (p *Pipe) Close() {
go func() {
p.eof <- true
}()
}
func newPipe(buffer int) *Pipe {
return &Pipe{
lines: make(chan *Line, buffer),
eof: make(chan bool),
}
}
// Line is a line of console output.
type Line struct {
Proc string `json:"proc,omitempty"`
Time int64 `json:"time,omitempty"`
Type int `json:"type,omitempty"`
Pos int `json:"pos,omityempty"`
Out string `json:"out,omitempty"`
}
func (l *Line) String() string {
return fmt.Sprintf("[%s:L%v:%vs] %s", l.Proc, l.Pos, l.Time, l.Out)
}
// TODO(bradrydzewski) consider an alternate buffer impelmentation based on the
// x.crypto ssh buffer https://github.com/golang/crypto/blob/master/ssh/buffer.go

View file

@ -0,0 +1,54 @@
package runner
import (
"sync"
"testing"
"github.com/franela/goblin"
)
func TestPipe(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Pipe", func() {
g.It("should get next line from buffer", func() {
line := &Line{
Proc: "redis",
Pos: 1,
Out: "starting redis server",
}
pipe := newPipe(10)
pipe.lines <- line
next := pipe.Next()
g.Assert(next).Equal(line)
})
g.It("should get null line on buffer closed", func() {
pipe := newPipe(10)
var wg sync.WaitGroup
wg.Add(1)
go func() {
next := pipe.Next()
g.Assert(next == nil).IsTrue("line should be nil")
wg.Done()
}()
pipe.Close()
wg.Wait()
})
g.Describe("Line output", func() {
g.It("should prefix string() with metadata", func() {
line := Line{
Proc: "redis",
Time: 60,
Pos: 1,
Out: "starting redis server",
}
g.Assert(line.String()).Equal("[redis:L1:60s] starting redis server")
})
})
})
}

247
engine/runner/runner.go Normal file
View file

@ -0,0 +1,247 @@
package runner
import (
"bufio"
"time"
"fmt"
"github.com/drone/drone/engine/runner/parse"
"golang.org/x/net/context"
)
// NoContext is the default context you should supply if not using your own
// context.Context
var NoContext = context.TODO()
// Tracer defines a tracing function that is invoked prior to creating and
// running the container.
type Tracer func(c *Container) error
// Config defines the configuration for creating the Runner.
type Config struct {
Tracer Tracer
Engine Engine
// Buffer defines the size of the buffer for the channel to which the
// console output is streamed.
Buffer uint
}
// Runner creates a build Runner using the specific configuration for the given
// Context and Specification.
func (c *Config) Runner(ctx context.Context, spec *Spec) *Runner {
// TODO(bradyrdzewski) we should make a copy of the configuration parameters
// instead of a direct reference. This helps avoid any race conditions or
//unexpected behavior if the Config changes.
return &Runner{
ctx: ctx,
conf: c,
spec: spec,
errc: make(chan error),
pipe: newPipe(int(c.Buffer) + 1),
}
}
type Runner struct {
ctx context.Context
conf *Config
spec *Spec
pipe *Pipe
errc chan (error)
containers []string
volumes []string
networks []string
}
// Run starts the build runner but does not wait for it to complete. The Wait
// method will return the exit code and release associated resources once the
// running containers exit.
func (r *Runner) Run() error {
go func() {
r.setup()
err := r.exec(r.spec.Nodes.ListNode)
r.pipe.Close()
r.cancel()
r.teardown()
r.errc <- err
}()
go func() {
<-r.ctx.Done()
r.cancel()
}()
return nil
}
// Wait waits for the runner to exit.
func (r *Runner) Wait() error {
return <-r.errc
}
// Pipe returns a Pipe that is connected to the console output stream.
func (r *Runner) Pipe() *Pipe {
return r.pipe
}
func (r *Runner) exec(node parse.Node) error {
switch v := node.(type) {
case *parse.ListNode:
return r.execList(v)
case *parse.DeferNode:
return r.execDefer(v)
case *parse.ErrorNode:
return r.execError(v)
case *parse.RecoverNode:
return r.execRecover(v)
case *parse.ParallelNode:
return r.execParallel(v)
case *parse.RunNode:
return r.execRun(v)
}
return fmt.Errorf("runner: unexepected node %s", node)
}
func (r *Runner) execList(node *parse.ListNode) error {
for _, n := range node.Body {
err := r.exec(n)
if err != nil {
return err
}
}
return nil
}
func (r *Runner) execDefer(node *parse.DeferNode) error {
err1 := r.exec(node.Body)
err2 := r.exec(node.Defer)
if err1 != nil {
return err1
}
return err2
}
func (r *Runner) execError(node *parse.ErrorNode) error {
err := r.exec(node.Body)
if err != nil {
r.exec(node.Defer)
}
return err
}
func (r *Runner) execRecover(node *parse.RecoverNode) error {
r.exec(node.Body)
return nil
}
func (r *Runner) execParallel(node *parse.ParallelNode) error {
errc := make(chan error)
for _, n := range node.Body {
go func(node parse.Node) {
errc <- r.exec(node)
}(n)
}
var err error
for i := 0; i < len(node.Body); i++ {
select {
case cerr := <-errc:
if cerr != nil {
err = cerr
}
}
}
return err
}
func (r *Runner) execRun(node *parse.RunNode) error {
container, err := r.spec.lookupContainer(node.Name)
if err != nil {
return err
}
if r.conf.Tracer != nil {
err := r.conf.Tracer(container)
switch {
case err == ErrSkip:
return nil
case err != nil:
return err
}
}
// TODO(bradrydzewski) there is potential here for a race condition where
// the context is cancelled just after this line, resulting in the container
// still being started.
if r.ctx.Err() != nil {
return err
}
name, err := r.conf.Engine.ContainerStart(container)
if err != nil {
return err
}
r.containers = append(r.containers, name)
go func() {
if node.Silent {
return
}
rc, err := r.conf.Engine.ContainerLogs(name)
if err != nil {
return
}
defer rc.Close()
num := 0
now := time.Now().UTC()
scanner := bufio.NewScanner(rc)
for scanner.Scan() {
r.pipe.lines <- &Line{
Proc: container.Alias,
Time: int64(time.Since(now).Seconds()),
Pos: num,
Out: scanner.Text(),
}
num++
}
}()
// exit when running container in detached mode in background
if node.Detach {
return nil
}
state, err := r.conf.Engine.ContainerWait(name)
if err != nil {
return err
}
if state.OOMKilled {
return &OomError{name}
} else if state.ExitCode != 0 {
return &ExitError{name, state.ExitCode}
}
return nil
}
func (r *Runner) setup() {
// this is where we will setup network and volumes
}
func (r *Runner) teardown() {
// TODO(bradrydzewski) this is not yet thread safe.
for _, container := range r.containers {
r.conf.Engine.ContainerRemove(container)
}
}
func (r *Runner) cancel() {
// TODO(bradrydzewski) this is not yet thread safe.
for _, container := range r.containers {
r.conf.Engine.ContainerStop(container)
}
}

View file

@ -0,0 +1,7 @@
package runner
import "testing"
func TestRunner(t *testing.T) {
t.Skip()
}

33
engine/runner/spec.go Normal file
View file

@ -0,0 +1,33 @@
package runner
import (
"fmt"
"github.com/drone/drone/engine/runner/parse"
)
// Spec defines the pipeline configuration and exeuction.
type Spec struct {
// Volumes defines a list of all container volumes.
Volumes []*Volume `json:"volumes,omitempty"`
// Networks defines a list of all container networks.
Networks []*Network `json:"networks,omitempty"`
// Containers defines a list of all containers in the pipeline.
Containers []*Container `json:"containers,omitempty"`
// Nodes defines the container execution tree.
Nodes *parse.Tree `json:"program,omitempty"`
}
// lookupContainer is a helper funciton that returns the named container from
// the slice of containers.
func (s *Spec) lookupContainer(name string) (*Container, error) {
for _, container := range s.Containers {
if container.Name == name {
return container, nil
}
}
return nil, fmt.Errorf("runner: unknown container %s", name)
}

View file

@ -0,0 +1,35 @@
package runner
import (
"testing"
"github.com/franela/goblin"
)
func TestSpec(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Spec file", func() {
g.Describe("when looking up a container", func() {
spec := Spec{}
spec.Containers = append(spec.Containers, &Container{
Name: "golang",
})
g.It("should find and return the container", func() {
c, err := spec.lookupContainer("golang")
g.Assert(err == nil).IsTrue("error should be nil")
g.Assert(c).Equal(spec.Containers[0])
})
g.It("should return an error when not found", func() {
c, err := spec.lookupContainer("node")
g.Assert(err == nil).IsFalse("should return error")
g.Assert(c == nil).IsTrue("should return nil container")
})
})
})
}

72
engine/shasum/shasum.go Normal file
View file

@ -0,0 +1,72 @@
package shasum
import (
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"io"
"strings"
)
// Check is a calculates and verifies a file checksum.
// This supports the sha1, sha256 and sha512 values.
func Check(in, checksum string) bool {
hash, size, _ := split(checksum)
// if a byte size is provided for the
// Yaml file it must match.
if size > 0 && int64(len(in)) != size {
return false
}
switch len(hash) {
case 64:
return sha256sum(in) == hash
case 128:
return sha512sum(in) == hash
case 40:
return sha1sum(in) == hash
case 0:
return true // if no checksum assume valid
}
return false
}
func sha1sum(in string) string {
h := sha1.New()
io.WriteString(h, in)
return fmt.Sprintf("%x", h.Sum(nil))
}
func sha256sum(in string) string {
h := sha256.New()
io.WriteString(h, in)
return fmt.Sprintf("%x", h.Sum(nil))
}
func sha512sum(in string) string {
h := sha512.New()
io.WriteString(h, in)
return fmt.Sprintf("%x", h.Sum(nil))
}
func split(in string) (string, int64, string) {
var hash string
var name string
var size int64
// the checksum might be split into multiple
// sections including the file size and name.
switch strings.Count(in, " ") {
case 1:
fmt.Sscanf(in, "%s %s", &hash, &name)
case 2:
fmt.Sscanf(in, "%s %d %s", &hash, &size, &name)
default:
hash = in
}
return hash, size, name
}

View file

@ -0,0 +1,97 @@
package shasum
import (
"testing"
"github.com/franela/goblin"
)
func TestParse(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Shasum", func() {
g.It("Should parse the shasum string", func() {
hash, _, _ := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
})
g.It("Should parse a two-part shasum string", func() {
hash, _, name := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 .drone.yml")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
g.Assert(name).Equal(".drone.yml")
})
g.It("Should parse a three-part shasum string", func() {
hash, size, name := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 42 .drone.yml")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
g.Assert(name).Equal(".drone.yml")
g.Assert(size).Equal(int64(42))
})
g.It("Should calc a sha1 sum", func() {
hash := sha1sum("foo\n")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
})
g.It("Should calc a sha256 sum", func() {
hash := sha256sum("foo\n")
g.Assert(hash).Equal("b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c")
})
g.It("Should calc a sha512 sum", func() {
hash := sha512sum("foo\n")
g.Assert(hash).Equal("0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6")
})
g.It("Should calc a sha1 sum", func() {
hash := sha1sum("foo\n")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
})
g.It("Should validate sha1 sum with file size", func() {
ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 4 -")
g.Assert(ok).IsTrue()
})
g.It("Should validate sha256 sum with file size", func() {
ok := Check("foo\n", "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c 4 -")
g.Assert(ok).IsTrue()
})
g.It("Should validate sha512 sum with file size", func() {
ok := Check("foo\n", "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6 4 -")
g.Assert(ok).IsTrue()
})
g.It("Should fail validation if incorrect sha1", func() {
ok := Check("bar\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 4 -")
g.Assert(ok).IsFalse()
})
g.It("Should fail validation if incorrect sha256", func() {
ok := Check("bar\n", "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c 4 -")
g.Assert(ok).IsFalse()
})
g.It("Should fail validation if incorrect sha512", func() {
ok := Check("bar\n", "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6 4 -")
g.Assert(ok).IsFalse()
})
g.It("Should return false if file size mismatch", func() {
ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 12 -")
g.Assert(ok).IsFalse()
})
g.It("Should return false if invalid checksum string", func() {
ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15234")
g.Assert(ok).IsFalse()
})
g.It("Should return true if empty checksum", func() {
ok := Check("foo\n", "")
g.Assert(ok).IsTrue()
})
})
}

View file

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -0,0 +1,57 @@
[![Build Status](https://travis-ci.org/cloudfoundry-incubator/candiedyaml.svg)](https://travis-ci.org/cloudfoundry-incubator/candiedyaml)
candiedyaml
===========
YAML for Go
A YAML 1.1 parser with support for YAML 1.2 features
Usage
-----
```go
package myApp
import (
"github.com/cloudfoundry-incubator/candiedyaml"
"fmt"
"os"
)
func main() {
file, err := os.Open("path/to/some/file.yml")
if err != nil {
println("File does not exist:", err.Error())
os.Exit(1)
}
defer file.Close()
document := new(interface{})
decoder := candiedyaml.NewDecoder(file)
err = decoder.Decode(document)
if err != nil {
println("Failed to decode document:", err.Error())
}
println("parsed yml into interface:", fmt.Sprintf("%#v", document))
fileToWrite, err := os.Create("path/to/some/new/file.yml")
if err != nil {
println("Failed to open file for writing:", err.Error())
os.Exit(1)
}
defer fileToWrite.Close()
encoder := candiedyaml.NewEncoder(fileToWrite)
err = encoder.Encode(document)
if err != nil {
println("Failed to encode document:", err.Error())
os.Exit(1)
}
return
}
```

View file

@ -0,0 +1,834 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
import (
"io"
)
/*
* 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) (int, 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) (int, error) {
return parser.input_reader.Read(buffer)
}
/*
* Set a string input.
*/
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
if parser.read_handler != nil {
panic("input already set")
}
parser.read_handler = yaml_string_read_handler
parser.input = input
parser.input_pos = 0
}
/*
* Set a reader input
*/
func yaml_parser_set_input_reader(parser *yaml_parser_t, reader io.Reader) {
if parser.read_handler != nil {
panic("input already set")
}
parser.read_handler = yaml_file_read_handler
parser.input_reader = reader
}
/*
* Set a generic input.
*/
func yaml_parser_set_input(parser *yaml_parser_t, handler yaml_read_handler_t) {
if parser.read_handler != nil {
panic("input already set")
}
parser.read_handler = handler
}
/*
* Set the source encoding.
*/
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
if parser.encoding != yaml_ANY_ENCODING {
panic("encoding already set")
}
parser.encoding = encoding
}
/*
* Create a new emitter object.
*/
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
*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),
}
}
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_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
_, err := emitter.output_writer.Write(buffer)
return err
}
/*
* Set a string output.
*/
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, buffer *[]byte) {
if emitter.write_handler != nil {
panic("output already set")
}
emitter.write_handler = yaml_string_write_handler
emitter.output_buffer = buffer
}
/*
* Set a file output.
*/
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
if emitter.write_handler != nil {
panic("output already set")
}
emitter.write_handler = yaml_writer_write_handler
emitter.output_writer = w
}
/*
* Set a generic output handler.
*/
func yaml_emitter_set_output(emitter *yaml_emitter_t, handler yaml_write_handler_t) {
if emitter.write_handler != nil {
panic("output already set")
}
emitter.write_handler = handler
}
/*
* Set the output encoding.
*/
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
if emitter.encoding != yaml_ANY_ENCODING {
panic("encoding already set")
}
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) {
*event = yaml_event_t{
event_type: yaml_STREAM_START_EVENT,
encoding: encoding,
}
}
/*
* Create STREAM-END.
*/
func yaml_stream_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{
event_type: yaml_STREAM_END_EVENT,
}
}
/*
* 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) {
*event = yaml_event_t{
event_type: yaml_DOCUMENT_START_EVENT,
version_directive: version_directive,
tag_directives: tag_directives,
implicit: implicit,
}
}
/*
* Create DOCUMENT-END.
*/
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
*event = yaml_event_t{
event_type: yaml_DOCUMENT_END_EVENT,
implicit: implicit,
}
}
/*
* Create ALIAS.
*/
func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) {
*event = yaml_event_t{
event_type: yaml_ALIAS_EVENT,
anchor: anchor,
}
}
/*
* Create SCALAR.
*/
func yaml_scalar_event_initialize(event *yaml_event_t,
anchor []byte, tag []byte,
value []byte,
plain_implicit bool, quoted_implicit bool,
style yaml_scalar_style_t) {
*event = yaml_event_t{
event_type: yaml_SCALAR_EVENT,
anchor: anchor,
tag: tag,
value: value,
implicit: plain_implicit,
quoted_implicit: quoted_implicit,
style: yaml_style_t(style),
}
}
/*
* Create SEQUENCE-START.
*/
func yaml_sequence_start_event_initialize(event *yaml_event_t,
anchor []byte, tag []byte, implicit bool, style yaml_sequence_style_t) {
*event = yaml_event_t{
event_type: yaml_SEQUENCE_START_EVENT,
anchor: anchor,
tag: tag,
implicit: implicit,
style: yaml_style_t(style),
}
}
/*
* Create SEQUENCE-END.
*/
func yaml_sequence_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{
event_type: yaml_SEQUENCE_END_EVENT,
}
}
/*
* Create MAPPING-START.
*/
func yaml_mapping_start_event_initialize(event *yaml_event_t,
anchor []byte, tag []byte, implicit bool, style yaml_mapping_style_t) {
*event = yaml_event_t{
event_type: yaml_MAPPING_START_EVENT,
anchor: anchor,
tag: tag,
implicit: implicit,
style: yaml_style_t(style),
}
}
/*
* Create MAPPING-END.
*/
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{
event_type: yaml_MAPPING_END_EVENT,
}
}
/*
* Destroy an event object.
*/
func yaml_event_delete(event *yaml_event_t) {
*event = yaml_event_t{}
}
// /*
// * Create a document object.
// */
//
// func yaml_document_initialize(document *yaml_document_t,
// version_directive *yaml_version_directive_t,
// tag_directives []yaml_tag_directive_t,
// start_implicit, end_implicit bool) bool {
//
//
// {
// struct {
// YAML_error_type_t error;
// } context;
// struct {
// yaml_node_t *start;
// yaml_node_t *end;
// yaml_node_t *top;
// } nodes = { NULL, NULL, NULL };
// yaml_version_directive_t *version_directive_copy = NULL;
// struct {
// yaml_tag_directive_t *start;
// yaml_tag_directive_t *end;
// yaml_tag_directive_t *top;
// } tag_directives_copy = { NULL, NULL, NULL };
// yaml_tag_directive_t value = { NULL, NULL };
// YAML_mark_t mark = { 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) {
// yaml_tag_directive_t *tag_directive;
// 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)) {
// yaml_tag_directive_t value = 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 {
// YAML_error_type_t error;
// } context;
// yaml_tag_directive_t *tag_directive;
//
// context.error = yaml_NO_ERROR; /* Eliminate a compliler warning. */
//
// assert(document); /* Non-NULL document object is expected. */
//
// while (!STACK_EMPTY(&context, document.nodes)) {
// yaml_node_t node = 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, int index)
// {
// 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,
// yaml_char_t *tag, yaml_char_t *value, int length,
// yaml_scalar_style_t style)
// {
// struct {
// YAML_error_type_t error;
// } context;
// YAML_mark_t mark = { 0, 0, 0 };
// yaml_char_t *tag_copy = NULL;
// yaml_char_t *value_copy = NULL;
// yaml_node_t node;
//
// 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,
// yaml_char_t *tag, yaml_sequence_style_t style)
// {
// struct {
// YAML_error_type_t error;
// } context;
// YAML_mark_t mark = { 0, 0, 0 };
// yaml_char_t *tag_copy = NULL;
// struct {
// yaml_node_item_t *start;
// yaml_node_item_t *end;
// yaml_node_item_t *top;
// } items = { NULL, NULL, NULL };
// yaml_node_t node;
//
// 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,
// yaml_char_t *tag, yaml_mapping_style_t style)
// {
// struct {
// YAML_error_type_t error;
// } context;
// YAML_mark_t mark = { 0, 0, 0 };
// yaml_char_t *tag_copy = NULL;
// struct {
// yaml_node_pair_t *start;
// yaml_node_pair_t *end;
// yaml_node_pair_t *top;
// } pairs = { NULL, NULL, NULL };
// yaml_node_t node;
//
// 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,
// int sequence, int item)
// {
// struct {
// YAML_error_type_t error;
// } 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,
// int mapping, int key, int value)
// {
// struct {
// YAML_error_type_t error;
// } context;
//
// yaml_node_pair_t pair;
//
// 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;
// }
//

View file

@ -0,0 +1,622 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"runtime"
"strconv"
"strings"
)
type Unmarshaler interface {
UnmarshalYAML(tag string, value interface{}) error
}
// A Number represents a JSON number literal.
type Number string
// String returns the literal text of the number.
func (n Number) String() string { return string(n) }
// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
return strconv.ParseFloat(string(n), 64)
}
// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64)
}
type Decoder struct {
parser yaml_parser_t
event yaml_event_t
replay_events []yaml_event_t
useNumber bool
anchors map[string][]yaml_event_t
tracking_anchors [][]yaml_event_t
}
type ParserError struct {
ErrorType YAML_error_type_t
Context string
ContextMark YAML_mark_t
Problem string
ProblemMark YAML_mark_t
}
func (e *ParserError) Error() string {
return fmt.Sprintf("yaml: [%s] %s at line %d, column %d", e.Context, e.Problem, e.ProblemMark.line+1, e.ProblemMark.column+1)
}
type UnexpectedEventError struct {
Value string
EventType yaml_event_type_t
At YAML_mark_t
}
func (e *UnexpectedEventError) Error() string {
return fmt.Sprintf("yaml: Unexpect event [%d]: '%s' at line %d, column %d", e.EventType, e.Value, e.At.line+1, e.At.column+1)
}
func recovery(err *error) {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
panic(r)
}
var tmpError error
switch r := r.(type) {
case error:
tmpError = r
case string:
tmpError = errors.New(r)
default:
tmpError = errors.New("Unknown panic: " + reflect.ValueOf(r).String())
}
*err = tmpError
}
}
func Unmarshal(data []byte, v interface{}) error {
d := NewDecoder(bytes.NewBuffer(data))
return d.Decode(v)
}
func NewDecoder(r io.Reader) *Decoder {
d := &Decoder{
anchors: make(map[string][]yaml_event_t),
tracking_anchors: make([][]yaml_event_t, 1),
}
yaml_parser_initialize(&d.parser)
yaml_parser_set_input_reader(&d.parser, r)
return d
}
func (d *Decoder) Decode(v interface{}) (err error) {
defer recovery(&err)
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return fmt.Errorf("Expected a pointer or nil but was a %s at %s", rv.String(), d.event.start_mark)
}
if d.event.event_type == yaml_NO_EVENT {
d.nextEvent()
if d.event.event_type != yaml_STREAM_START_EVENT {
return errors.New("Invalid stream")
}
d.nextEvent()
}
d.document(rv)
return nil
}
func (d *Decoder) UseNumber() { d.useNumber = true }
func (d *Decoder) error(err error) {
panic(err)
}
func (d *Decoder) nextEvent() {
if d.event.event_type == yaml_STREAM_END_EVENT {
d.error(errors.New("The stream is closed"))
}
if d.replay_events != nil {
d.event = d.replay_events[0]
if len(d.replay_events) == 1 {
d.replay_events = nil
} else {
d.replay_events = d.replay_events[1:]
}
} else {
if !yaml_parser_parse(&d.parser, &d.event) {
yaml_event_delete(&d.event)
d.error(&ParserError{
ErrorType: d.parser.error,
Context: d.parser.context,
ContextMark: d.parser.context_mark,
Problem: d.parser.problem,
ProblemMark: d.parser.problem_mark,
})
}
}
last := len(d.tracking_anchors)
// skip aliases when tracking an anchor
if last > 0 && d.event.event_type != yaml_ALIAS_EVENT {
d.tracking_anchors[last-1] = append(d.tracking_anchors[last-1], d.event)
}
}
func (d *Decoder) document(rv reflect.Value) {
if d.event.event_type != yaml_DOCUMENT_START_EVENT {
d.error(fmt.Errorf("Expected document start at %s", d.event.start_mark))
}
d.nextEvent()
d.parse(rv)
if d.event.event_type != yaml_DOCUMENT_END_EVENT {
d.error(fmt.Errorf("Expected document end at %s", d.event.start_mark))
}
d.nextEvent()
}
func (d *Decoder) parse(rv reflect.Value) {
if !rv.IsValid() {
// skip ahead since we cannot store
d.valueInterface()
return
}
anchor := string(d.event.anchor)
switch d.event.event_type {
case yaml_SEQUENCE_START_EVENT:
d.begin_anchor(anchor)
d.sequence(rv)
d.end_anchor(anchor)
case yaml_MAPPING_START_EVENT:
d.begin_anchor(anchor)
d.mapping(rv)
d.end_anchor(anchor)
case yaml_SCALAR_EVENT:
d.begin_anchor(anchor)
d.scalar(rv)
d.end_anchor(anchor)
case yaml_ALIAS_EVENT:
d.alias(rv)
case yaml_DOCUMENT_END_EVENT:
default:
d.error(&UnexpectedEventError{
Value: string(d.event.value),
EventType: d.event.event_type,
At: d.event.start_mark,
})
}
}
func (d *Decoder) begin_anchor(anchor string) {
if anchor != "" {
events := []yaml_event_t{d.event}
d.tracking_anchors = append(d.tracking_anchors, events)
}
}
func (d *Decoder) end_anchor(anchor string) {
if anchor != "" {
events := d.tracking_anchors[len(d.tracking_anchors)-1]
d.tracking_anchors = d.tracking_anchors[0 : len(d.tracking_anchors)-1]
// remove the anchor, replaying events shouldn't have anchors
events[0].anchor = nil
// we went one too many, remove the extra event
events = events[:len(events)-1]
// if nested, append to all the other anchors
for i, e := range d.tracking_anchors {
d.tracking_anchors[i] = append(e, events...)
}
d.anchors[anchor] = events
}
}
func (d *Decoder) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, reflect.Value) {
// If v is a named type and is addressable,
// start with its address, so that if the type has pointer methods,
// we find them.
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
}
for {
// Load value from interface, but only if the result will be
// usefully addressable.
if v.Kind() == reflect.Interface && !v.IsNil() {
e := v.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
v = e
continue
}
}
if v.Kind() != reflect.Ptr {
break
}
if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
break
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
if v.Type().NumMethod() > 0 {
if u, ok := v.Interface().(Unmarshaler); ok {
var temp interface{}
return u, reflect.ValueOf(&temp)
}
}
v = v.Elem()
}
return nil, v
}
func (d *Decoder) sequence(v reflect.Value) {
if d.event.event_type != yaml_SEQUENCE_START_EVENT {
d.error(fmt.Errorf("Expected sequence start at %s", d.event.start_mark))
}
u, pv := d.indirect(v, false)
if u != nil {
defer func() {
if err := u.UnmarshalYAML(yaml_SEQ_TAG, pv.Interface()); err != nil {
d.error(err)
}
}()
_, pv = d.indirect(pv, false)
}
v = pv
// Check type of target.
switch v.Kind() {
case reflect.Interface:
if v.NumMethod() == 0 {
// Decoding into nil interface? Switch to non-reflect code.
v.Set(reflect.ValueOf(d.sequenceInterface()))
return
}
// Otherwise it's invalid.
fallthrough
default:
d.error(fmt.Errorf("Expected an array, slice or interface{} but was a %s at %s", v, d.event.start_mark))
case reflect.Array:
case reflect.Slice:
break
}
d.nextEvent()
i := 0
done:
for {
switch d.event.event_type {
case yaml_SEQUENCE_END_EVENT, yaml_DOCUMENT_END_EVENT:
break done
}
// Get element of array, growing if necessary.
if v.Kind() == reflect.Slice {
// Grow slice if necessary
if i >= v.Cap() {
newcap := v.Cap() + v.Cap()/2
if newcap < 4 {
newcap = 4
}
newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
reflect.Copy(newv, v)
v.Set(newv)
}
if i >= v.Len() {
v.SetLen(i + 1)
}
}
if i < v.Len() {
// Decode into element.
d.parse(v.Index(i))
} else {
// Ran out of fixed array: skip.
d.parse(reflect.Value{})
}
i++
}
if i < v.Len() {
if v.Kind() == reflect.Array {
// Array. Zero the rest.
z := reflect.Zero(v.Type().Elem())
for ; i < v.Len(); i++ {
v.Index(i).Set(z)
}
} else {
v.SetLen(i)
}
}
if i == 0 && v.Kind() == reflect.Slice {
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
}
if d.event.event_type != yaml_DOCUMENT_END_EVENT {
d.nextEvent()
}
}
func (d *Decoder) mapping(v reflect.Value) {
u, pv := d.indirect(v, false)
if u != nil {
defer func() {
if err := u.UnmarshalYAML(yaml_MAP_TAG, pv.Interface()); err != nil {
d.error(err)
}
}()
_, pv = d.indirect(pv, false)
}
v = pv
// Decoding into nil interface? Switch to non-reflect code.
if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
v.Set(reflect.ValueOf(d.mappingInterface()))
return
}
// Check type of target: struct or map[X]Y
switch v.Kind() {
case reflect.Struct:
d.mappingStruct(v)
return
case reflect.Map:
default:
d.error(fmt.Errorf("Expected a struct or map but was a %s at %s ", v, d.event.start_mark))
}
mapt := v.Type()
if v.IsNil() {
v.Set(reflect.MakeMap(mapt))
}
d.nextEvent()
keyt := mapt.Key()
mapElemt := mapt.Elem()
var mapElem reflect.Value
done:
for {
switch d.event.event_type {
case yaml_MAPPING_END_EVENT:
break done
case yaml_DOCUMENT_END_EVENT:
return
}
key := reflect.New(keyt)
d.parse(key.Elem())
if !mapElem.IsValid() {
mapElem = reflect.New(mapElemt).Elem()
} else {
mapElem.Set(reflect.Zero(mapElemt))
}
d.parse(mapElem)
v.SetMapIndex(key.Elem(), mapElem)
}
d.nextEvent()
}
func (d *Decoder) mappingStruct(v reflect.Value) {
structt := v.Type()
fields := cachedTypeFields(structt)
d.nextEvent()
done:
for {
switch d.event.event_type {
case yaml_MAPPING_END_EVENT:
break done
case yaml_DOCUMENT_END_EVENT:
return
}
key := ""
d.parse(reflect.ValueOf(&key))
// Figure out field corresponding to key.
var subv reflect.Value
var f *field
for i := range fields {
ff := &fields[i]
if ff.name == key {
f = ff
break
}
if f == nil && strings.EqualFold(ff.name, key) {
f = ff
}
}
if f != nil {
subv = v
for _, i := range f.index {
if subv.Kind() == reflect.Ptr {
if subv.IsNil() {
subv.Set(reflect.New(subv.Type().Elem()))
}
subv = subv.Elem()
}
subv = subv.Field(i)
}
}
d.parse(subv)
}
d.nextEvent()
}
func (d *Decoder) scalar(v reflect.Value) {
val := string(d.event.value)
wantptr := null_values[val]
u, pv := d.indirect(v, wantptr)
var tag string
if u != nil {
defer func() {
if err := u.UnmarshalYAML(tag, pv.Interface()); err != nil {
d.error(err)
}
}()
_, pv = d.indirect(pv, wantptr)
}
v = pv
var err error
tag, err = resolve(d.event, v, d.useNumber)
if err != nil {
d.error(err)
}
d.nextEvent()
}
func (d *Decoder) alias(rv reflect.Value) {
val, ok := d.anchors[string(d.event.anchor)]
if !ok {
d.error(fmt.Errorf("missing anchor: '%s' at %s", d.event.anchor, d.event.start_mark))
}
d.replay_events = val
d.nextEvent()
d.parse(rv)
}
func (d *Decoder) valueInterface() interface{} {
var v interface{}
anchor := string(d.event.anchor)
switch d.event.event_type {
case yaml_SEQUENCE_START_EVENT:
d.begin_anchor(anchor)
v = d.sequenceInterface()
case yaml_MAPPING_START_EVENT:
d.begin_anchor(anchor)
v = d.mappingInterface()
case yaml_SCALAR_EVENT:
d.begin_anchor(anchor)
v = d.scalarInterface()
case yaml_ALIAS_EVENT:
rv := reflect.ValueOf(&v)
d.alias(rv)
return v
case yaml_DOCUMENT_END_EVENT:
d.error(&UnexpectedEventError{
Value: string(d.event.value),
EventType: d.event.event_type,
At: d.event.start_mark,
})
}
d.end_anchor(anchor)
return v
}
func (d *Decoder) scalarInterface() interface{} {
_, v := resolveInterface(d.event, d.useNumber)
d.nextEvent()
return v
}
// sequenceInterface is like sequence but returns []interface{}.
func (d *Decoder) sequenceInterface() []interface{} {
var v = make([]interface{}, 0)
d.nextEvent()
done:
for {
switch d.event.event_type {
case yaml_SEQUENCE_END_EVENT, yaml_DOCUMENT_END_EVENT:
break done
}
v = append(v, d.valueInterface())
}
if d.event.event_type != yaml_DOCUMENT_END_EVENT {
d.nextEvent()
}
return v
}
// mappingInterface is like mapping but returns map[interface{}]interface{}.
func (d *Decoder) mappingInterface() map[interface{}]interface{} {
m := make(map[interface{}]interface{})
d.nextEvent()
done:
for {
switch d.event.event_type {
case yaml_MAPPING_END_EVENT, yaml_DOCUMENT_END_EVENT:
break done
}
key := d.valueInterface()
// Read value.
m[key] = d.valueInterface()
}
if d.event.event_type != yaml_DOCUMENT_END_EVENT {
d.nextEvent()
}
return m
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,395 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
import (
"bytes"
"encoding/base64"
"io"
"math"
"reflect"
"regexp"
"sort"
"strconv"
"time"
)
var (
timeTimeType = reflect.TypeOf(time.Time{})
marshalerType = reflect.TypeOf(new(Marshaler)).Elem()
numberType = reflect.TypeOf(Number(""))
nonPrintable = regexp.MustCompile("[^\t\n\r\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFD]")
multiline = regexp.MustCompile("\n|\u0085|\u2028|\u2029")
shortTags = map[string]string{
yaml_NULL_TAG: "!!null",
yaml_BOOL_TAG: "!!bool",
yaml_STR_TAG: "!!str",
yaml_INT_TAG: "!!int",
yaml_FLOAT_TAG: "!!float",
yaml_TIMESTAMP_TAG: "!!timestamp",
yaml_SEQ_TAG: "!!seq",
yaml_MAP_TAG: "!!map",
yaml_BINARY_TAG: "!!binary",
}
)
type Marshaler interface {
MarshalYAML() (tag string, value interface{}, err error)
}
// An Encoder writes JSON objects to an output stream.
type Encoder struct {
w io.Writer
emitter yaml_emitter_t
event yaml_event_t
flow bool
err error
}
func Marshal(v interface{}) ([]byte, error) {
b := bytes.Buffer{}
e := NewEncoder(&b)
err := e.Encode(v)
return b.Bytes(), err
}
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
e := &Encoder{w: w}
yaml_emitter_initialize(&e.emitter)
yaml_emitter_set_output_writer(&e.emitter, e.w)
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
e.emit()
yaml_document_start_event_initialize(&e.event, nil, nil, true)
e.emit()
return e
}
func (e *Encoder) Encode(v interface{}) (err error) {
defer recovery(&err)
if e.err != nil {
return e.err
}
e.marshal("", reflect.ValueOf(v), true)
yaml_document_end_event_initialize(&e.event, true)
e.emit()
e.emitter.open_ended = false
yaml_stream_end_event_initialize(&e.event)
e.emit()
return nil
}
func (e *Encoder) emit() {
if !yaml_emitter_emit(&e.emitter, &e.event) {
panic("bad emit")
}
}
func (e *Encoder) marshal(tag string, v reflect.Value, allowAddr bool) {
vt := v.Type()
if vt.Implements(marshalerType) {
e.emitMarshaler(tag, v)
return
}
if vt.Kind() != reflect.Ptr && allowAddr {
if reflect.PtrTo(vt).Implements(marshalerType) {
e.emitAddrMarshaler(tag, v)
return
}
}
switch v.Kind() {
case reflect.Interface:
if v.IsNil() {
e.emitNil()
} else {
e.marshal(tag, v.Elem(), allowAddr)
}
case reflect.Map:
e.emitMap(tag, v)
case reflect.Ptr:
if v.IsNil() {
e.emitNil()
} else {
e.marshal(tag, v.Elem(), true)
}
case reflect.Struct:
e.emitStruct(tag, v)
case reflect.Slice:
e.emitSlice(tag, v)
case reflect.String:
e.emitString(tag, v)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
e.emitInt(tag, v)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
e.emitUint(tag, v)
case reflect.Float32, reflect.Float64:
e.emitFloat(tag, v)
case reflect.Bool:
e.emitBool(tag, v)
default:
panic("Can't marshal type yet: " + v.Type().String())
}
}
func (e *Encoder) emitMap(tag string, v reflect.Value) {
e.mapping(tag, func() {
var keys stringValues = v.MapKeys()
sort.Sort(keys)
for _, k := range keys {
e.marshal("", k, true)
e.marshal("", v.MapIndex(k), true)
}
})
}
func (e *Encoder) emitStruct(tag string, v reflect.Value) {
if v.Type() == timeTimeType {
e.emitTime(tag, v)
return
}
fields := cachedTypeFields(v.Type())
e.mapping(tag, func() {
for _, f := range fields {
fv := fieldByIndex(v, f.index)
if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) {
continue
}
e.marshal("", reflect.ValueOf(f.name), true)
e.flow = f.flow
e.marshal("", fv, true)
}
})
}
func (e *Encoder) emitTime(tag string, v reflect.Value) {
t := v.Interface().(time.Time)
bytes, _ := t.MarshalText()
e.emitScalar(string(bytes), "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
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.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
func (e *Encoder) mapping(tag string, f func()) {
implicit := tag == ""
style := yaml_BLOCK_MAPPING_STYLE
if e.flow {
e.flow = false
style = yaml_FLOW_MAPPING_STYLE
}
yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
e.emit()
f()
yaml_mapping_end_event_initialize(&e.event)
e.emit()
}
func (e *Encoder) emitSlice(tag string, v reflect.Value) {
if v.Type() == byteSliceType {
e.emitBase64(tag, v)
return
}
implicit := tag == ""
style := yaml_BLOCK_SEQUENCE_STYLE
if e.flow {
e.flow = false
style = yaml_FLOW_SEQUENCE_STYLE
}
yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
e.emit()
n := v.Len()
for i := 0; i < n; i++ {
e.marshal("", v.Index(i), true)
}
yaml_sequence_end_event_initialize(&e.event)
e.emit()
}
func (e *Encoder) emitBase64(tag string, v reflect.Value) {
if v.IsNil() {
e.emitNil()
return
}
s := v.Bytes()
dst := make([]byte, base64.StdEncoding.EncodedLen(len(s)))
base64.StdEncoding.Encode(dst, s)
e.emitScalar(string(dst), "", yaml_BINARY_TAG, yaml_DOUBLE_QUOTED_SCALAR_STYLE)
}
func (e *Encoder) emitString(tag string, v reflect.Value) {
var style yaml_scalar_style_t
s := v.String()
if nonPrintable.MatchString(s) {
e.emitBase64(tag, v)
return
}
if v.Type() == numberType {
style = yaml_PLAIN_SCALAR_STYLE
} else {
event := yaml_event_t{
implicit: true,
value: []byte(s),
}
rtag, _ := resolveInterface(event, false)
if tag == "" && rtag != yaml_STR_TAG {
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
} else if multiline.MatchString(s) {
style = yaml_LITERAL_SCALAR_STYLE
} else {
style = yaml_PLAIN_SCALAR_STYLE
}
}
e.emitScalar(s, "", tag, style)
}
func (e *Encoder) emitBool(tag string, v reflect.Value) {
s := strconv.FormatBool(v.Bool())
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *Encoder) emitInt(tag string, v reflect.Value) {
s := strconv.FormatInt(v.Int(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *Encoder) emitUint(tag string, v reflect.Value) {
s := strconv.FormatUint(v.Uint(), 10)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *Encoder) emitFloat(tag string, v reflect.Value) {
f := v.Float()
var s string
switch {
case math.IsNaN(f):
s = ".nan"
case math.IsInf(f, 1):
s = "+.inf"
case math.IsInf(f, -1):
s = "-.inf"
default:
s = strconv.FormatFloat(f, 'g', -1, v.Type().Bits())
}
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *Encoder) emitNil() {
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
}
func (e *Encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
implicit := tag == ""
if !implicit {
style = yaml_PLAIN_SCALAR_STYLE
}
stag := shortTags[tag]
if stag == "" {
stag = tag
}
yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(stag), []byte(value), implicit, implicit, style)
e.emit()
}
func (e *Encoder) emitMarshaler(tag string, v reflect.Value) {
if v.Kind() == reflect.Ptr && v.IsNil() {
e.emitNil()
return
}
m := v.Interface().(Marshaler)
if m == nil {
e.emitNil()
return
}
t, val, err := m.MarshalYAML()
if err != nil {
panic(err)
}
if val == nil {
e.emitNil()
return
}
e.marshal(t, reflect.ValueOf(val), false)
}
func (e *Encoder) emitAddrMarshaler(tag string, v reflect.Value) {
if !v.CanAddr() {
e.marshal(tag, v, false)
return
}
va := v.Addr()
if va.IsNil() {
e.emitNil()
return
}
m := v.Interface().(Marshaler)
t, val, err := m.MarshalYAML()
if err != nil {
panic(err)
}
if val == nil {
e.emitNil()
return
}
e.marshal(t, reflect.ValueOf(val), false)
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2006 Kirill Simonov
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,465 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
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. */
raw := parser.raw_buffer
pos := parser.raw_buffer_pos
remaining := len(raw) - pos
if remaining >= 2 &&
raw[pos] == BOM_UTF16LE[0] && raw[pos+1] == BOM_UTF16LE[1] {
parser.encoding = yaml_UTF16LE_ENCODING
parser.raw_buffer_pos += 2
parser.offset += 2
} else if remaining >= 2 &&
raw[pos] == BOM_UTF16BE[0] && raw[pos+1] == BOM_UTF16BE[1] {
parser.encoding = yaml_UTF16BE_ENCODING
parser.raw_buffer_pos += 2
parser.offset += 2
} else if remaining >= 3 &&
raw[pos] == BOM_UTF8[0] && raw[pos+1] == BOM_UTF8[1] && raw[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 1 on success, 0 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 {
/* Read handler must be set. */
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_end := len(parser.buffer)
if 0 < parser.buffer_pos &&
parser.buffer_pos < buffer_end {
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
buffer_end -= parser.buffer_pos
parser.buffer_pos = 0
} else if parser.buffer_pos == buffer_end {
buffer_end = 0
parser.buffer_pos = 0
}
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_end]
return false
}
}
first = false
/* Decode the raw buffer. */
for parser.raw_buffer_pos != len(parser.raw_buffer) {
var value rune
var w int
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
incomplete := false
/* 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]
w = width(octet)
/* Check if the leading octet is valid. */
if w == 0 {
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 w > raw_unread {
if parser.eof {
return yaml_parser_set_reader_error(parser,
"incomplete UTF-8 octet sequence",
parser.offset, -1)
}
incomplete = true
break
}
/* 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 < w; 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 w == 1:
case w == 2 && value >= 0x80:
case w == 3 && value >= 0x800:
case w == 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)
}
incomplete = true
break
}
/* 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 {
w = 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)
}
incomplete = true
break
}
/* 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 {
w = 2
}
break
default:
panic("Impossible") /* Impossible. */
}
/* Check if the raw buffer contains enough bytes to form a character. */
if incomplete {
break
}
/*
* 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)
*/
if !(value == 0x09 || value == 0x0A || value == 0x0D ||
(value >= 0x20 && value <= 0x7E) ||
(value == 0x85) || (value >= 0xA0 && value <= 0xD7FF) ||
(value >= 0xE000 && value <= 0xFFFD) ||
(value >= 0x10000 && value <= 0x10FFFF)) {
return yaml_parser_set_reader_error(parser,
"control characters are not allowed",
parser.offset, int(value))
}
/* Move the raw pointers. */
parser.raw_buffer_pos += w
parser.offset += w
/* Finally put the character into the buffer. */
/* 0000 0000-0000 007F . 0xxxxxxx */
if value <= 0x7F {
parser.buffer[buffer_end] = byte(value)
} else if value <= 0x7FF {
/* 0000 0080-0000 07FF . 110xxxxx 10xxxxxx */
parser.buffer[buffer_end] = byte(0xC0 + (value >> 6))
parser.buffer[buffer_end+1] = byte(0x80 + (value & 0x3F))
} else if value <= 0xFFFF {
/* 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx */
parser.buffer[buffer_end] = byte(0xE0 + (value >> 12))
parser.buffer[buffer_end+1] = byte(0x80 + ((value >> 6) & 0x3F))
parser.buffer[buffer_end+2] = byte(0x80 + (value & 0x3F))
} else {
/* 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
parser.buffer[buffer_end] = byte(0xF0 + (value >> 18))
parser.buffer[buffer_end+1] = byte(0x80 + ((value >> 12) & 0x3F))
parser.buffer[buffer_end+2] = byte(0x80 + ((value >> 6) & 0x3F))
parser.buffer[buffer_end+3] = byte(0x80 + (value & 0x3F))
}
buffer_end += w
parser.unread++
}
/* On EOF, put NUL into the buffer and return. */
if parser.eof {
parser.buffer[buffer_end] = 0
buffer_end++
parser.buffer = parser.buffer[:buffer_end]
parser.unread++
return true
}
}
parser.buffer = parser.buffer[:buffer_end]
return true
}

View file

@ -0,0 +1,449 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
import (
"bytes"
"encoding/base64"
"fmt"
"math"
"reflect"
"regexp"
"strconv"
"strings"
"time"
)
var byteSliceType = reflect.TypeOf([]byte(nil))
var binary_tags = [][]byte{[]byte("!binary"), []byte(yaml_BINARY_TAG)}
var bool_values map[string]bool
var null_values map[string]bool
var signs = []byte{'-', '+'}
var nulls = []byte{'~', 'n', 'N'}
var bools = []byte{'t', 'T', 'f', 'F', 'y', 'Y', 'n', 'N', 'o', 'O'}
var timestamp_regexp *regexp.Regexp
var ymd_regexp *regexp.Regexp
func init() {
bool_values = make(map[string]bool)
bool_values["y"] = true
bool_values["yes"] = true
bool_values["n"] = false
bool_values["no"] = false
bool_values["true"] = true
bool_values["false"] = false
bool_values["on"] = true
bool_values["off"] = false
null_values = make(map[string]bool)
null_values["~"] = true
null_values["null"] = true
null_values["Null"] = true
null_values["NULL"] = true
timestamp_regexp = regexp.MustCompile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$")
ymd_regexp = regexp.MustCompile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$")
}
func resolve(event yaml_event_t, v reflect.Value, useNumber bool) (string, error) {
val := string(event.value)
if null_values[val] {
v.Set(reflect.Zero(v.Type()))
return yaml_NULL_TAG, nil
}
switch v.Kind() {
case reflect.String:
if useNumber && v.Type() == numberType {
tag, i := resolveInterface(event, useNumber)
if n, ok := i.(Number); ok {
v.Set(reflect.ValueOf(n))
return tag, nil
}
return "", fmt.Errorf("Not a number: '%s' at %s", event.value, event.start_mark)
}
return resolve_string(val, v, event)
case reflect.Bool:
return resolve_bool(val, v, event)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return resolve_int(val, v, useNumber, event)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return resolve_uint(val, v, useNumber, event)
case reflect.Float32, reflect.Float64:
return resolve_float(val, v, useNumber, event)
case reflect.Interface:
_, i := resolveInterface(event, useNumber)
if i != nil {
v.Set(reflect.ValueOf(i))
} else {
v.Set(reflect.Zero(v.Type()))
}
case reflect.Struct:
return resolve_time(val, v, event)
case reflect.Slice:
if v.Type() != byteSliceType {
return "", fmt.Errorf("Cannot resolve %s into %s at %s", val, v.String(), event.start_mark)
}
b, err := decode_binary(event.value, event)
if err != nil {
return "", err
}
v.Set(reflect.ValueOf(b))
default:
return "", fmt.Errorf("Unknown resolution for '%s' using %s at %s", val, v.String(), event.start_mark)
}
return yaml_STR_TAG, nil
}
func hasBinaryTag(event yaml_event_t) bool {
for _, tag := range binary_tags {
if bytes.Equal(event.tag, tag) {
return true
}
}
return false
}
func decode_binary(value []byte, event yaml_event_t) ([]byte, error) {
b := make([]byte, base64.StdEncoding.DecodedLen(len(value)))
n, err := base64.StdEncoding.Decode(b, value)
if err != nil {
return nil, fmt.Errorf("Invalid base64 text: '%s' at %s", string(b), event.start_mark)
}
return b[:n], nil
}
func resolve_string(val string, v reflect.Value, event yaml_event_t) (string, error) {
if len(event.tag) > 0 {
if hasBinaryTag(event) {
b, err := decode_binary(event.value, event)
if err != nil {
return "", err
}
val = string(b)
}
}
v.SetString(val)
return yaml_STR_TAG, nil
}
func resolve_bool(val string, v reflect.Value, event yaml_event_t) (string, error) {
b, found := bool_values[strings.ToLower(val)]
if !found {
return "", fmt.Errorf("Invalid boolean: '%s' at %s", val, event.start_mark)
}
v.SetBool(b)
return yaml_BOOL_TAG, nil
}
func resolve_int(val string, v reflect.Value, useNumber bool, event yaml_event_t) (string, error) {
original := val
val = strings.Replace(val, "_", "", -1)
var value uint64
isNumberValue := v.Type() == numberType
sign := int64(1)
if val[0] == '-' {
sign = -1
val = val[1:]
} else if val[0] == '+' {
val = val[1:]
}
base := 0
if val == "0" {
if isNumberValue {
v.SetString("0")
} else {
v.Set(reflect.Zero(v.Type()))
}
return yaml_INT_TAG, nil
}
if strings.HasPrefix(val, "0o") {
base = 8
val = val[2:]
}
value, err := strconv.ParseUint(val, base, 64)
if err != nil {
return "", fmt.Errorf("Invalid integer: '%s' at %s", original, event.start_mark)
}
var val64 int64
if value <= math.MaxInt64 {
val64 = int64(value)
if sign == -1 {
val64 = -val64
}
} else if sign == -1 && value == uint64(math.MaxInt64)+1 {
val64 = math.MinInt64
} else {
return "", fmt.Errorf("Invalid integer: '%s' at %s", original, event.start_mark)
}
if isNumberValue {
v.SetString(strconv.FormatInt(val64, 10))
} else {
if v.OverflowInt(val64) {
return "", fmt.Errorf("Invalid integer: '%s' at %s", original, event.start_mark)
}
v.SetInt(val64)
}
return yaml_INT_TAG, nil
}
func resolve_uint(val string, v reflect.Value, useNumber bool, event yaml_event_t) (string, error) {
original := val
val = strings.Replace(val, "_", "", -1)
var value uint64
isNumberValue := v.Type() == numberType
if val[0] == '-' {
return "", fmt.Errorf("Unsigned int with negative value: '%s' at %s", original, event.start_mark)
}
if val[0] == '+' {
val = val[1:]
}
base := 0
if val == "0" {
if isNumberValue {
v.SetString("0")
} else {
v.Set(reflect.Zero(v.Type()))
}
return yaml_INT_TAG, nil
}
if strings.HasPrefix(val, "0o") {
base = 8
val = val[2:]
}
value, err := strconv.ParseUint(val, base, 64)
if err != nil {
return "", fmt.Errorf("Invalid unsigned integer: '%s' at %s", val, event.start_mark)
}
if isNumberValue {
v.SetString(strconv.FormatUint(value, 10))
} else {
if v.OverflowUint(value) {
return "", fmt.Errorf("Invalid unsigned integer: '%s' at %s", val, event.start_mark)
}
v.SetUint(value)
}
return yaml_INT_TAG, nil
}
func resolve_float(val string, v reflect.Value, useNumber bool, event yaml_event_t) (string, error) {
val = strings.Replace(val, "_", "", -1)
var value float64
isNumberValue := v.Type() == numberType
typeBits := 64
if !isNumberValue {
typeBits = v.Type().Bits()
}
sign := 1
if val[0] == '-' {
sign = -1
val = val[1:]
} else if val[0] == '+' {
val = val[1:]
}
valLower := strings.ToLower(val)
if valLower == ".inf" {
value = math.Inf(sign)
} else if valLower == ".nan" {
value = math.NaN()
} else {
var err error
value, err = strconv.ParseFloat(val, typeBits)
value *= float64(sign)
if err != nil {
return "", fmt.Errorf("Invalid float: '%s' at %s", val, event.start_mark)
}
}
if isNumberValue {
v.SetString(strconv.FormatFloat(value, 'g', -1, typeBits))
} else {
if v.OverflowFloat(value) {
return "", fmt.Errorf("Invalid float: '%s' at %s", val, event.start_mark)
}
v.SetFloat(value)
}
return yaml_FLOAT_TAG, nil
}
func resolve_time(val string, v reflect.Value, event yaml_event_t) (string, error) {
var parsedTime time.Time
matches := ymd_regexp.FindStringSubmatch(val)
if len(matches) > 0 {
year, _ := strconv.Atoi(matches[1])
month, _ := strconv.Atoi(matches[2])
day, _ := strconv.Atoi(matches[3])
parsedTime = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
} else {
matches = timestamp_regexp.FindStringSubmatch(val)
if len(matches) == 0 {
return "", fmt.Errorf("Invalid timestamp: '%s' at %s", val, event.start_mark)
}
year, _ := strconv.Atoi(matches[1])
month, _ := strconv.Atoi(matches[2])
day, _ := strconv.Atoi(matches[3])
hour, _ := strconv.Atoi(matches[4])
min, _ := strconv.Atoi(matches[5])
sec, _ := strconv.Atoi(matches[6])
nsec := 0
if matches[7] != "" {
millis, _ := strconv.Atoi(matches[7])
nsec = int(time.Duration(millis) * time.Millisecond)
}
loc := time.UTC
if matches[8] != "" {
sign := matches[8][0]
hr, _ := strconv.Atoi(matches[8][1:])
min := 0
if matches[9] != "" {
min, _ = strconv.Atoi(matches[9])
}
zoneOffset := (hr*60 + min) * 60
if sign == '-' {
zoneOffset = -zoneOffset
}
loc = time.FixedZone("", zoneOffset)
}
parsedTime = time.Date(year, time.Month(month), day, hour, min, sec, nsec, loc)
}
v.Set(reflect.ValueOf(parsedTime))
return "", nil
}
func resolveInterface(event yaml_event_t, useNumber bool) (string, interface{}) {
val := string(event.value)
if len(event.tag) == 0 && !event.implicit {
return "", val
}
if len(val) == 0 {
return yaml_NULL_TAG, nil
}
var result interface{}
sign := false
c := val[0]
switch {
case bytes.IndexByte(signs, c) != -1:
sign = true
fallthrough
case c >= '0' && c <= '9':
i := int64(0)
result = &i
if useNumber {
var n Number
result = &n
}
v := reflect.ValueOf(result).Elem()
if _, err := resolve_int(val, v, useNumber, event); err == nil {
return yaml_INT_TAG, v.Interface()
}
f := float64(0)
result = &f
if useNumber {
var n Number
result = &n
}
v = reflect.ValueOf(result).Elem()
if _, err := resolve_float(val, v, useNumber, event); err == nil {
return yaml_FLOAT_TAG, v.Interface()
}
if !sign {
t := time.Time{}
if _, err := resolve_time(val, reflect.ValueOf(&t).Elem(), event); err == nil {
return "", t
}
}
case bytes.IndexByte(nulls, c) != -1:
if null_values[val] {
return yaml_NULL_TAG, nil
}
b := false
if _, err := resolve_bool(val, reflect.ValueOf(&b).Elem(), event); err == nil {
return yaml_BOOL_TAG, b
}
case c == '.':
f := float64(0)
result = &f
if useNumber {
var n Number
result = &n
}
v := reflect.ValueOf(result).Elem()
if _, err := resolve_float(val, v, useNumber, event); err == nil {
return yaml_FLOAT_TAG, v.Interface()
}
case bytes.IndexByte(bools, c) != -1:
b := false
if _, err := resolve_bool(val, reflect.ValueOf(&b).Elem(), event); err == nil {
return yaml_BOOL_TAG, b
}
}
if hasBinaryTag(event) {
bytes, err := decode_binary(event.value, event)
if err == nil {
return yaml_BINARY_TAG, bytes
}
}
return yaml_STR_TAG, val
}

View file

@ -0,0 +1,62 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
import (
"fmt"
"os"
)
func Run_parser(cmd string, args []string) {
for i := 0; i < len(args); i++ {
fmt.Printf("[%d] Scanning '%s'", i, args[i])
file, err := os.Open(args[i])
if err != nil {
panic(fmt.Sprintf("Invalid file '%s': %s", args[i], err.Error()))
}
parser := yaml_parser_t{}
yaml_parser_initialize(&parser)
yaml_parser_set_input_reader(&parser, file)
failed := false
token := yaml_token_t{}
count := 0
for {
if !yaml_parser_scan(&parser, &token) {
failed = true
break
}
if token.token_type == yaml_STREAM_END_TOKEN {
break
}
count++
}
file.Close()
msg := "SUCCESS"
if failed {
msg = "FAILED"
if parser.error != yaml_NO_ERROR {
m := parser.problem_mark
fmt.Printf("ERROR: (%s) %s @ line: %d col: %d\n",
parser.context, parser.problem, m.line, m.column)
}
}
fmt.Printf("%s (%d tokens)\n", msg, count)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,360 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
import (
"reflect"
"sort"
"strings"
"sync"
"unicode"
)
// A field represents a single field found in a struct.
type field struct {
name string
tag bool
index []int
typ reflect.Type
omitEmpty bool
flow bool
}
// byName sorts field by name, breaking ties with depth,
// then breaking ties with "name came from json tag", then
// breaking ties with index sequence.
type byName []field
func (x byName) Len() int { return len(x) }
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byName) Less(i, j int) bool {
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].index) != len(x[j].index) {
return len(x[i].index) < len(x[j].index)
}
if x[i].tag != x[j].tag {
return x[i].tag
}
return byIndex(x).Less(i, j)
}
// byIndex sorts field by index sequence.
type byIndex []field
func (x byIndex) Len() int { return len(x) }
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byIndex) Less(i, j int) bool {
for k, xik := range x[i].index {
if k >= len(x[j].index) {
return false
}
if xik != x[j].index[k] {
return xik < x[j].index[k]
}
}
return len(x[i].index) < len(x[j].index)
}
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type) []field {
// Anonymous fields to explore at the current level and the next.
current := []field{}
next := []field{{typ: t}}
// Count of queued names for current level and the next.
count := map[reflect.Type]int{}
nextCount := map[reflect.Type]int{}
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.PkgPath != "" { // unexported
continue
}
tag := sf.Tag.Get("yaml")
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if !isValidTag(name) {
name = ""
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
tagged := name != ""
if name == "" {
name = sf.Name
}
fields = append(fields, field{name, tagged, index, ft,
opts.Contains("omitempty"), opts.Contains("flow")})
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, field{name: ft.Name(), index: index, typ: ft})
}
}
}
}
sort.Sort(byName(fields))
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
sort.Sort(byIndex(fields))
return fields
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order. The winner
// must therefore be one with the shortest index length. Drop all
// longer entries, which is easy: just truncate the slice.
length := len(fields[0].index)
tagged := -1 // Index of first tagged field.
for i, f := range fields {
if len(f.index) > length {
fields = fields[:i]
break
}
if f.tag {
if tagged >= 0 {
// Multiple tagged fields at the same level: conflict.
// Return no field.
return field{}, false
}
tagged = i
}
}
if tagged >= 0 {
return fields[tagged], true
}
// All remaining fields have the same length. If there's more than one,
// we have a conflict (two fields named "X" at the same level) and we
// return no field.
if len(fields) > 1 {
return field{}, false
}
return fields[0], true
}
var fieldCache struct {
sync.RWMutex
m map[reflect.Type][]field
}
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
func cachedTypeFields(t reflect.Type) []field {
fieldCache.RLock()
f := fieldCache.m[t]
fieldCache.RUnlock()
if f != nil {
return f
}
// Compute fields without lock.
// Might duplicate effort but won't hold other computations back.
f = typeFields(t)
if f == nil {
f = []field{}
}
fieldCache.Lock()
if fieldCache.m == nil {
fieldCache.m = map[reflect.Type][]field{}
}
fieldCache.m[t] = f
fieldCache.Unlock()
return f
}
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}
func fieldByIndex(v reflect.Value, index []int) reflect.Value {
for _, i := range index {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
v = v.Field(i)
}
return v
}
func typeByIndex(t reflect.Type, index []int) reflect.Type {
for _, i := range index {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
t = t.Field(i).Type
}
return t
}
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
type stringValues []reflect.Value
func (sv stringValues) Len() int { return len(sv) }
func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
func (sv stringValues) Less(i, j int) bool {
av, ak := getElem(sv[i])
bv, bk := getElem(sv[j])
if ak == reflect.String && bk == reflect.String {
return av.String() < bv.String()
}
return ak < bk
}
func getElem(v reflect.Value) (reflect.Value, reflect.Kind) {
k := v.Kind()
for k == reflect.Interface || k == reflect.Ptr && !v.IsNil() {
v = v.Elem()
k = v.Kind()
}
return v, k
}
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

View file

@ -0,0 +1,128 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
/*
* Set the writer error and return 0.
*/
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 must be set") /* Write handler must be set. */
}
if emitter.encoding == yaml_ANY_ENCODING {
panic("Encoding must be set") /* Output encoding must be 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. */
// 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
}

View file

@ -0,0 +1,22 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
const (
yaml_VERSION_MAJOR = 0
yaml_VERSION_MINOR = 1
yaml_VERSION_PATCH = 6
yaml_VERSION_STRING = "0.1.6"
)

View file

@ -0,0 +1,891 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
const (
INPUT_RAW_BUFFER_SIZE = 1024
/*
* 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 = 512
/*
* 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)
INITIAL_STACK_SIZE = 16
INITIAL_QUEUE_SIZE = 16
)
func width(b byte) int {
if b&0x80 == 0 {
return 1
}
if b&0xE0 == 0xC0 {
return 2
}
if b&0xF0 == 0xE0 {
return 3
}
if b&0xF8 == 0xF0 {
return 4
}
return 0
}
func copy_bytes(dest []byte, dest_pos *int, src []byte, src_pos *int) {
w := width(src[*src_pos])
switch w {
case 4:
dest[*dest_pos+3] = src[*src_pos+3]
fallthrough
case 3:
dest[*dest_pos+2] = src[*src_pos+2]
fallthrough
case 2:
dest[*dest_pos+1] = src[*src_pos+1]
fallthrough
case 1:
dest[*dest_pos] = src[*src_pos]
default:
panic("invalid width")
}
*dest_pos += w
*src_pos += w
}
// /*
// * Check if the character at the specified position is an alphabetical
// * character, a digit, '_', or '-'.
// */
func is_alpha(b byte) bool {
return (b >= '0' && b <= '9') ||
(b >= 'A' && b <= 'Z') ||
(b >= 'a' && b <= 'z') ||
b == '_' || b == '-'
}
// /*
// * Check if the character at the specified position is a digit.
// */
//
func is_digit(b byte) bool {
return b >= '0' && b <= '9'
}
// /*
// * Get the value of a digit.
// */
//
func as_digit(b byte) int {
return int(b) - '0'
}
// /*
// * Check if the character at the specified position is a hex-digit.
// */
//
func is_hex(b byte) bool {
return (b >= '0' && b <= '9') ||
(b >= 'A' && b <= 'F') ||
(b >= 'a' && b <= 'f')
}
//
// /*
// * Get the value of a hex-digit.
// */
//
func as_hex(b byte) int {
if b >= 'A' && b <= 'F' {
return int(b) - 'A' + 10
} else if b >= 'a' && b <= 'f' {
return int(b) - 'a' + 10
}
return int(b) - '0'
}
// #define AS_HEX_AT(string,offset) \
// (((string).pointer[offset] >= (yaml_char_t) 'A' && \
// (string).pointer[offset] <= (yaml_char_t) 'F') ? \
// ((string).pointer[offset] - (yaml_char_t) 'A' + 10) : \
// ((string).pointer[offset] >= (yaml_char_t) 'a' && \
// (string).pointer[offset] <= (yaml_char_t) 'f') ? \
// ((string).pointer[offset] - (yaml_char_t) 'a' + 10) : \
// ((string).pointer[offset] - (yaml_char_t) '0'))
// /*
// * Check if the character is a line break, space, tab, or NUL.
// */
func is_blankz_at(b []byte, i int) bool {
return is_blank(b[i]) || is_breakz_at(b, i)
}
// /*
// * Check if the character at the specified position is a line break.
// */
func is_break_at(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_breakz_at(b []byte, i int) bool {
return is_break_at(b, i) || is_z(b[i])
}
func is_crlf_at(b []byte, i int) bool {
return b[i] == '\r' && b[i+1] == '\n'
}
// /*
// * Check if the character at the specified position is NUL.
// */
func is_z(b byte) bool {
return b == 0x0
}
// /*
// * Check if the character at the specified position is space.
// */
func is_space(b byte) bool {
return b == ' '
}
//
// /*
// * Check if the character at the specified position is tab.
// */
func is_tab(b byte) bool {
return b == '\t'
}
// /*
// * Check if the character at the specified position is blank (space or tab).
// */
func is_blank(b byte) bool {
return is_space(b) || is_tab(b)
}
// /*
// * Check if the character is ASCII.
// */
func is_ascii(b byte) bool {
return b <= '\x7f'
}
// /*
// * Check if the character can be printed unescaped.
// */
func is_printable_at(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 && /* && . != #xFEFF */
!(b[i+1] == 0xBB && b[i+2] == 0xBF) &&
!(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF))))
}
func insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
// collapse the slice
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
if parser.tokens_head != len(parser.tokens) {
// move the tokens down
copy(parser.tokens, parser.tokens[parser.tokens_head:])
}
// readjust the length
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
}
// /*
// * Check if the character at the specified position is BOM.
// */
//
func is_bom_at(b []byte, i int) bool {
return b[i] == 0xEF && b[i+1] == 0xBB && b[i+2] == 0xBF
}
//
// #ifdef HAVE_CONFIG_H
// #include <config.h>
// #endif
//
// #include "./yaml.h"
//
// #include <assert.h>
// #include <limits.h>
//
// /*
// * Memory management.
// */
//
// yaml_DECLARE(void *)
// yaml_malloc(size_t size);
//
// yaml_DECLARE(void *)
// yaml_realloc(void *ptr, size_t size);
//
// yaml_DECLARE(void)
// yaml_free(void *ptr);
//
// yaml_DECLARE(yaml_char_t *)
// yaml_strdup(const yaml_char_t *);
//
// /*
// * Reader: Ensure that the buffer contains at least `length` characters.
// */
//
// yaml_DECLARE(int)
// yaml_parser_update_buffer(yaml_parser_t *parser, size_t length);
//
// /*
// * Scanner: Ensure that the token stack contains at least one token ready.
// */
//
// yaml_DECLARE(int)
// yaml_parser_fetch_more_tokens(yaml_parser_t *parser);
//
// /*
// * The size of the input raw buffer.
// */
//
// #define INPUT_RAW_BUFFER_SIZE 16384
//
// /*
// * The size of the input buffer.
// *
// * It should be possible to decode the whole raw buffer.
// */
//
// #define INPUT_BUFFER_SIZE (INPUT_RAW_BUFFER_SIZE*3)
//
// /*
// * The size of the output buffer.
// */
//
// #define OUTPUT_BUFFER_SIZE 16384
//
// /*
// * The size of the output raw buffer.
// *
// * It should be possible to encode the whole output buffer.
// */
//
// #define OUTPUT_RAW_BUFFER_SIZE (OUTPUT_BUFFER_SIZE*2+2)
//
// /*
// * The size of other stacks and queues.
// */
//
// #define INITIAL_STACK_SIZE 16
// #define INITIAL_QUEUE_SIZE 16
// #define INITIAL_STRING_SIZE 16
//
// /*
// * Buffer management.
// */
//
// #define BUFFER_INIT(context,buffer,size) \
// (((buffer).start = yaml_malloc(size)) ? \
// ((buffer).last = (buffer).pointer = (buffer).start, \
// (buffer).end = (buffer).start+(size), \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// #define BUFFER_DEL(context,buffer) \
// (yaml_free((buffer).start), \
// (buffer).start = (buffer).pointer = (buffer).end = 0)
//
// /*
// * String management.
// */
//
// typedef struct {
// yaml_char_t *start;
// yaml_char_t *end;
// yaml_char_t *pointer;
// } yaml_string_t;
//
// yaml_DECLARE(int)
// yaml_string_extend(yaml_char_t **start,
// yaml_char_t **pointer, yaml_char_t **end);
//
// yaml_DECLARE(int)
// yaml_string_join(
// yaml_char_t **a_start, yaml_char_t **a_pointer, yaml_char_t **a_end,
// yaml_char_t **b_start, yaml_char_t **b_pointer, yaml_char_t **b_end);
//
// #define NULL_STRING { NULL, NULL, NULL }
//
// #define STRING(string,length) { (string), (string)+(length), (string) }
//
// #define STRING_ASSIGN(value,string,length) \
// ((value).start = (string), \
// (value).end = (string)+(length), \
// (value).pointer = (string))
//
// #define STRING_INIT(context,string,size) \
// (((string).start = yaml_malloc(size)) ? \
// ((string).pointer = (string).start, \
// (string).end = (string).start+(size), \
// memset((string).start, 0, (size)), \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// #define STRING_DEL(context,string) \
// (yaml_free((string).start), \
// (string).start = (string).pointer = (string).end = 0)
//
// #define STRING_EXTEND(context,string) \
// (((string).pointer+5 < (string).end) \
// || yaml_string_extend(&(string).start, \
// &(string).pointer, &(string).end))
//
// #define CLEAR(context,string) \
// ((string).pointer = (string).start, \
// memset((string).start, 0, (string).end-(string).start))
//
// #define JOIN(context,string_a,string_b) \
// ((yaml_string_join(&(string_a).start, &(string_a).pointer, \
// &(string_a).end, &(string_b).start, \
// &(string_b).pointer, &(string_b).end)) ? \
// ((string_b).pointer = (string_b).start, \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// /*
// * String check operations.
// */
//
// /*
// * Check the octet at the specified position.
// */
//
// #define CHECK_AT(string,octet,offset) \
// ((string).pointer[offset] == (yaml_char_t)(octet))
//
// /*
// * Check the current octet in the buffer.
// */
//
// #define CHECK(string,octet) CHECK_AT((string),(octet),0)
//
// /*
// * Check if the character at the specified position is an alphabetical
// * character, a digit, '_', or '-'.
// */
//
// #define IS_ALPHA_AT(string,offset) \
// (((string).pointer[offset] >= (yaml_char_t) '0' && \
// (string).pointer[offset] <= (yaml_char_t) '9') || \
// ((string).pointer[offset] >= (yaml_char_t) 'A' && \
// (string).pointer[offset] <= (yaml_char_t) 'Z') || \
// ((string).pointer[offset] >= (yaml_char_t) 'a' && \
// (string).pointer[offset] <= (yaml_char_t) 'z') || \
// (string).pointer[offset] == '_' || \
// (string).pointer[offset] == '-')
//
// #define IS_ALPHA(string) IS_ALPHA_AT((string),0)
//
// /*
// * Check if the character at the specified position is a digit.
// */
//
// #define IS_DIGIT_AT(string,offset) \
// (((string).pointer[offset] >= (yaml_char_t) '0' && \
// (string).pointer[offset] <= (yaml_char_t) '9'))
//
// #define IS_DIGIT(string) IS_DIGIT_AT((string),0)
//
// /*
// * Get the value of a digit.
// */
//
// #define AS_DIGIT_AT(string,offset) \
// ((string).pointer[offset] - (yaml_char_t) '0')
//
// #define AS_DIGIT(string) AS_DIGIT_AT((string),0)
//
// /*
// * Check if the character at the specified position is a hex-digit.
// */
//
// #define IS_HEX_AT(string,offset) \
// (((string).pointer[offset] >= (yaml_char_t) '0' && \
// (string).pointer[offset] <= (yaml_char_t) '9') || \
// ((string).pointer[offset] >= (yaml_char_t) 'A' && \
// (string).pointer[offset] <= (yaml_char_t) 'F') || \
// ((string).pointer[offset] >= (yaml_char_t) 'a' && \
// (string).pointer[offset] <= (yaml_char_t) 'f'))
//
// #define IS_HEX(string) IS_HEX_AT((string),0)
//
// /*
// * Get the value of a hex-digit.
// */
//
// #define AS_HEX_AT(string,offset) \
// (((string).pointer[offset] >= (yaml_char_t) 'A' && \
// (string).pointer[offset] <= (yaml_char_t) 'F') ? \
// ((string).pointer[offset] - (yaml_char_t) 'A' + 10) : \
// ((string).pointer[offset] >= (yaml_char_t) 'a' && \
// (string).pointer[offset] <= (yaml_char_t) 'f') ? \
// ((string).pointer[offset] - (yaml_char_t) 'a' + 10) : \
// ((string).pointer[offset] - (yaml_char_t) '0'))
//
// #define AS_HEX(string) AS_HEX_AT((string),0)
//
// /*
// * Check if the character is ASCII.
// */
//
// #define IS_ASCII_AT(string,offset) \
// ((string).pointer[offset] <= (yaml_char_t) '\x7F')
//
// #define IS_ASCII(string) IS_ASCII_AT((string),0)
//
// /*
// * Check if the character can be printed unescaped.
// */
//
// #define IS_PRINTABLE_AT(string,offset) \
// (((string).pointer[offset] == 0x0A) /* . == #x0A */ \
// || ((string).pointer[offset] >= 0x20 /* #x20 <= . <= #x7E */ \
// && (string).pointer[offset] <= 0x7E) \
// || ((string).pointer[offset] == 0xC2 /* #0xA0 <= . <= #xD7FF */ \
// && (string).pointer[offset+1] >= 0xA0) \
// || ((string).pointer[offset] > 0xC2 \
// && (string).pointer[offset] < 0xED) \
// || ((string).pointer[offset] == 0xED \
// && (string).pointer[offset+1] < 0xA0) \
// || ((string).pointer[offset] == 0xEE) \
// || ((string).pointer[offset] == 0xEF /* #xE000 <= . <= #xFFFD */ \
// && !((string).pointer[offset+1] == 0xBB /* && . != #xFEFF */ \
// && (string).pointer[offset+2] == 0xBF) \
// && !((string).pointer[offset+1] == 0xBF \
// && ((string).pointer[offset+2] == 0xBE \
// || (string).pointer[offset+2] == 0xBF))))
//
// #define IS_PRINTABLE(string) IS_PRINTABLE_AT((string),0)
//
// /*
// * Check if the character at the specified position is NUL.
// */
//
// #define IS_Z_AT(string,offset) CHECK_AT((string),'\0',(offset))
//
// #define IS_Z(string) IS_Z_AT((string),0)
//
// /*
// * Check if the character at the specified position is BOM.
// */
//
// #define IS_BOM_AT(string,offset) \
// (CHECK_AT((string),'\xEF',(offset)) \
// && CHECK_AT((string),'\xBB',(offset)+1) \
// && CHECK_AT((string),'\xBF',(offset)+2)) /* BOM (#xFEFF) */
//
// #define IS_BOM(string) IS_BOM_AT(string,0)
//
// /*
// * Check if the character at the specified position is space.
// */
//
// #define IS_SPACE_AT(string,offset) CHECK_AT((string),' ',(offset))
//
// #define IS_SPACE(string) IS_SPACE_AT((string),0)
//
// /*
// * Check if the character at the specified position is tab.
// */
//
// #define IS_TAB_AT(string,offset) CHECK_AT((string),'\t',(offset))
//
// #define IS_TAB(string) IS_TAB_AT((string),0)
//
// /*
// * Check if the character at the specified position is blank (space or tab).
// */
//
// #define IS_BLANK_AT(string,offset) \
// (IS_SPACE_AT((string),(offset)) || IS_TAB_AT((string),(offset)))
//
// #define IS_BLANK(string) IS_BLANK_AT((string),0)
//
// /*
// * Check if the character at the specified position is a line break.
// */
//
// #define IS_BREAK_AT(string,offset) \
// (CHECK_AT((string),'\r',(offset)) /* CR (#xD)*/ \
// || CHECK_AT((string),'\n',(offset)) /* LF (#xA) */ \
// || (CHECK_AT((string),'\xC2',(offset)) \
// && CHECK_AT((string),'\x85',(offset)+1)) /* NEL (#x85) */ \
// || (CHECK_AT((string),'\xE2',(offset)) \
// && CHECK_AT((string),'\x80',(offset)+1) \
// && CHECK_AT((string),'\xA8',(offset)+2)) /* LS (#x2028) */ \
// || (CHECK_AT((string),'\xE2',(offset)) \
// && CHECK_AT((string),'\x80',(offset)+1) \
// && CHECK_AT((string),'\xA9',(offset)+2))) /* PS (#x2029) */
//
// #define IS_BREAK(string) IS_BREAK_AT((string),0)
//
// #define IS_CRLF_AT(string,offset) \
// (CHECK_AT((string),'\r',(offset)) && CHECK_AT((string),'\n',(offset)+1))
//
// #define IS_CRLF(string) IS_CRLF_AT((string),0)
//
// /*
// * Check if the character is a line break or NUL.
// */
//
// #define IS_BREAKZ_AT(string,offset) \
// (IS_BREAK_AT((string),(offset)) || IS_Z_AT((string),(offset)))
//
// #define IS_BREAKZ(string) IS_BREAKZ_AT((string),0)
//
// /*
// * Check if the character is a line break, space, or NUL.
// */
//
// #define IS_SPACEZ_AT(string,offset) \
// (IS_SPACE_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset)))
//
// #define IS_SPACEZ(string) IS_SPACEZ_AT((string),0)
//
// /*
// * Check if the character is a line break, space, tab, or NUL.
// */
//
// #define IS_BLANKZ_AT(string,offset) \
// (IS_BLANK_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset)))
//
// #define IS_BLANKZ(string) IS_BLANKZ_AT((string),0)
//
// /*
// * Determine the width of the character.
// */
//
// #define WIDTH_AT(string,offset) \
// (((string).pointer[offset] & 0x80) == 0x00 ? 1 : \
// ((string).pointer[offset] & 0xE0) == 0xC0 ? 2 : \
// ((string).pointer[offset] & 0xF0) == 0xE0 ? 3 : \
// ((string).pointer[offset] & 0xF8) == 0xF0 ? 4 : 0)
//
// #define WIDTH(string) WIDTH_AT((string),0)
//
// /*
// * Move the string pointer to the next character.
// */
//
// #define MOVE(string) ((string).pointer += WIDTH((string)))
//
// /*
// * Copy a character and move the pointers of both strings.
// */
//
// #define COPY(string_a,string_b) \
// ((*(string_b).pointer & 0x80) == 0x00 ? \
// (*((string_a).pointer++) = *((string_b).pointer++)) : \
// (*(string_b).pointer & 0xE0) == 0xC0 ? \
// (*((string_a).pointer++) = *((string_b).pointer++), \
// *((string_a).pointer++) = *((string_b).pointer++)) : \
// (*(string_b).pointer & 0xF0) == 0xE0 ? \
// (*((string_a).pointer++) = *((string_b).pointer++), \
// *((string_a).pointer++) = *((string_b).pointer++), \
// *((string_a).pointer++) = *((string_b).pointer++)) : \
// (*(string_b).pointer & 0xF8) == 0xF0 ? \
// (*((string_a).pointer++) = *((string_b).pointer++), \
// *((string_a).pointer++) = *((string_b).pointer++), \
// *((string_a).pointer++) = *((string_b).pointer++), \
// *((string_a).pointer++) = *((string_b).pointer++)) : 0)
//
// /*
// * Stack and queue management.
// */
//
// yaml_DECLARE(int)
// yaml_stack_extend(void **start, void **top, void **end);
//
// yaml_DECLARE(int)
// yaml_queue_extend(void **start, void **head, void **tail, void **end);
//
// #define STACK_INIT(context,stack,size) \
// (((stack).start = yaml_malloc((size)*sizeof(*(stack).start))) ? \
// ((stack).top = (stack).start, \
// (stack).end = (stack).start+(size), \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// #define STACK_DEL(context,stack) \
// (yaml_free((stack).start), \
// (stack).start = (stack).top = (stack).end = 0)
//
// #define STACK_EMPTY(context,stack) \
// ((stack).start == (stack).top)
//
// #define PUSH(context,stack,value) \
// (((stack).top != (stack).end \
// || yaml_stack_extend((void **)&(stack).start, \
// (void **)&(stack).top, (void **)&(stack).end)) ? \
// (*((stack).top++) = value, \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// #define POP(context,stack) \
// (*(--(stack).top))
//
// #define QUEUE_INIT(context,queue,size) \
// (((queue).start = yaml_malloc((size)*sizeof(*(queue).start))) ? \
// ((queue).head = (queue).tail = (queue).start, \
// (queue).end = (queue).start+(size), \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// #define QUEUE_DEL(context,queue) \
// (yaml_free((queue).start), \
// (queue).start = (queue).head = (queue).tail = (queue).end = 0)
//
// #define QUEUE_EMPTY(context,queue) \
// ((queue).head == (queue).tail)
//
// #define ENQUEUE(context,queue,value) \
// (((queue).tail != (queue).end \
// || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \
// (void **)&(queue).tail, (void **)&(queue).end)) ? \
// (*((queue).tail++) = value, \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// #define DEQUEUE(context,queue) \
// (*((queue).head++))
//
// #define QUEUE_INSERT(context,queue,index,value) \
// (((queue).tail != (queue).end \
// || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \
// (void **)&(queue).tail, (void **)&(queue).end)) ? \
// (memmove((queue).head+(index)+1,(queue).head+(index), \
// ((queue).tail-(queue).head-(index))*sizeof(*(queue).start)), \
// *((queue).head+(index)) = value, \
// (queue).tail++, \
// 1) : \
// ((context)->error = yaml_MEMORY_ERROR, \
// 0))
//
// /*
// * Token initializers.
// */
//
// #define TOKEN_INIT(token,token_type,token_start_mark,token_end_mark) \
// (memset(&(token), 0, sizeof(yaml_token_t)), \
// (token).type = (token_type), \
// (token).start_mark = (token_start_mark), \
// (token).end_mark = (token_end_mark))
//
// #define STREAM_START_TOKEN_INIT(token,token_encoding,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_STREAM_START_TOKEN,(start_mark),(end_mark)), \
// (token).data.stream_start.encoding = (token_encoding))
//
// #define STREAM_END_TOKEN_INIT(token,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_STREAM_END_TOKEN,(start_mark),(end_mark)))
//
// #define ALIAS_TOKEN_INIT(token,token_value,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_ALIAS_TOKEN,(start_mark),(end_mark)), \
// (token).data.alias.value = (token_value))
//
// #define ANCHOR_TOKEN_INIT(token,token_value,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_ANCHOR_TOKEN,(start_mark),(end_mark)), \
// (token).data.anchor.value = (token_value))
//
// #define TAG_TOKEN_INIT(token,token_handle,token_suffix,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_TAG_TOKEN,(start_mark),(end_mark)), \
// (token).data.tag.handle = (token_handle), \
// (token).data.tag.suffix = (token_suffix))
//
// #define SCALAR_TOKEN_INIT(token,token_value,token_length,token_style,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_SCALAR_TOKEN,(start_mark),(end_mark)), \
// (token).data.scalar.value = (token_value), \
// (token).data.scalar.length = (token_length), \
// (token).data.scalar.style = (token_style))
//
// #define VERSION_DIRECTIVE_TOKEN_INIT(token,token_major,token_minor,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_VERSION_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \
// (token).data.version_directive.major = (token_major), \
// (token).data.version_directive.minor = (token_minor))
//
// #define TAG_DIRECTIVE_TOKEN_INIT(token,token_handle,token_prefix,start_mark,end_mark) \
// (TOKEN_INIT((token),yaml_TAG_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \
// (token).data.tag_directive.handle = (token_handle), \
// (token).data.tag_directive.prefix = (token_prefix))
//
// /*
// * Event initializers.
// */
//
// #define EVENT_INIT(event,event_type,event_start_mark,event_end_mark) \
// (memset(&(event), 0, sizeof(yaml_event_t)), \
// (event).type = (event_type), \
// (event).start_mark = (event_start_mark), \
// (event).end_mark = (event_end_mark))
//
// #define STREAM_START_EVENT_INIT(event,event_encoding,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_STREAM_START_EVENT,(start_mark),(end_mark)), \
// (event).data.stream_start.encoding = (event_encoding))
//
// #define STREAM_END_EVENT_INIT(event,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_STREAM_END_EVENT,(start_mark),(end_mark)))
//
// #define DOCUMENT_START_EVENT_INIT(event,event_version_directive, \
// event_tag_directives_start,event_tag_directives_end,event_implicit,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_DOCUMENT_START_EVENT,(start_mark),(end_mark)), \
// (event).data.document_start.version_directive = (event_version_directive), \
// (event).data.document_start.tag_directives.start = (event_tag_directives_start), \
// (event).data.document_start.tag_directives.end = (event_tag_directives_end), \
// (event).data.document_start.implicit = (event_implicit))
//
// #define DOCUMENT_END_EVENT_INIT(event,event_implicit,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_DOCUMENT_END_EVENT,(start_mark),(end_mark)), \
// (event).data.document_end.implicit = (event_implicit))
//
// #define ALIAS_EVENT_INIT(event,event_anchor,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_ALIAS_EVENT,(start_mark),(end_mark)), \
// (event).data.alias.anchor = (event_anchor))
//
// #define SCALAR_EVENT_INIT(event,event_anchor,event_tag,event_value,event_length, \
// event_plain_implicit, event_quoted_implicit,event_style,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_SCALAR_EVENT,(start_mark),(end_mark)), \
// (event).data.scalar.anchor = (event_anchor), \
// (event).data.scalar.tag = (event_tag), \
// (event).data.scalar.value = (event_value), \
// (event).data.scalar.length = (event_length), \
// (event).data.scalar.plain_implicit = (event_plain_implicit), \
// (event).data.scalar.quoted_implicit = (event_quoted_implicit), \
// (event).data.scalar.style = (event_style))
//
// #define SEQUENCE_START_EVENT_INIT(event,event_anchor,event_tag, \
// event_implicit,event_style,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_SEQUENCE_START_EVENT,(start_mark),(end_mark)), \
// (event).data.sequence_start.anchor = (event_anchor), \
// (event).data.sequence_start.tag = (event_tag), \
// (event).data.sequence_start.implicit = (event_implicit), \
// (event).data.sequence_start.style = (event_style))
//
// #define SEQUENCE_END_EVENT_INIT(event,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_SEQUENCE_END_EVENT,(start_mark),(end_mark)))
//
// #define MAPPING_START_EVENT_INIT(event,event_anchor,event_tag, \
// event_implicit,event_style,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_MAPPING_START_EVENT,(start_mark),(end_mark)), \
// (event).data.mapping_start.anchor = (event_anchor), \
// (event).data.mapping_start.tag = (event_tag), \
// (event).data.mapping_start.implicit = (event_implicit), \
// (event).data.mapping_start.style = (event_style))
//
// #define MAPPING_END_EVENT_INIT(event,start_mark,end_mark) \
// (EVENT_INIT((event),yaml_MAPPING_END_EVENT,(start_mark),(end_mark)))
//
// /*
// * Document initializer.
// */
//
// #define DOCUMENT_INIT(document,document_nodes_start,document_nodes_end, \
// document_version_directive,document_tag_directives_start, \
// document_tag_directives_end,document_start_implicit, \
// document_end_implicit,document_start_mark,document_end_mark) \
// (memset(&(document), 0, sizeof(yaml_document_t)), \
// (document).nodes.start = (document_nodes_start), \
// (document).nodes.end = (document_nodes_end), \
// (document).nodes.top = (document_nodes_start), \
// (document).version_directive = (document_version_directive), \
// (document).tag_directives.start = (document_tag_directives_start), \
// (document).tag_directives.end = (document_tag_directives_end), \
// (document).start_implicit = (document_start_implicit), \
// (document).end_implicit = (document_end_implicit), \
// (document).start_mark = (document_start_mark), \
// (document).end_mark = (document_end_mark))
//
// /*
// * Node initializers.
// */
//
// #define NODE_INIT(node,node_type,node_tag,node_start_mark,node_end_mark) \
// (memset(&(node), 0, sizeof(yaml_node_t)), \
// (node).type = (node_type), \
// (node).tag = (node_tag), \
// (node).start_mark = (node_start_mark), \
// (node).end_mark = (node_end_mark))
//
// #define SCALAR_NODE_INIT(node,node_tag,node_value,node_length, \
// node_style,start_mark,end_mark) \
// (NODE_INIT((node),yaml_SCALAR_NODE,(node_tag),(start_mark),(end_mark)), \
// (node).data.scalar.value = (node_value), \
// (node).data.scalar.length = (node_length), \
// (node).data.scalar.style = (node_style))
//
// #define SEQUENCE_NODE_INIT(node,node_tag,node_items_start,node_items_end, \
// node_style,start_mark,end_mark) \
// (NODE_INIT((node),yaml_SEQUENCE_NODE,(node_tag),(start_mark),(end_mark)), \
// (node).data.sequence.items.start = (node_items_start), \
// (node).data.sequence.items.end = (node_items_end), \
// (node).data.sequence.items.top = (node_items_start), \
// (node).data.sequence.style = (node_style))
//
// #define MAPPING_NODE_INIT(node,node_tag,node_pairs_start,node_pairs_end, \
// node_style,start_mark,end_mark) \
// (NODE_INIT((node),yaml_MAPPING_NODE,(node_tag),(start_mark),(end_mark)), \
// (node).data.mapping.pairs.start = (node_pairs_start), \
// (node).data.mapping.pairs.end = (node_pairs_end), \
// (node).data.mapping.pairs.top = (node_pairs_start), \
// (node).data.mapping.style = (node_style))
//

View file

@ -0,0 +1,953 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package candiedyaml
import (
"fmt"
"io"
)
/** The version directive data. */
type yaml_version_directive_t struct {
major int // The major version number
minor int // The minor version number
}
/** The tag directive data. */
type yaml_tag_directive_t struct {
handle []byte // The tag handle
prefix []byte // The tag prefix
}
/** The stream encoding. */
type yaml_encoding_t int
const (
/** Let the parser choose the encoding. */
yaml_ANY_ENCODING yaml_encoding_t = iota
/** The defau lt UTF-8 encoding. */
yaml_UTF8_ENCODING
/** The UTF-16-LE encoding with BOM. */
yaml_UTF16LE_ENCODING
/** The UTF-16-BE encoding with BOM. */
yaml_UTF16BE_ENCODING
)
/** Line break types. */
type yaml_break_t int
const (
yaml_ANY_BREAK yaml_break_t = iota /** Let the parser choose the break type. */
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). */
)
/** Many bad things could happen with the parser and emitter. */
type YAML_error_type_t int
const (
/** No error is produced. */
yaml_NO_ERROR YAML_error_type_t = iota
/** Cannot allocate or reallocate a block of memory. */
yaml_MEMORY_ERROR
/** Cannot read or decode the input stream. */
yaml_READER_ERROR
/** Cannot scan the input stream. */
yaml_SCANNER_ERROR
/** Cannot parse the input stream. */
yaml_PARSER_ERROR
/** Cannot compose a YAML document. */
yaml_COMPOSER_ERROR
/** Cannot write to the output stream. */
yaml_WRITER_ERROR
/** Cannot emit a YAML stream. */
yaml_EMITTER_ERROR
)
/** The pointer position. */
type YAML_mark_t struct {
/** The position index. */
index int
/** The position line. */
line int
/** The position column. */
column int
}
func (m YAML_mark_t) String() string {
return fmt.Sprintf("line %d, column %d", m.line, m.column)
}
/** @} */
/**
* @defgroup styles Node Styles
* @{
*/
type yaml_style_t int
/** Scalar styles. */
type yaml_scalar_style_t yaml_style_t
const (
/** Let the emitter choose the style. */
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota
/** The plain scalar style. */
yaml_PLAIN_SCALAR_STYLE
/** The single-quoted scalar style. */
yaml_SINGLE_QUOTED_SCALAR_STYLE
/** The double-quoted scalar style. */
yaml_DOUBLE_QUOTED_SCALAR_STYLE
/** The literal scalar style. */
yaml_LITERAL_SCALAR_STYLE
/** The folded scalar style. */
yaml_FOLDED_SCALAR_STYLE
)
/** Sequence styles. */
type yaml_sequence_style_t yaml_style_t
const (
/** Let the emitter choose the style. */
yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota
/** The block sequence style. */
yaml_BLOCK_SEQUENCE_STYLE
/** The flow sequence style. */
yaml_FLOW_SEQUENCE_STYLE
)
/** Mapping styles. */
type yaml_mapping_style_t yaml_style_t
const (
/** Let the emitter choose the style. */
yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota
/** The block mapping style. */
yaml_BLOCK_MAPPING_STYLE
/** The flow mapping style. */
yaml_FLOW_MAPPING_STYLE
/* yaml_FLOW_SET_MAPPING_STYLE */
)
/** @} */
/**
* @defgroup tokens Tokens
* @{
*/
/** Token types. */
type yaml_token_type_t int
const (
/** An empty token. */
yaml_NO_TOKEN yaml_token_type_t = iota
/** A STREAM-START token. */
yaml_STREAM_START_TOKEN
/** A STREAM-END token. */
yaml_STREAM_END_TOKEN
/** A VERSION-DIRECTIVE token. */
yaml_VERSION_DIRECTIVE_TOKEN
/** A TAG-DIRECTIVE token. */
yaml_TAG_DIRECTIVE_TOKEN
/** A DOCUMENT-START token. */
yaml_DOCUMENT_START_TOKEN
/** A DOCUMENT-END token. */
yaml_DOCUMENT_END_TOKEN
/** A BLOCK-SEQUENCE-START token. */
yaml_BLOCK_SEQUENCE_START_TOKEN
/** A BLOCK-SEQUENCE-END token. */
yaml_BLOCK_MAPPING_START_TOKEN
/** A BLOCK-END token. */
yaml_BLOCK_END_TOKEN
/** A FLOW-SEQUENCE-START token. */
yaml_FLOW_SEQUENCE_START_TOKEN
/** A FLOW-SEQUENCE-END token. */
yaml_FLOW_SEQUENCE_END_TOKEN
/** A FLOW-MAPPING-START token. */
yaml_FLOW_MAPPING_START_TOKEN
/** A FLOW-MAPPING-END token. */
yaml_FLOW_MAPPING_END_TOKEN
/** A BLOCK-ENTRY token. */
yaml_BLOCK_ENTRY_TOKEN
/** A FLOW-ENTRY token. */
yaml_FLOW_ENTRY_TOKEN
/** A KEY token. */
yaml_KEY_TOKEN
/** A VALUE token. */
yaml_VALUE_TOKEN
/** An ALIAS token. */
yaml_ALIAS_TOKEN
/** An ANCHOR token. */
yaml_ANCHOR_TOKEN
/** A TAG token. */
yaml_TAG_TOKEN
/** A SCALAR token. */
yaml_SCALAR_TOKEN
)
/** The token structure. */
type yaml_token_t struct {
/** The token type. */
token_type yaml_token_type_t
/** The token data. */
/** The stream start (for @c yaml_STREAM_START_TOKEN). */
encoding yaml_encoding_t
/** The alias (for @c yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN,yaml_TAG_TOKEN ). */
/** The anchor (for @c ). */
/** The scalar value (for @c ). */
value []byte
/** The tag suffix. */
suffix []byte
/** The scalar value (for @c yaml_SCALAR_TOKEN). */
/** The scalar style. */
style yaml_scalar_style_t
/** The version directive (for @c yaml_VERSION_DIRECTIVE_TOKEN). */
version_directive yaml_version_directive_t
/** The tag directive (for @c yaml_TAG_DIRECTIVE_TOKEN). */
prefix []byte
/** The beginning of the token. */
start_mark YAML_mark_t
/** The end of the token. */
end_mark YAML_mark_t
major, minor int
}
/**
* @defgroup events Events
* @{
*/
/** Event types. */
type yaml_event_type_t int
const (
/** An empty event. */
yaml_NO_EVENT yaml_event_type_t = iota
/** A STREAM-START event. */
yaml_STREAM_START_EVENT
/** A STREAM-END event. */
yaml_STREAM_END_EVENT
/** A DOCUMENT-START event. */
yaml_DOCUMENT_START_EVENT
/** A DOCUMENT-END event. */
yaml_DOCUMENT_END_EVENT
/** An ALIAS event. */
yaml_ALIAS_EVENT
/** A SCALAR event. */
yaml_SCALAR_EVENT
/** A SEQUENCE-START event. */
yaml_SEQUENCE_START_EVENT
/** A SEQUENCE-END event. */
yaml_SEQUENCE_END_EVENT
/** A MAPPING-START event. */
yaml_MAPPING_START_EVENT
/** A MAPPING-END event. */
yaml_MAPPING_END_EVENT
)
/** The event structure. */
type yaml_event_t struct {
/** The event type. */
event_type yaml_event_type_t
/** The stream parameters (for @c yaml_STREAM_START_EVENT). */
encoding yaml_encoding_t
/** The document parameters (for @c yaml_DOCUMENT_START_EVENT). */
version_directive *yaml_version_directive_t
/** The beginning and end of the tag directives list. */
tag_directives []yaml_tag_directive_t
/** The document parameters (for @c yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT,yaml_MAPPING_START_EVENT). */
/** Is the document indicator implicit? */
implicit bool
/** The alias parameters (for @c yaml_ALIAS_EVENT,yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). */
/** The anchor. */
anchor []byte
/** The scalar parameters (for @c yaml_SCALAR_EVENT,yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). */
/** The tag. */
tag []byte
/** The scalar value. */
value []byte
/** Is the tag optional for the plain style? */
plain_implicit bool
/** Is the tag optional for any non-plain style? */
quoted_implicit bool
/** The sequence parameters (for @c yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). */
/** The sequence style. */
/** The scalar style. */
style yaml_style_t
/** The beginning of the event. */
start_mark, end_mark YAML_mark_t
}
/**
* @defgroup nodes Nodes
* @{
*/
const (
/** The tag @c !!null with the only possible value: @c null. */
yaml_NULL_TAG = "tag:yaml.org,2002:null"
/** The tag @c !!bool with the values: @c true and @c falce. */
yaml_BOOL_TAG = "tag:yaml.org,2002:bool"
/** The tag @c !!str for string values. */
yaml_STR_TAG = "tag:yaml.org,2002:str"
/** The tag @c !!int for integer values. */
yaml_INT_TAG = "tag:yaml.org,2002:int"
/** The tag @c !!float for float values. */
yaml_FLOAT_TAG = "tag:yaml.org,2002:float"
/** The tag @c !!timestamp for date and time values. */
yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp"
/** The tag @c !!seq is used to denote sequences. */
yaml_SEQ_TAG = "tag:yaml.org,2002:seq"
/** The tag @c !!map is used to denote mapping. */
yaml_MAP_TAG = "tag:yaml.org,2002:map"
/** The default scalar tag is @c !!str. */
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG
/** The default sequence tag is @c !!seq. */
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG
/** The default mapping tag is @c !!map. */
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
)
/** Node types. */
type yaml_node_type_t int
const (
/** An empty node. */
yaml_NO_NODE yaml_node_type_t = iota
/** A scalar node. */
yaml_SCALAR_NODE
/** A sequence node. */
yaml_SEQUENCE_NODE
/** A mapping node. */
yaml_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 {
/** The key of the element. */
key int
/** The value of the element. */
value int
}
/** The node structure. */
type yaml_node_t struct {
/** The node type. */
node_type yaml_node_type_t
/** The node tag. */
tag []byte
/** The scalar parameters (for @c yaml_SCALAR_NODE). */
scalar struct {
/** The scalar value. */
value []byte
/** The scalar style. */
style yaml_scalar_style_t
}
/** The sequence parameters (for @c yaml_SEQUENCE_NODE). */
sequence struct {
/** The stack of sequence items. */
items []yaml_node_item_t
/** The sequence style. */
style yaml_sequence_style_t
}
/** The mapping parameters (for @c yaml_MAPPING_NODE). */
mapping struct {
/** The stack of mapping pairs (key, value). */
pairs []yaml_node_pair_t
/** The mapping style. */
style yaml_mapping_style_t
}
/** The beginning of the node. */
start_mark YAML_mark_t
/** The end of the node. */
end_mark YAML_mark_t
}
/** 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. */
tags []yaml_tag_directive_t
/** Is the document start indicator implicit? */
start_implicit bool
/** Is the document end indicator implicit? */
end_implicit bool
/** The beginning of the document. */
start_mark YAML_mark_t
/** The end of the document. */
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 @a size bytes to the @a
* buffer. The number of written bytes should be set to the @a length variable.
*
* @param[in,out] data A pointer to an application data specified by
* yaml_parser_set_input().
* @param[out] buffer The buffer to write the data from the source.
* @param[in] size The size of the buffer.
* @param[out] size_read The actual number of bytes read from the source.
*
* @returns On success, the handler should return @c 1. If the handler failed,
* the returned value should be @c 0. On EOF, the handler should set the
* @a size_read to @c 0 and return @c 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 {
/** Is a simple key possible? */
possible bool
/** Is a simple key required? */
required bool
/** The number of the token. */
token_number int
/** The position mark. */
mark YAML_mark_t
}
/**
* The states of the parser.
*/
type yaml_parser_state_t int
const (
/** Expect STREAM-START. */
yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota
/** Expect the beginning of an implicit document. */
yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE
/** Expect DOCUMENT-START. */
yaml_PARSE_DOCUMENT_START_STATE
/** Expect the content of a document. */
yaml_PARSE_DOCUMENT_CONTENT_STATE
/** Expect DOCUMENT-END. */
yaml_PARSE_DOCUMENT_END_STATE
/** Expect a block node. */
yaml_PARSE_BLOCK_NODE_STATE
/** Expect a block node or indentless sequence. */
yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE
/** Expect a flow node. */
yaml_PARSE_FLOW_NODE_STATE
/** Expect the first entry of a block sequence. */
yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE
/** Expect an entry of a block sequence. */
yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE
/** Expect an entry of an indentless sequence. */
yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE
/** Expect the first key of a block mapping. */
yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE
/** Expect a block mapping key. */
yaml_PARSE_BLOCK_MAPPING_KEY_STATE
/** Expect a block mapping value. */
yaml_PARSE_BLOCK_MAPPING_VALUE_STATE
/** Expect the first entry of a flow sequence. */
yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE
/** Expect an entry of a flow sequence. */
yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE
/** Expect a key of an ordered mapping. */
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE
/** Expect a value of an ordered mapping. */
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE
/** Expect the and of an ordered mapping entry. */
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE
/** Expect the first key of a flow mapping. */
yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE
/** Expect a key of a flow mapping. */
yaml_PARSE_FLOW_MAPPING_KEY_STATE
/** Expect a value of a flow mapping. */
yaml_PARSE_FLOW_MAPPING_VALUE_STATE
/** Expect an empty value of a flow mapping. */
yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE
/** Expect nothing. */
yaml_PARSE_END_STATE
)
/**
* This structure holds aliases data.
*/
type yaml_alias_data_t struct {
/** The anchor. */
anchor []byte
/** The node id. */
index int
/** The anchor mark. */
mark YAML_mark_t
}
/**
* The parser structure.
*
* All members are internal. Manage the structure using the @c yaml_parser_
* family of functions.
*/
type yaml_parser_t struct {
/**
* @name Error handling
* @{
*/
/** Error type. */
error YAML_error_type_t
/** Error description. */
problem string
/** The byte about which the problem occured. */
problem_offset int
/** The problematic value (@c -1 is none). */
problem_value int
/** The problem position. */
problem_mark YAML_mark_t
/** The error context. */
context string
/** The context position. */
context_mark YAML_mark_t
/**
* @}
*/
/**
* @name Reader stuff
* @{
*/
/** Read handler. */
read_handler yaml_read_handler_t
/** Reader input data. */
input_reader io.Reader
input []byte
input_pos int
/** EOF flag */
eof bool
/** The working buffer. */
buffer []byte
buffer_pos int
/* The number of unread characters in the buffer. */
unread int
/** The raw buffer. */
raw_buffer []byte
raw_buffer_pos int
/** The input encoding. */
encoding yaml_encoding_t
/** The offset of the current position (in bytes). */
offset int
/** The mark of the current position. */
mark YAML_mark_t
/**
* @}
*/
/**
* @name Scanner stuff
* @{
*/
/** Have we started to scan the input stream? */
stream_start_produced bool
/** Have we reached the end of the input stream? */
stream_end_produced bool
/** The number of unclosed '[' and '{' indicators. */
flow_level int
/** The tokens queue. */
tokens []yaml_token_t
tokens_head int
/** The number of tokens fetched from the queue. */
tokens_parsed int
/* Does the tokens queue contain a token ready for dequeueing. */
token_available bool
/** The indentation levels stack. */
indents []int
/** The current indentation level. */
indent int
/** May a simple key occur at the current position? */
simple_key_allowed bool
/** The stack of simple keys. */
simple_keys []yaml_simple_key_t
/**
* @}
*/
/**
* @name Parser stuff
* @{
*/
/** The parser states stack. */
states []yaml_parser_state_t
/** The current parser state. */
state yaml_parser_state_t
/** The stack of marks. */
marks []YAML_mark_t
/** The list of TAG directives. */
tag_directives []yaml_tag_directive_t
/**
* @}
*/
/**
* @name Dumper stuff
* @{
*/
/** The alias data. */
aliases []yaml_alias_data_t
/** The currently parsed document. */
document *yaml_document_t
/**
* @}
*/
}
/**
* 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
/** The emitter states. */
type yaml_emitter_state_t int
const (
/** Expect STREAM-START. */
yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota
/** Expect the first DOCUMENT-START or STREAM-END. */
yaml_EMIT_FIRST_DOCUMENT_START_STATE
/** Expect DOCUMENT-START or STREAM-END. */
yaml_EMIT_DOCUMENT_START_STATE
/** Expect the content of a document. */
yaml_EMIT_DOCUMENT_CONTENT_STATE
/** Expect DOCUMENT-END. */
yaml_EMIT_DOCUMENT_END_STATE
/** Expect the first item of a flow sequence. */
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE
/** Expect an item of a flow sequence. */
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE
/** Expect the first key of a flow mapping. */
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE
/** Expect a key of a flow mapping. */
yaml_EMIT_FLOW_MAPPING_KEY_STATE
/** Expect a value for a simple key of a flow mapping. */
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE
/** Expect a value of a flow mapping. */
yaml_EMIT_FLOW_MAPPING_VALUE_STATE
/** Expect the first item of a block sequence. */
yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE
/** Expect an item of a block sequence. */
yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE
/** Expect the first key of a block mapping. */
yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE
/** Expect the key of a block mapping. */
yaml_EMIT_BLOCK_MAPPING_KEY_STATE
/** Expect a value for a simple key of a block mapping. */
yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE
/** Expect a value of a block mapping. */
yaml_EMIT_BLOCK_MAPPING_VALUE_STATE
/** Expect nothing. */
yaml_EMIT_END_STATE
)
/**
* The emitter structure.
*
* All members are internal. Manage the structure using the @c yaml_emitter_
* family of functions.
*/
type yaml_emitter_t struct {
/**
* @name Error handling
* @{
*/
/** Error type. */
error YAML_error_type_t
/** Error description. */
problem string
/**
* @}
*/
/**
* @name Writer stuff
* @{
*/
/** Write handler. */
write_handler yaml_write_handler_t
/** Standard (string or file) output data. */
output_buffer *[]byte
output_writer io.Writer
/** The working buffer. */
buffer []byte
buffer_pos int
/** The raw buffer. */
raw_buffer []byte
raw_buffer_pos int
/** The stream encoding. */
encoding yaml_encoding_t
/**
* @}
*/
/**
* @name Emitter stuff
* @{
*/
/** If the output is in the canonical style? */
canonical bool
/** The number of indentation spaces. */
best_indent int
/** The preferred width of the output lines. */
best_width int
/** Allow unescaped non-ASCII characters? */
unicode bool
/** The preferred line break. */
line_break yaml_break_t
/** The stack of states. */
states []yaml_emitter_state_t
/** The current emitter state. */
state yaml_emitter_state_t
/** The event queue. */
events []yaml_event_t
events_head int
/** The stack of indentation levels. */
indents []int
/** The list of tag directives. */
tag_directives []yaml_tag_directive_t
/** The current indentation level. */
indent int
/** The current flow level. */
flow_level int
/** Is it the document root context? */
root_context bool
/** Is it a sequence context? */
sequence_context bool
/** Is it a mapping context? */
mapping_context bool
/** Is it a simple mapping key context? */
simple_key_context bool
/** The current line. */
line int
/** The current column. */
column int
/** If the last character was a whitespace? */
whitespace bool
/** If the last character was an indentation character (' ', '-', '?', ':')? */
indention bool
/** If an explicit document end is required? */
open_ended bool
/** Anchor analysis. */
anchor_data struct {
/** The anchor value. */
anchor []byte
/** Is it an alias? */
alias bool
}
/** Tag analysis. */
tag_data struct {
/** The tag handle. */
handle []byte
/** The tag suffix. */
suffix []byte
}
/** Scalar analysis. */
scalar_data struct {
/** The scalar value. */
value []byte
/** Does the scalar contain line breaks? */
multiline bool
/** Can the scalar be expessed in the flow plain style? */
flow_plain_allowed bool
/** Can the scalar be expressed in the block plain style? */
block_plain_allowed bool
/** Can the scalar be expressed in the single quoted style? */
single_quoted_allowed bool
/** Can the scalar be expressed in the literal or folded styles? */
block_allowed bool
/** The output style. */
style yaml_scalar_style_t
}
/**
* @}
*/
/**
* @name Dumper stuff
* @{
*/
/** If the stream was already opened? */
opened bool
/** If the stream was already closed? */
closed bool
/** The information associated with the document nodes. */
anchors *struct {
/** The number of references. */
references int
/** The anchor id. */
anchor int
/** If the node has been emitted? */
serialized bool
}
/** The last assigned anchor id. */
last_anchor_id int
/** The currently emitted document. */
document *yaml_document_t
/**
* @}
*/
}

50
vendor/github.com/ghodss/yaml/LICENSE generated vendored Normal file
View file

@ -0,0 +1,50 @@
The MIT License (MIT)
Copyright (c) 2014 Sam Ghods
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

116
vendor/github.com/ghodss/yaml/README.md generated vendored Normal file
View file

@ -0,0 +1,116 @@
# YAML marshaling and unmarshaling support for Go
[![Build Status](https://travis-ci.org/ghodss/yaml.svg)](https://travis-ci.org/ghodss/yaml)
## Introduction
A wrapper around [candiedyaml](https://github.com/cloudfoundry-incubator/candiedyaml) designed to enable a better way of handling YAML when marshaling to and from structs.
In short, this library first converts YAML to JSON using candiedyaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike candiedyaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
## Compatibility
This package uses [candiedyaml](https://github.com/cloudfoundry-incubator/candiedyaml) and therefore supports [everything candiedyaml supports](https://github.com/cloudfoundry-incubator/candiedyaml#candiedyaml).
## Caveats
**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, candiedyaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example:
```
BAD:
exampleKey: !!binary gIGC
GOOD:
exampleKey: gIGC
... and decode the base64 data in your code.
```
**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys.
## Installation and usage
To install, run:
```
$ go get github.com/ghodss/yaml
```
And import using:
```
import "github.com/ghodss/yaml"
```
Usage is very similar to the JSON library:
```go
import (
"fmt"
"github.com/ghodss/yaml"
)
type Person struct {
Name string `json:"name"` // Affects YAML field names too.
Age int `json:"name"`
}
func main() {
// Marshal a Person struct to YAML.
p := Person{"John", 30}
y, err := yaml.Marshal(p)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(y))
/* Output:
name: John
age: 30
*/
// Unmarshal the YAML back into a Person struct.
var p2 Person
err := yaml.Unmarshal(y, &p2)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(p2)
/* Output:
{John 30}
*/
}
```
`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available:
```go
import (
"fmt"
"github.com/ghodss/yaml"
)
func main() {
j := []byte(`{"name": "John", "age": 30}`)
y, err := yaml.JSONToYAML(j)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(y))
/* Output:
name: John
age: 30
*/
j2, err := yaml.YAMLToJSON(y)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(j2))
/* Output:
{"age":30,"name":"John"}
*/
}
```

497
vendor/github.com/ghodss/yaml/fields.go generated vendored Normal file
View file

@ -0,0 +1,497 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package yaml
import (
"bytes"
"encoding"
"encoding/json"
"reflect"
"sort"
"strings"
"sync"
"unicode"
"unicode/utf8"
)
// indirect walks down v allocating pointers as needed,
// until it gets to a non-pointer.
// if it encounters an Unmarshaler, indirect stops and returns that.
// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
// If v is a named type and is addressable,
// start with its address, so that if the type has pointer methods,
// we find them.
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
}
for {
// Load value from interface, but only if the result will be
// usefully addressable.
if v.Kind() == reflect.Interface && !v.IsNil() {
e := v.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
v = e
continue
}
}
if v.Kind() != reflect.Ptr {
break
}
if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
break
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
if v.Type().NumMethod() > 0 {
if u, ok := v.Interface().(json.Unmarshaler); ok {
return u, nil, reflect.Value{}
}
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
return nil, u, reflect.Value{}
}
}
v = v.Elem()
}
return nil, nil, v
}
// A field represents a single field found in a struct.
type field struct {
name string
nameBytes []byte // []byte(name)
equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
tag bool
index []int
typ reflect.Type
omitEmpty bool
quoted bool
}
func fillField(f field) field {
f.nameBytes = []byte(f.name)
f.equalFold = foldFunc(f.nameBytes)
return f
}
// byName sorts field by name, breaking ties with depth,
// then breaking ties with "name came from json tag", then
// breaking ties with index sequence.
type byName []field
func (x byName) Len() int { return len(x) }
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byName) Less(i, j int) bool {
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].index) != len(x[j].index) {
return len(x[i].index) < len(x[j].index)
}
if x[i].tag != x[j].tag {
return x[i].tag
}
return byIndex(x).Less(i, j)
}
// byIndex sorts field by index sequence.
type byIndex []field
func (x byIndex) Len() int { return len(x) }
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byIndex) Less(i, j int) bool {
for k, xik := range x[i].index {
if k >= len(x[j].index) {
return false
}
if xik != x[j].index[k] {
return xik < x[j].index[k]
}
}
return len(x[i].index) < len(x[j].index)
}
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type) []field {
// Anonymous fields to explore at the current level and the next.
current := []field{}
next := []field{{typ: t}}
// Count of queued names for current level and the next.
count := map[reflect.Type]int{}
nextCount := map[reflect.Type]int{}
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.PkgPath != "" { // unexported
continue
}
tag := sf.Tag.Get("json")
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if !isValidTag(name) {
name = ""
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
tagged := name != ""
if name == "" {
name = sf.Name
}
fields = append(fields, fillField(field{
name: name,
tag: tagged,
index: index,
typ: ft,
omitEmpty: opts.Contains("omitempty"),
quoted: opts.Contains("string"),
}))
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
}
}
}
}
sort.Sort(byName(fields))
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
sort.Sort(byIndex(fields))
return fields
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order. The winner
// must therefore be one with the shortest index length. Drop all
// longer entries, which is easy: just truncate the slice.
length := len(fields[0].index)
tagged := -1 // Index of first tagged field.
for i, f := range fields {
if len(f.index) > length {
fields = fields[:i]
break
}
if f.tag {
if tagged >= 0 {
// Multiple tagged fields at the same level: conflict.
// Return no field.
return field{}, false
}
tagged = i
}
}
if tagged >= 0 {
return fields[tagged], true
}
// All remaining fields have the same length. If there's more than one,
// we have a conflict (two fields named "X" at the same level) and we
// return no field.
if len(fields) > 1 {
return field{}, false
}
return fields[0], true
}
var fieldCache struct {
sync.RWMutex
m map[reflect.Type][]field
}
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
func cachedTypeFields(t reflect.Type) []field {
fieldCache.RLock()
f := fieldCache.m[t]
fieldCache.RUnlock()
if f != nil {
return f
}
// Compute fields without lock.
// Might duplicate effort but won't hold other computations back.
f = typeFields(t)
if f == nil {
f = []field{}
}
fieldCache.Lock()
if fieldCache.m == nil {
fieldCache.m = map[reflect.Type][]field{}
}
fieldCache.m[t] = f
fieldCache.Unlock()
return f
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}
const (
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
kelvin = '\u212a'
smallLongEss = '\u017f'
)
// foldFunc returns one of four different case folding equivalence
// functions, from most general (and slow) to fastest:
//
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
// 3) asciiEqualFold, no special, but includes non-letters (including _)
// 4) simpleLetterEqualFold, no specials, no non-letters.
//
// The letters S and K are special because they map to 3 runes, not just 2:
// * S maps to s and to U+017F 'ſ' Latin small letter long s
// * k maps to K and to U+212A '' Kelvin sign
// See http://play.golang.org/p/tTxjOc0OGo
//
// The returned function is specialized for matching against s and
// should only be given s. It's not curried for performance reasons.
func foldFunc(s []byte) func(s, t []byte) bool {
nonLetter := false
special := false // special letter
for _, b := range s {
if b >= utf8.RuneSelf {
return bytes.EqualFold
}
upper := b & caseMask
if upper < 'A' || upper > 'Z' {
nonLetter = true
} else if upper == 'K' || upper == 'S' {
// See above for why these letters are special.
special = true
}
}
if special {
return equalFoldRight
}
if nonLetter {
return asciiEqualFold
}
return simpleLetterEqualFold
}
// equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc.
func equalFoldRight(s, t []byte) bool {
for _, sb := range s {
if len(t) == 0 {
return false
}
tb := t[0]
if tb < utf8.RuneSelf {
if sb != tb {
sbUpper := sb & caseMask
if 'A' <= sbUpper && sbUpper <= 'Z' {
if sbUpper != tb&caseMask {
return false
}
} else {
return false
}
}
t = t[1:]
continue
}
// sb is ASCII and t is not. t must be either kelvin
// sign or long s; sb must be s, S, k, or K.
tr, size := utf8.DecodeRune(t)
switch sb {
case 's', 'S':
if tr != smallLongEss {
return false
}
case 'k', 'K':
if tr != kelvin {
return false
}
default:
return false
}
t = t[size:]
}
if len(t) > 0 {
return false
}
return true
}
// asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no
// special-folding letters.
// See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, sb := range s {
tb := t[i]
if sb == tb {
continue
}
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
if sb&caseMask != tb&caseMask {
return false
}
} else {
return false
}
}
return true
}
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, b := range s {
if b&caseMask != t[i]&caseMask {
return false
}
}
return true
}
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

277
vendor/github.com/ghodss/yaml/yaml.go generated vendored Normal file
View file

@ -0,0 +1,277 @@
package yaml
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
)
// Marshals the object into JSON then converts JSON to YAML and returns the
// YAML.
func Marshal(o interface{}) ([]byte, error) {
j, err := json.Marshal(o)
if err != nil {
return nil, fmt.Errorf("error marshaling into JSON: ", err)
}
y, err := JSONToYAML(j)
if err != nil {
return nil, fmt.Errorf("error converting JSON to YAML: ", err)
}
return y, nil
}
// Converts YAML to JSON then uses JSON to unmarshal into an object.
func Unmarshal(y []byte, o interface{}) error {
vo := reflect.ValueOf(o)
j, err := yamlToJSON(y, &vo)
if err != nil {
return fmt.Errorf("error converting YAML to JSON: %v", err)
}
err = json.Unmarshal(j, o)
if err != nil {
return fmt.Errorf("error unmarshaling JSON: %v", err)
}
return nil
}
// Convert JSON to YAML.
func JSONToYAML(j []byte) ([]byte, error) {
// Convert the JSON to an object.
var jsonObj interface{}
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
// Go JSON library doesn't try to pick the right number type (int, float,
// etc.) when unmarshling to interface{}, it just picks float64
// universally. go-yaml does go through the effort of picking the right
// number type, so we can preserve number type throughout this process.
err := yaml.Unmarshal(j, &jsonObj)
if err != nil {
return nil, err
}
// Marshal this object into YAML.
return yaml.Marshal(jsonObj)
}
// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through
// this method should be a no-op.
//
// Things YAML can do that are not supported by JSON:
// * In YAML you can have binary and null keys in your maps. These are invalid
// in JSON. (int and float keys are converted to strings.)
// * Binary data in YAML with the !!binary tag is not supported. If you want to
// use binary data with this library, encode the data as base64 as usual but do
// not use the !!binary tag in your YAML. This will ensure the original base64
// encoded data makes it all the way through to the JSON.
func YAMLToJSON(y []byte) ([]byte, error) {
return yamlToJSON(y, nil)
}
func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) {
// Convert the YAML to an object.
var yamlObj interface{}
err := yaml.Unmarshal(y, &yamlObj)
if err != nil {
return nil, err
}
// YAML objects are not completely compatible with JSON objects (e.g. you
// can have non-string keys in YAML). So, convert the YAML-compatible object
// to a JSON-compatible object, failing with an error if irrecoverable
// incompatibilties happen along the way.
jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
if err != nil {
return nil, err
}
// Convert this object to JSON and return the data.
return json.Marshal(jsonObj)
}
func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) {
var err error
// Resolve jsonTarget to a concrete value (i.e. not a pointer or an
// interface). We pass decodingNull as false because we're not actually
// decoding into the value, we're just checking if the ultimate target is a
// string.
if jsonTarget != nil {
ju, tu, pv := indirect(*jsonTarget, false)
// We have a JSON or Text Umarshaler at this level, so we can't be trying
// to decode into a string.
if ju != nil || tu != nil {
jsonTarget = nil
} else {
jsonTarget = &pv
}
}
// If yamlObj is a number or a boolean, check if jsonTarget is a string -
// if so, coerce. Else return normal.
// If yamlObj is a map or array, find the field that each key is
// unmarshaling to, and when you recurse pass the reflect.Value for that
// field back into this function.
switch typedYAMLObj := yamlObj.(type) {
case map[interface{}]interface{}:
// JSON does not support arbitrary keys in a map, so we must convert
// these keys to strings.
//
// From my reading of go-yaml v2 (specifically the resolve function),
// keys can only have the types string, int, int64, float64, binary
// (unsupported), or null (unsupported).
strMap := make(map[string]interface{})
for k, v := range typedYAMLObj {
// Resolve the key to a string first.
var keyString string
switch typedKey := k.(type) {
case string:
keyString = typedKey
case int:
keyString = strconv.Itoa(typedKey)
case int64:
// go-yaml will only return an int64 as a key if the system
// architecture is 32-bit and the key's value is between 32-bit
// and 64-bit. Otherwise the key type will simply be int.
keyString = strconv.FormatInt(typedKey, 10)
case float64:
// Stolen from go-yaml to use the same conversion to string as
// the go-yaml library uses to convert float to string when
// Marshaling.
s := strconv.FormatFloat(typedKey, 'g', -1, 32)
switch s {
case "+Inf":
s = ".inf"
case "-Inf":
s = "-.inf"
case "NaN":
s = ".nan"
}
keyString = s
case bool:
if typedKey {
keyString = "true"
} else {
keyString = "false"
}
default:
return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v",
reflect.TypeOf(k), k, v)
}
// jsonTarget should be a struct or a map. If it's a struct, find
// the field it's going to map to and pass its reflect.Value. If
// it's a map, find the element type of the map and pass the
// reflect.Value created from that type. If it's neither, just pass
// nil - JSON conversion will error for us if it's a real issue.
if jsonTarget != nil {
t := *jsonTarget
if t.Kind() == reflect.Struct {
keyBytes := []byte(keyString)
// Find the field that the JSON library would use.
var f *field
fields := cachedTypeFields(t.Type())
for i := range fields {
ff := &fields[i]
if bytes.Equal(ff.nameBytes, keyBytes) {
f = ff
break
}
// Do case-insensitive comparison.
if f == nil && ff.equalFold(ff.nameBytes, keyBytes) {
f = ff
}
}
if f != nil {
// Find the reflect.Value of the most preferential
// struct field.
jtf := t.Field(f.index[0])
strMap[keyString], err = convertToJSONableObject(v, &jtf)
if err != nil {
return nil, err
}
continue
}
} else if t.Kind() == reflect.Map {
// Create a zero value of the map's element type to use as
// the JSON target.
jtv := reflect.Zero(t.Type().Elem())
strMap[keyString], err = convertToJSONableObject(v, &jtv)
if err != nil {
return nil, err
}
continue
}
}
strMap[keyString], err = convertToJSONableObject(v, nil)
if err != nil {
return nil, err
}
}
return strMap, nil
case []interface{}:
// We need to recurse into arrays in case there are any
// map[interface{}]interface{}'s inside and to convert any
// numbers to strings.
// If jsonTarget is a slice (which it really should be), find the
// thing it's going to map to. If it's not a slice, just pass nil
// - JSON conversion will error for us if it's a real issue.
var jsonSliceElemValue *reflect.Value
if jsonTarget != nil {
t := *jsonTarget
if t.Kind() == reflect.Slice {
// By default slices point to nil, but we need a reflect.Value
// pointing to a value of the slice type, so we create one here.
ev := reflect.Indirect(reflect.New(t.Type().Elem()))
jsonSliceElemValue = &ev
}
}
// Make and use a new array.
arr := make([]interface{}, len(typedYAMLObj))
for i, v := range typedYAMLObj {
arr[i], err = convertToJSONableObject(v, jsonSliceElemValue)
if err != nil {
return nil, err
}
}
return arr, nil
default:
// If the target type is a string and the YAML type is a number,
// convert the YAML type to a string.
if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String {
// Based on my reading of go-yaml, it may return int, int64,
// float64, or uint64.
var s string
switch typedVal := typedYAMLObj.(type) {
case int:
s = strconv.FormatInt(int64(typedVal), 10)
case int64:
s = strconv.FormatInt(typedVal, 10)
case float64:
s = strconv.FormatFloat(typedVal, 'g', -1, 32)
case uint64:
s = strconv.FormatUint(typedVal, 10)
case bool:
if typedVal {
s = "true"
} else {
s = "false"
}
}
if len(s) > 0 {
yamlObj = interface{}(s)
}
}
return yamlObj, nil
}
return nil, nil
}

10
vendor/vendor.json vendored
View file

@ -12,6 +12,11 @@
"revision": "4b6ea7319e214d98c938f12692336f7ca9348d6b",
"revisionTime": "2016-03-17T14:11:10Z"
},
{
"path": "github.com/cloudfoundry-incubator/candiedyaml",
"revision": "5cef21e2e4f0fd147973b558d4db7395176bcd95",
"revisionTime": "2016-03-22T13:50:45-07:00"
},
{
"origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew",
"path": "github.com/davecgh/go-spew/spew",
@ -58,6 +63,11 @@
"revision": "889391d730237f8aca06ce3e62975112983f96b4",
"revisionTime": "2016-01-23T18:11:54-03:00"
},
{
"path": "github.com/ghodss/yaml",
"revision": "1a6f069841556a7bcaff4a397ca6e8328d266c2f",
"revisionTime": "2016-03-07T13:52:05-08:00"
},
{
"path": "github.com/gin-gonic/contrib/ginrus",
"revision": "14f66d54cdb96059bafca98665bcc6d9df4951f2",