diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..44f6900 --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,73 @@ +{ + "ImportPath": "github.com/appc/acpush", + "GoVersion": "go1.5.1", + "Packages": [ + "./..." + ], + "Deps": [ + { + "ImportPath": "github.com/appc/spec/aci", + "Comment": "v0.7.1-3-g89715a6", + "Rev": "89715a66b8f8ac3750a4986022747c2da73fdcf1" + }, + { + "ImportPath": "github.com/appc/spec/discovery", + "Comment": "v0.7.1-3-g89715a6", + "Rev": "89715a66b8f8ac3750a4986022747c2da73fdcf1" + }, + { + "ImportPath": "github.com/appc/spec/pkg/device", + "Comment": "v0.7.1-3-g89715a6", + "Rev": "89715a66b8f8ac3750a4986022747c2da73fdcf1" + }, + { + "ImportPath": "github.com/appc/spec/pkg/tarheader", + "Comment": "v0.7.1-3-g89715a6", + "Rev": "89715a66b8f8ac3750a4986022747c2da73fdcf1" + }, + { + "ImportPath": "github.com/appc/spec/schema", + "Comment": "v0.7.1-3-g89715a6", + "Rev": "89715a66b8f8ac3750a4986022747c2da73fdcf1" + }, + { + "ImportPath": "github.com/coreos/go-semver/semver", + "Rev": "6fe83ccda8fb9b7549c9ab4ba47f47858bc950aa" + }, + { + "ImportPath": "github.com/coreos/ioprogress", + "Rev": "a9d1979d9f4e84fe8d6ab9f4760757dd67ab6be1" + }, + { + "ImportPath": "github.com/coreos/rkt/common", + "Comment": "v0.8.1-191-g0d4bbae", + "Rev": "0d4bbae500b8f72674c455b5e8c9264cc2be1a6f" + }, + { + "ImportPath": "github.com/coreos/rkt/rkt/config", + "Comment": "v0.8.1-191-g0d4bbae", + "Rev": "0d4bbae500b8f72674c455b5e8c9264cc2be1a6f" + }, + { + "ImportPath": "github.com/spf13/pflag", + "Rev": "67cbc198fd11dab704b214c1e629a97af392c085" + }, + { + "ImportPath": "golang.org/x/crypto/ssh/terminal", + "Rev": "a7ead6ddf06233883deca151dffaef2effbf498f" + }, + { + "ImportPath": "golang.org/x/net/html", + "Rev": "c2528b2dd8352441850638a8bb678c2ad056fd3e" + }, + { + "ImportPath": "k8s.io/kubernetes/pkg/api/resource", + "Comment": "v0.12.0-270-g53ec66c", + "Rev": "53ec66caf4e952a1384ec93b9f0cde37616e4caf" + }, + { + "ImportPath": "speter.net/go/exp/math/dec/inf", + "Rev": "42ca6cd68aa922bc3f32f1e056e61b65945d9ad7" + } + ] +} diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/Godeps/_workspace/src/github.com/appc/spec/aci/build.go b/Godeps/_workspace/src/github.com/appc/spec/aci/build.go new file mode 100644 index 0000000..a594eb9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/aci/build.go @@ -0,0 +1,110 @@ +// Copyright 2015 The appc Authors +// +// 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 aci + +import ( + "archive/tar" + "io" + "os" + "path/filepath" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader" +) + +// TarHeaderWalkFunc is the type of the function which allows setting tar +// headers or filtering out tar entries when building an ACI. It will be +// applied to every entry in the tar file. +// +// If true is returned, the entry will be included in the final ACI; if false, +// the entry will not be included. +type TarHeaderWalkFunc func(hdr *tar.Header) bool + +// BuildWalker creates a filepath.WalkFunc that walks over the given root +// (which should represent an ACI layout on disk) and adds the files in the +// rootfs/ subdirectory to the given ArchiveWriter +func BuildWalker(root string, aw ArchiveWriter, cb TarHeaderWalkFunc) filepath.WalkFunc { + // cache of inode -> filepath, used to leverage hard links in the archive + inos := map[uint64]string{} + return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + relpath, err := filepath.Rel(root, path) + if err != nil { + return err + } + if relpath == "." { + return nil + } + if relpath == ManifestFile { + // ignore; this will be written by the archive writer + // TODO(jonboulle): does this make sense? maybe just remove from archivewriter? + return nil + } + + link := "" + var r io.Reader + switch info.Mode() & os.ModeType { + case os.ModeSocket: + return nil + case os.ModeNamedPipe: + case os.ModeCharDevice: + case os.ModeDevice: + case os.ModeDir: + case os.ModeSymlink: + target, err := os.Readlink(path) + if err != nil { + return err + } + link = target + default: + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + r = file + } + + hdr, err := tar.FileInfoHeader(info, link) + if err != nil { + panic(err) + } + // Because os.FileInfo's Name method returns only the base + // name of the file it describes, it may be necessary to + // modify the Name field of the returned header to provide the + // full path name of the file. + hdr.Name = relpath + tarheader.Populate(hdr, info, inos) + // If the file is a hard link to a file we've already seen, we + // don't need the contents + if hdr.Typeflag == tar.TypeLink { + hdr.Size = 0 + r = nil + } + + if cb != nil { + if !cb(hdr) { + return nil + } + } + + if err := aw.AddFile(hdr, r); err != nil { + return err + } + + return nil + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/aci/doc.go b/Godeps/_workspace/src/github.com/appc/spec/aci/doc.go new file mode 100644 index 0000000..624d431 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/aci/doc.go @@ -0,0 +1,16 @@ +// Copyright 2015 The appc Authors +// +// 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 aci contains various functions for working with App Container Images. +package aci diff --git a/Godeps/_workspace/src/github.com/appc/spec/aci/file.go b/Godeps/_workspace/src/github.com/appc/spec/aci/file.go new file mode 100644 index 0000000..4ec6826 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/aci/file.go @@ -0,0 +1,246 @@ +// Copyright 2015 The appc Authors +// +// 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 aci + +import ( + "archive/tar" + "bytes" + "compress/bzip2" + "compress/gzip" + "encoding/hex" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os/exec" + "path/filepath" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema" +) + +type FileType string + +const ( + TypeGzip = FileType("gz") + TypeBzip2 = FileType("bz2") + TypeXz = FileType("xz") + TypeTar = FileType("tar") + TypeText = FileType("text") + TypeUnknown = FileType("unknown") + + readLen = 512 // max bytes to sniff + + hexHdrGzip = "1f8b" + hexHdrBzip2 = "425a68" + hexHdrXz = "fd377a585a00" + hexSigTar = "7573746172" + + tarOffset = 257 + + textMime = "text/plain; charset=utf-8" +) + +var ( + hdrGzip []byte + hdrBzip2 []byte + hdrXz []byte + sigTar []byte + tarEnd int +) + +func mustDecodeHex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +func init() { + hdrGzip = mustDecodeHex(hexHdrGzip) + hdrBzip2 = mustDecodeHex(hexHdrBzip2) + hdrXz = mustDecodeHex(hexHdrXz) + sigTar = mustDecodeHex(hexSigTar) + tarEnd = tarOffset + len(sigTar) +} + +// DetectFileType attempts to detect the type of file that the given reader +// represents by comparing it against known file signatures (magic numbers) +func DetectFileType(r io.Reader) (FileType, error) { + var b bytes.Buffer + n, err := io.CopyN(&b, r, readLen) + if err != nil && err != io.EOF { + return TypeUnknown, err + } + bs := b.Bytes() + switch { + case bytes.HasPrefix(bs, hdrGzip): + return TypeGzip, nil + case bytes.HasPrefix(bs, hdrBzip2): + return TypeBzip2, nil + case bytes.HasPrefix(bs, hdrXz): + return TypeXz, nil + case n > int64(tarEnd) && bytes.Equal(bs[tarOffset:tarEnd], sigTar): + return TypeTar, nil + case http.DetectContentType(bs) == textMime: + return TypeText, nil + default: + return TypeUnknown, nil + } +} + +// XzReader is an io.ReadCloser which decompresses xz compressed data. +type XzReader struct { + io.ReadCloser + cmd *exec.Cmd + closech chan error +} + +// NewXzReader shells out to a command line xz executable (if +// available) to decompress the given io.Reader using the xz +// compression format and returns an *XzReader. +// It is the caller's responsibility to call Close on the XzReader when done. +func NewXzReader(r io.Reader) (*XzReader, error) { + rpipe, wpipe := io.Pipe() + ex, err := exec.LookPath("xz") + if err != nil { + log.Fatalf("couldn't find xz executable: %v", err) + } + cmd := exec.Command(ex, "--decompress", "--stdout") + + closech := make(chan error) + + cmd.Stdin = r + cmd.Stdout = wpipe + + go func() { + err := cmd.Run() + wpipe.CloseWithError(err) + closech <- err + }() + + return &XzReader{rpipe, cmd, closech}, nil +} + +func (r *XzReader) Close() error { + r.ReadCloser.Close() + r.cmd.Process.Kill() + return <-r.closech +} + +// ManifestFromImage extracts a new schema.ImageManifest from the given ACI image. +func ManifestFromImage(rs io.ReadSeeker) (*schema.ImageManifest, error) { + var im schema.ImageManifest + + tr, err := NewCompressedTarReader(rs) + if err != nil { + return nil, err + } + defer tr.Close() + + for { + hdr, err := tr.Next() + switch err { + case io.EOF: + return nil, errors.New("missing manifest") + case nil: + if filepath.Clean(hdr.Name) == ManifestFile { + data, err := ioutil.ReadAll(tr) + if err != nil { + return nil, err + } + if err := im.UnmarshalJSON(data); err != nil { + return nil, err + } + return &im, nil + } + default: + return nil, fmt.Errorf("error extracting tarball: %v", err) + } + } +} + +// TarReadCloser embeds a *tar.Reader and the related io.Closer +// It is the caller's responsibility to call Close on TarReadCloser when +// done. +type TarReadCloser struct { + *tar.Reader + io.Closer +} + +func (r *TarReadCloser) Close() error { + return r.Closer.Close() +} + +// NewCompressedTarReader creates a new TarReadCloser reading from the +// given ACI image. +// It is the caller's responsibility to call Close on the TarReadCloser +// when done. +func NewCompressedTarReader(rs io.ReadSeeker) (*TarReadCloser, error) { + cr, err := NewCompressedReader(rs) + if err != nil { + return nil, err + } + return &TarReadCloser{tar.NewReader(cr), cr}, nil +} + +// NewCompressedReader creates a new io.ReaderCloser from the given ACI image. +// It is the caller's responsibility to call Close on the Reader when done. +func NewCompressedReader(rs io.ReadSeeker) (io.ReadCloser, error) { + + var ( + dr io.ReadCloser + err error + ) + + _, err = rs.Seek(0, 0) + if err != nil { + return nil, err + } + + ftype, err := DetectFileType(rs) + if err != nil { + return nil, err + } + + _, err = rs.Seek(0, 0) + if err != nil { + return nil, err + } + + switch ftype { + case TypeGzip: + dr, err = gzip.NewReader(rs) + if err != nil { + return nil, err + } + case TypeBzip2: + dr = ioutil.NopCloser(bzip2.NewReader(rs)) + case TypeXz: + dr, err = NewXzReader(rs) + if err != nil { + return nil, err + } + case TypeTar: + dr = ioutil.NopCloser(rs) + case TypeUnknown: + return nil, errors.New("error: unknown image filetype") + default: + return nil, errors.New("no type returned from DetectFileType?") + } + return dr, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/aci/file_test.go b/Godeps/_workspace/src/github.com/appc/spec/aci/file_test.go new file mode 100644 index 0000000..ea4532b --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/aci/file_test.go @@ -0,0 +1,150 @@ +// Copyright 2015 The appc Authors +// +// 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 aci + +import ( + "archive/tar" + "compress/gzip" + "io/ioutil" + "os" + "testing" +) + +func newTestACI(usedotslash bool) (*os.File, error) { + tf, err := ioutil.TempFile("", "") + if err != nil { + return nil, err + } + + manifestBody := `{"acKind":"ImageManifest","acVersion":"0.7.1","name":"example.com/app"}` + + gw := gzip.NewWriter(tf) + tw := tar.NewWriter(gw) + + manifestPath := "manifest" + if usedotslash { + manifestPath = "./" + manifestPath + } + hdr := &tar.Header{ + Name: manifestPath, + Size: int64(len(manifestBody)), + } + if err := tw.WriteHeader(hdr); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(manifestBody)); err != nil { + return nil, err + } + if err := tw.Close(); err != nil { + return nil, err + } + if err := gw.Close(); err != nil { + return nil, err + } + return tf, nil +} + +func newEmptyTestACI() (*os.File, error) { + tf, err := ioutil.TempFile("", "") + if err != nil { + return nil, err + } + gw := gzip.NewWriter(tf) + tw := tar.NewWriter(gw) + if err := tw.Close(); err != nil { + return nil, err + } + if err := gw.Close(); err != nil { + return nil, err + } + return tf, nil +} + +func TestManifestFromImage(t *testing.T) { + for _, usedotslash := range []bool{false, true} { + img, err := newTestACI(usedotslash) + if err != nil { + t.Fatalf("newTestACI: unexpected error: %v", err) + } + defer img.Close() + defer os.Remove(img.Name()) + + im, err := ManifestFromImage(img) + if err != nil { + t.Fatalf("ManifestFromImage: unexpected error: %v", err) + } + if im.Name.String() != "example.com/app" { + t.Errorf("expected %s, got %s", "example.com/app", im.Name.String()) + } + + emptyImg, err := newEmptyTestACI() + if err != nil { + t.Fatalf("newEmptyTestACI: unexpected error: %v", err) + } + defer emptyImg.Close() + defer os.Remove(emptyImg.Name()) + + im, err = ManifestFromImage(emptyImg) + if err == nil { + t.Fatalf("ManifestFromImage: expected error") + } + } +} + +func TestNewCompressedTarReader(t *testing.T) { + img, err := newTestACI(false) + if err != nil { + t.Fatalf("newTestACI: unexpected error: %v", err) + } + defer img.Close() + defer os.Remove(img.Name()) + + cr, err := NewCompressedTarReader(img) + if err != nil { + t.Fatalf("NewCompressedTarReader: unexpected error: %v", err) + } + + ftype, err := DetectFileType(cr) + if err != nil { + t.Fatalf("DetectFileType: unexpected error: %v", err) + } + + if ftype != TypeText { + t.Errorf("expected %v, got %v", TypeText, ftype) + } +} + +func TestNewCompressedReader(t *testing.T) { + img, err := newTestACI(false) + if err != nil { + t.Fatalf("newTestACI: unexpected error: %v", err) + } + defer img.Close() + defer os.Remove(img.Name()) + + cr, err := NewCompressedReader(img) + if err != nil { + t.Fatalf("NewCompressedReader: unexpected error: %v", err) + } + + ftype, err := DetectFileType(cr) + if err != nil { + t.Fatalf("DetectFileType: unexpected error: %v", err) + } + + if ftype != TypeTar { + t.Errorf("expected %v, got %v", TypeTar, ftype) + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/aci/layout.go b/Godeps/_workspace/src/github.com/appc/spec/aci/layout.go new file mode 100644 index 0000000..ce4ac7f --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/aci/layout.go @@ -0,0 +1,187 @@ +// Copyright 2015 The appc Authors +// +// 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 aci + +/* + +Image Layout + +The on-disk layout of an app container is straightforward. +It includes a rootfs with all of the files that will exist in the root of the app and a manifest describing the image. +The layout MUST contain an image manifest. + +/manifest +/rootfs/ +/rootfs/usr/bin/mysql + +*/ + +import ( + "archive/tar" + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema" + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +const ( + // Path to manifest file inside the layout + ManifestFile = "manifest" + // Path to rootfs directory inside the layout + RootfsDir = "rootfs" +) + +type ErrOldVersion struct { + version types.SemVer +} + +func (e ErrOldVersion) Error() string { + return fmt.Sprintf("ACVersion too old. Found major version %v, expected %v", e.version.Major, schema.AppContainerVersion.Major) +} + +var ( + ErrNoRootFS = errors.New("no rootfs found in layout") + ErrNoManifest = errors.New("no image manifest found in layout") +) + +// ValidateLayout takes a directory and validates that the layout of the directory +// matches that expected by the Application Container Image format. +// If any errors are encountered during the validation, it will abort and +// return the first one. +func ValidateLayout(dir string) error { + fi, err := os.Stat(dir) + if err != nil { + return fmt.Errorf("error accessing layout: %v", err) + } + if !fi.IsDir() { + return fmt.Errorf("given path %q is not a directory", dir) + } + var flist []string + var imOK, rfsOK bool + var im io.Reader + walkLayout := func(fpath string, fi os.FileInfo, err error) error { + rpath, err := filepath.Rel(dir, fpath) + if err != nil { + return err + } + switch rpath { + case ".": + case ManifestFile: + im, err = os.Open(fpath) + if err != nil { + return err + } + imOK = true + case RootfsDir: + if !fi.IsDir() { + return errors.New("rootfs is not a directory") + } + rfsOK = true + default: + flist = append(flist, rpath) + } + return nil + } + if err := filepath.Walk(dir, walkLayout); err != nil { + return err + } + return validate(imOK, im, rfsOK, flist) +} + +// ValidateArchive takes a *tar.Reader and validates that the layout of the +// filesystem the reader encapsulates matches that expected by the +// Application Container Image format. If any errors are encountered during +// the validation, it will abort and return the first one. +func ValidateArchive(tr *tar.Reader) error { + var fseen map[string]bool = make(map[string]bool) + var imOK, rfsOK bool + var im bytes.Buffer +Tar: + for { + hdr, err := tr.Next() + switch { + case err == nil: + case err == io.EOF: + break Tar + default: + return err + } + name := filepath.Clean(hdr.Name) + switch name { + case ".": + case ManifestFile: + _, err := io.Copy(&im, tr) + if err != nil { + return err + } + imOK = true + case RootfsDir: + if !hdr.FileInfo().IsDir() { + return fmt.Errorf("rootfs is not a directory") + } + rfsOK = true + default: + if _, seen := fseen[name]; seen { + return fmt.Errorf("duplicate file entry in archive: %s", name) + } + fseen[name] = true + } + } + var flist []string + for key := range fseen { + flist = append(flist, key) + } + return validate(imOK, &im, rfsOK, flist) +} + +func validate(imOK bool, im io.Reader, rfsOK bool, files []string) error { + defer func() { + if rc, ok := im.(io.Closer); ok { + rc.Close() + } + }() + if !imOK { + return ErrNoManifest + } + if !rfsOK { + return ErrNoRootFS + } + b, err := ioutil.ReadAll(im) + if err != nil { + return fmt.Errorf("error reading image manifest: %v", err) + } + var a schema.ImageManifest + if err := a.UnmarshalJSON(b); err != nil { + return fmt.Errorf("image manifest validation failed: %v", err) + } + if a.ACVersion.LessThanMajor(schema.AppContainerVersion) { + return ErrOldVersion{ + version: a.ACVersion, + } + } + for _, f := range files { + if !strings.HasPrefix(f, "rootfs") { + return fmt.Errorf("unrecognized file path in layout: %q", f) + } + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/aci/layout_test.go b/Godeps/_workspace/src/github.com/appc/spec/aci/layout_test.go new file mode 100644 index 0000000..44e0856 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/aci/layout_test.go @@ -0,0 +1,79 @@ +// Copyright 2015 The appc Authors +// +// 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 aci + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "testing" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema" +) + +func newValidateLayoutTest() (string, error) { + td, err := ioutil.TempDir("", "") + if err != nil { + return "", err + } + + if err := os.MkdirAll(path.Join(td, "rootfs"), 0755); err != nil { + return "", err + } + + if err := os.MkdirAll(path.Join(td, "rootfs", "dir", "rootfs"), 0755); err != nil { + return "", err + } + + evilManifestBody := "malformedManifest" + manifestBody := fmt.Sprintf(`{"acKind":"ImageManifest","acVersion":"%s","name":"example.com/app"}`, schema.AppContainerVersion) + + evilManifestPath := "rootfs/manifest" + evilManifestPath = path.Join(td, evilManifestPath) + + em, err := os.Create(evilManifestPath) + if err != nil { + return "", err + } + + em.WriteString(evilManifestBody) + em.Close() + + manifestPath := path.Join(td, "manifest") + + m, err := os.Create(manifestPath) + if err != nil { + return "", err + } + + m.WriteString(manifestBody) + m.Close() + + return td, nil +} + +func TestValidateLayout(t *testing.T) { + layoutPath, err := newValidateLayoutTest() + if err != nil { + t.Fatalf("newValidateLayoutTest: unexpected error: %v", err) + } + defer os.RemoveAll(layoutPath) + + err = ValidateLayout(layoutPath) + if err != nil { + t.Fatalf("ValidateLayout: unexpected error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/aci/writer.go b/Godeps/_workspace/src/github.com/appc/spec/aci/writer.go new file mode 100644 index 0000000..328bbc6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/aci/writer.go @@ -0,0 +1,98 @@ +// Copyright 2015 The appc Authors +// +// 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 aci + +import ( + "archive/tar" + "bytes" + "encoding/json" + "io" + "time" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema" +) + +// ArchiveWriter writes App Container Images. Users wanting to create an ACI or +// should create an ArchiveWriter and add files to it; the ACI will be written +// to the underlying tar.Writer +type ArchiveWriter interface { + AddFile(hdr *tar.Header, r io.Reader) error + Close() error +} + +type imageArchiveWriter struct { + *tar.Writer + am *schema.ImageManifest +} + +// NewImageWriter creates a new ArchiveWriter which will generate an App +// Container Image based on the given manifest and write it to the given +// tar.Writer +func NewImageWriter(am schema.ImageManifest, w *tar.Writer) ArchiveWriter { + aw := &imageArchiveWriter{ + w, + &am, + } + return aw +} + +func (aw *imageArchiveWriter) AddFile(hdr *tar.Header, r io.Reader) error { + err := aw.Writer.WriteHeader(hdr) + if err != nil { + return err + } + + if r != nil { + _, err := io.Copy(aw.Writer, r) + if err != nil { + return err + } + } + + return nil +} + +func (aw *imageArchiveWriter) addFileNow(path string, contents []byte) error { + buf := bytes.NewBuffer(contents) + now := time.Now() + hdr := tar.Header{ + Name: path, + Mode: 0644, + Uid: 0, + Gid: 0, + Size: int64(buf.Len()), + ModTime: now, + Typeflag: tar.TypeReg, + Uname: "root", + Gname: "root", + ChangeTime: now, + } + return aw.AddFile(&hdr, buf) +} + +func (aw *imageArchiveWriter) addManifest(name string, m json.Marshaler) error { + out, err := m.MarshalJSON() + if err != nil { + return err + } + return aw.addFileNow(name, out) +} + +func (aw *imageArchiveWriter) Close() error { + if err := aw.addManifest(ManifestFile, aw.am); err != nil { + return err + } + return aw.Writer.Close() +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/discovery.go b/Godeps/_workspace/src/github.com/appc/spec/discovery/discovery.go new file mode 100644 index 0000000..ba95c2a --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/discovery.go @@ -0,0 +1,260 @@ +// Copyright 2015 The appc Authors +// +// 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 discovery + +import ( + "errors" + "fmt" + "io" + "regexp" + "strings" + + "github.com/appc/acpush/Godeps/_workspace/src/golang.org/x/net/html" + "github.com/appc/acpush/Godeps/_workspace/src/golang.org/x/net/html/atom" +) + +type acMeta struct { + name string + prefix string + uri string +} + +type ACIEndpoint struct { + ACI string + ASC string +} + +type Endpoints struct { + ACIEndpoints []ACIEndpoint + Keys []string + ACIPushEndpoints []string +} + +func (e *Endpoints) Append(ep Endpoints) { + e.ACIEndpoints = append(e.ACIEndpoints, ep.ACIEndpoints...) + e.Keys = append(e.Keys, ep.Keys...) + e.ACIPushEndpoints = append(e.ACIPushEndpoints, ep.ACIPushEndpoints...) +} + +const ( + defaultVersion = "latest" +) + +var ( + templateExpression = regexp.MustCompile(`{.*?}`) + errEnough = errors.New("enough discovery information found") +) + +func appendMeta(meta []acMeta, attrs []html.Attribute) []acMeta { + m := acMeta{} + + for _, a := range attrs { + if a.Namespace != "" { + continue + } + + switch a.Key { + case "name": + m.name = a.Val + + case "content": + parts := strings.SplitN(strings.TrimSpace(a.Val), " ", 2) + if len(parts) < 2 { + break + } + m.prefix = parts[0] + m.uri = strings.TrimSpace(parts[1]) + } + } + + // TODO(eyakubovich): should prefix be optional? + if !strings.HasPrefix(m.name, "ac-") || m.prefix == "" || m.uri == "" { + return meta + } + + return append(meta, m) +} + +func extractACMeta(r io.Reader) []acMeta { + var meta []acMeta + + z := html.NewTokenizer(r) + + for { + switch z.Next() { + case html.ErrorToken: + return meta + + case html.StartTagToken, html.SelfClosingTagToken: + tok := z.Token() + if tok.DataAtom == atom.Meta { + meta = appendMeta(meta, tok.Attr) + } + } + } +} + +func renderTemplate(tpl string, kvs ...string) (string, bool) { + for i := 0; i < len(kvs); i += 2 { + k := kvs[i] + v := kvs[i+1] + tpl = strings.Replace(tpl, k, v, -1) + } + return tpl, !templateExpression.MatchString(tpl) +} + +func createTemplateVars(app App) []string { + tplVars := []string{"{name}", app.Name.String()} + // If a label is called "name", it will be ignored as it appears after + // in the slice + for n, v := range app.Labels { + tplVars = append(tplVars, fmt.Sprintf("{%s}", n), v) + } + return tplVars +} + +func doDiscover(pre string, app App, insecure bool) (*Endpoints, error) { + app = *app.Copy() + if app.Labels["version"] == "" { + app.Labels["version"] = defaultVersion + } + + _, body, err := httpsOrHTTP(pre, insecure) + if err != nil { + return nil, err + } + defer body.Close() + + meta := extractACMeta(body) + + tplVars := createTemplateVars(app) + + de := &Endpoints{} + + for _, m := range meta { + if !strings.HasPrefix(app.Name.String(), m.prefix) { + continue + } + + switch m.name { + case "ac-discovery": + // Ignore not handled variables as {ext} isn't already rendered. + uri, _ := renderTemplate(m.uri, tplVars...) + asc, ok := renderTemplate(uri, "{ext}", "aci.asc") + if !ok { + continue + } + aci, ok := renderTemplate(uri, "{ext}", "aci") + if !ok { + continue + } + de.ACIEndpoints = append(de.ACIEndpoints, ACIEndpoint{ACI: aci, ASC: asc}) + + case "ac-discovery-pubkeys": + de.Keys = append(de.Keys, m.uri) + case "ac-push-discovery": + uri, _ := renderTemplate(m.uri, tplVars...) + de.ACIPushEndpoints = append(de.ACIPushEndpoints, uri) + } + } + + return de, nil +} + +// DiscoverWalk will make HTTPS requests to find discovery meta tags and +// optionally will use HTTP if insecure is set. Based on the response of the +// discoverFn it will continue to recurse up the tree. +func DiscoverWalk(app App, insecure bool, discoverFn DiscoverWalkFunc) (err error) { + var ( + eps *Endpoints + ) + + parts := strings.Split(string(app.Name), "/") + for i := range parts { + end := len(parts) - i + pre := strings.Join(parts[:end], "/") + + eps, err = doDiscover(pre, app, insecure) + if derr := discoverFn(pre, eps, err); derr != nil { + return derr + } + } + + return +} + +// DiscoverWalkFunc can stop a DiscoverWalk by returning non-nil error. +type DiscoverWalkFunc func(prefix string, eps *Endpoints, err error) error + +// FailedAttempt represents a failed discovery attempt. This is for debugging +// and user feedback. +type FailedAttempt struct { + Prefix string + Error error +} + +func walker(out *Endpoints, attempts *[]FailedAttempt, testFn DiscoverWalkFunc) DiscoverWalkFunc { + return func(pre string, eps *Endpoints, err error) error { + if err != nil { + *attempts = append(*attempts, FailedAttempt{pre, err}) + return nil + } + out.Append(*eps) + if err := testFn(pre, eps, err); err != nil { + return err + } + return nil + } +} + +// DiscoverEndpoints will make HTTPS requests to find the ac-discovery meta +// tags and optionally will use HTTP if insecure is set. It will not give up +// until it has exhausted the path or found an image discovery. +func DiscoverEndpoints(app App, insecure bool) (out *Endpoints, attempts []FailedAttempt, err error) { + out = &Endpoints{} + testFn := func(pre string, eps *Endpoints, err error) error { + if len(out.ACIEndpoints) != 0 || len(out.Keys) != 0 || len(out.ACIPushEndpoints) != 0 { + return errEnough + } + return nil + } + + err = DiscoverWalk(app, insecure, walker(out, &attempts, testFn)) + if err != nil && err != errEnough { + return nil, attempts, err + } + + return out, attempts, nil +} + +// DiscoverPublicKey will make HTTPS requests to find the ac-public-keys meta +// tags and optionally will use HTTP if insecure is set. It will not give up +// until it has exhausted the path or found an public key. +func DiscoverPublicKeys(app App, insecure bool) (out *Endpoints, attempts []FailedAttempt, err error) { + out = &Endpoints{} + testFn := func(pre string, eps *Endpoints, err error) error { + if len(out.Keys) != 0 { + return errEnough + } + return nil + } + + err = DiscoverWalk(app, insecure, walker(out, &attempts, testFn)) + if err != nil && err != errEnough { + return nil, attempts, err + } + + return out, attempts, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/discovery_test.go b/Godeps/_workspace/src/github.com/appc/spec/discovery/discovery_test.go new file mode 100644 index 0000000..4cbd7ed --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/discovery_test.go @@ -0,0 +1,227 @@ +// Copyright 2015 The appc Authors +// +// 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 discovery + +import ( + "bytes" + "io/ioutil" + "net/http" + "os" + "testing" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +func fakeHTTPGet(filename string, failures int) func(uri string) (*http.Response, error) { + attempts := 0 + return func(uri string) (*http.Response, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + + var resp *http.Response + + switch { + case attempts < failures: + resp = &http.Response{ + Status: "404 Not Found", + StatusCode: http.StatusNotFound, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: http.Header{ + "Content-Type": []string{"text/html"}, + }, + Body: ioutil.NopCloser(bytes.NewBufferString("")), + } + default: + resp = &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: http.Header{ + "Content-Type": []string{"text/html"}, + }, + Body: f, + } + } + + attempts = attempts + 1 + return resp, nil + } +} + +type httpgetter func(uri string) (*http.Response, error) + +func TestDiscoverEndpoints(t *testing.T) { + tests := []struct { + get httpgetter + expectDiscoverySuccess bool + app App + expectedACIEndpoints []ACIEndpoint + expectedKeys []string + }{ + { + fakeHTTPGet("myapp.html", 0), + true, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + "os": "linux", + "arch": "amd64", + }, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp-1.0.0.aci?torrent", + ASC: "https://storage.example.com/example.com/myapp-1.0.0.aci.asc?torrent", + }, + ACIEndpoint{ + ACI: "hdfs://storage.example.com/example.com/myapp-1.0.0.aci", + ASC: "hdfs://storage.example.com/example.com/myapp-1.0.0.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + }, + { + fakeHTTPGet("myapp.html", 1), + true, + App{ + Name: "example.com/myapp/foobar", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + "os": "linux", + "arch": "amd64", + }, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp/foobar-1.0.0.aci?torrent", + ASC: "https://storage.example.com/example.com/myapp/foobar-1.0.0.aci.asc?torrent", + }, + ACIEndpoint{ + ACI: "hdfs://storage.example.com/example.com/myapp/foobar-1.0.0.aci", + ASC: "hdfs://storage.example.com/example.com/myapp/foobar-1.0.0.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + }, + { + fakeHTTPGet("myapp.html", 20), + false, + App{ + Name: "example.com/myapp/foobar/bazzer", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + "os": "linux", + "arch": "amd64", + }, + }, + []ACIEndpoint{}, + []string{}, + }, + // Test missing label. Only one ac-discovery template should be + // returned as the other one cannot be completely rendered due to + // missing labels. + { + fakeHTTPGet("myapp2.html", 0), + true, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + }, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp-1.0.0.aci", + ASC: "https://storage.example.com/example.com/myapp-1.0.0.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + }, + // Test missing labels. version label should default to + // "latest" and the first template should be rendered + { + fakeHTTPGet("myapp2.html", 0), + false, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{}, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp-latest.aci", + ASC: "https://storage.example.com/example.com/myapp-latest.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + }, + // Test with a label called "name". It should be ignored. + { + fakeHTTPGet("myapp2.html", 0), + false, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{ + "name": "labelcalledname", + "version": "1.0.0", + }, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp-1.0.0.aci", + ASC: "https://storage.example.com/example.com/myapp-1.0.0.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + }, + } + + for i, tt := range tests { + httpGet = &mockHttpGetter{getter: tt.get} + de, _, err := DiscoverEndpoints(tt.app, true) + if err != nil && !tt.expectDiscoverySuccess { + continue + } + if err != nil { + t.Fatalf("#%d DiscoverEndpoints failed: %v", i, err) + } + + if len(de.ACIEndpoints) != len(tt.expectedACIEndpoints) { + t.Errorf("ACIEndpoints array is wrong length want %d got %d", len(tt.expectedACIEndpoints), len(de.ACIEndpoints)) + } else { + for n, _ := range de.ACIEndpoints { + if de.ACIEndpoints[n] != tt.expectedACIEndpoints[n] { + t.Errorf("#%d ACIEndpoints[%d] mismatch: want %v got %v", i, n, tt.expectedACIEndpoints[n], de.ACIEndpoints[n]) + } + } + } + + if len(de.Keys) != len(tt.expectedKeys) { + t.Errorf("Keys array is wrong length want %d got %d", len(tt.expectedKeys), len(de.Keys)) + } else { + for n, _ := range de.Keys { + if de.Keys[n] != tt.expectedKeys[n] { + t.Errorf("#%d sig[%d] mismatch: want %v got %v", i, n, tt.expectedKeys[n], de.Keys[n]) + } + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/doc.go b/Godeps/_workspace/src/github.com/appc/spec/discovery/doc.go new file mode 100644 index 0000000..55bfc3a --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/doc.go @@ -0,0 +1,17 @@ +// Copyright 2015 The appc Authors +// +// 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 discovery contains an experimental implementation of the Image +// Discovery section of the appc specification. +package discovery diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/http.go b/Godeps/_workspace/src/github.com/appc/spec/discovery/http.go new file mode 100644 index 0000000..4198201 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/http.go @@ -0,0 +1,91 @@ +// Copyright 2015 The appc Authors +// +// 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 discovery + +import ( + "fmt" + "io" + "net" + "net/http" + "net/url" + "time" +) + +const ( + defaultDialTimeout = 5 * time.Second +) + +var ( + // Client is the default http.Client used for discovery requests. + Client *http.Client + + // httpGet is the internal object used by discovery to retrieve URLs; it is + // defined here so it can be overridden for testing + httpGet httpGetter +) + +// httpGetter is an interface used to wrap http.Client for real requests and +// allow easy mocking in local tests. +type httpGetter interface { + Get(url string) (resp *http.Response, err error) +} + +func init() { + t := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: func(n, a string) (net.Conn, error) { + return net.DialTimeout(n, a, defaultDialTimeout) + }, + } + Client = &http.Client{ + Transport: t, + } + httpGet = Client +} + +func httpsOrHTTP(name string, insecure bool) (urlStr string, body io.ReadCloser, err error) { + fetch := func(scheme string) (urlStr string, res *http.Response, err error) { + u, err := url.Parse(scheme + "://" + name) + if err != nil { + return "", nil, err + } + u.RawQuery = "ac-discovery=1" + urlStr = u.String() + res, err = httpGet.Get(urlStr) + return + } + closeBody := func(res *http.Response) { + if res != nil { + res.Body.Close() + } + } + urlStr, res, err := fetch("https") + if err != nil || res.StatusCode != http.StatusOK { + if insecure { + closeBody(res) + urlStr, res, err = fetch("http") + } + } + + if res != nil && res.StatusCode != http.StatusOK { + err = fmt.Errorf("expected a 200 OK got %d", res.StatusCode) + } + + if err != nil { + closeBody(res) + return "", nil, err + } + return urlStr, res.Body, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/http_test.go b/Godeps/_workspace/src/github.com/appc/spec/discovery/http_test.go new file mode 100644 index 0000000..4aa76a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/http_test.go @@ -0,0 +1,162 @@ +// Copyright 2015 The appc Authors +// +// 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 discovery + +import ( + "bytes" + "errors" + "io/ioutil" + "net/http" + "os" + "strings" + "testing" +) + +// mockHttpGetter defines a wrapper that allows returning a mocked response. +type mockHttpGetter struct { + getter func(url string) (resp *http.Response, err error) +} + +func (m *mockHttpGetter) Get(url string) (resp *http.Response, err error) { + return m.getter(url) +} + +func fakeHttpOrHttpsGet(filename string, httpSuccess bool, httpsSuccess bool, httpErrorCode int) func(uri string) (*http.Response, error) { + return func(uri string) (*http.Response, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + + var resp *http.Response + + switch { + case strings.HasPrefix(uri, "https://") && httpsSuccess: + fallthrough + case strings.HasPrefix(uri, "http://") && httpSuccess: + resp = &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: http.Header{ + "Content-Type": []string{"text/html"}, + }, + Body: f, + } + case httpErrorCode > 0: + resp = &http.Response{ + Status: "Error", + StatusCode: httpErrorCode, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: http.Header{ + "Content-Type": []string{"text/html"}, + }, + Body: ioutil.NopCloser(bytes.NewBufferString("")), + } + default: + err = errors.New("fakeHttpOrHttpsGet failed as requested") + return nil, err + } + + return resp, nil + } +} + +func TestHttpsOrHTTP(t *testing.T) { + tests := []struct { + name string + insecure bool + get httpgetter + expectUrlStr string + expectSuccess bool + }{ + { + "good-server", + false, + fakeHttpOrHttpsGet("myapp.html", true, true, 0), + "https://good-server?ac-discovery=1", + true, + }, + { + "file-not-found", + false, + fakeHttpOrHttpsGet("myapp.html", false, false, 404), + "", + false, + }, + { + "completely-broken-server", + false, + fakeHttpOrHttpsGet("myapp.html", false, false, 0), + "", + false, + }, + { + "file-only-on-http", + false, // do not accept fallback on http + fakeHttpOrHttpsGet("myapp.html", true, false, 404), + "", + false, + }, + { + "file-only-on-http", + true, // accept fallback on http + fakeHttpOrHttpsGet("myapp.html", true, false, 404), + "http://file-only-on-http?ac-discovery=1", + true, + }, + { + "https-server-is-down", + true, // accept fallback on http + fakeHttpOrHttpsGet("myapp.html", true, false, 0), + "http://https-server-is-down?ac-discovery=1", + true, + }, + } + + for i, tt := range tests { + httpGet = &mockHttpGetter{getter: tt.get} + urlStr, body, err := httpsOrHTTP(tt.name, tt.insecure) + if tt.expectSuccess { + if err != nil { + t.Fatalf("#%d httpsOrHTTP failed: %v", i, err) + } + if urlStr == "" { + t.Fatalf("#%d httpsOrHTTP didn't return a urlStr", i) + } + if urlStr != tt.expectUrlStr { + t.Fatalf("#%d httpsOrHTTP urlStr mismatch: want %s got %s", + i, tt.expectUrlStr, urlStr) + } + if body == nil { + t.Fatalf("#%d httpsOrHTTP didn't return a body", i) + } + } else { + if err == nil { + t.Fatalf("#%d httpsOrHTTP should have failed", i) + } + if urlStr != "" { + t.Fatalf("#%d httpsOrHTTP should not have returned a urlStr", i) + } + if body != nil { + t.Fatalf("#%d httpsOrHTTP should not have returned a body", i) + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/myapp.html b/Godeps/_workspace/src/github.com/appc/spec/discovery/myapp.html new file mode 100644 index 0000000..10c80eb --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/myapp.html @@ -0,0 +1,15 @@ + + + + + My app + + + + + + + +

My App

+ + diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/myapp2.html b/Godeps/_workspace/src/github.com/appc/spec/discovery/myapp2.html new file mode 100644 index 0000000..270b4c6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/myapp2.html @@ -0,0 +1,15 @@ + + + + + My app + + + + + + + +

My App

+ + diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/parse.go b/Godeps/_workspace/src/github.com/appc/spec/discovery/parse.go new file mode 100644 index 0000000..c1c12a6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/parse.go @@ -0,0 +1,142 @@ +// Copyright 2015 The appc Authors +// +// 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 discovery + +import ( + "fmt" + "net/url" + "strings" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +type App struct { + Name types.ACIdentifier + Labels map[types.ACIdentifier]string +} + +func NewApp(name string, labels map[types.ACIdentifier]string) (*App, error) { + if labels == nil { + labels = make(map[types.ACIdentifier]string, 0) + } + acn, err := types.NewACIdentifier(name) + if err != nil { + return nil, err + } + return &App{ + Name: *acn, + Labels: labels, + }, nil +} + +// NewAppFromString takes a command line app parameter and returns a map of labels. +// +// Example app parameters: +// example.com/reduce-worker:1.0.0 +// example.com/reduce-worker,channel=alpha,label=value +// example.com/reduce-worker:1.0.0,label=value +// +// As can be seen in above examples - colon, comma and equal sign have +// special meaning. If any of them has to be a part of a label's value +// then consider writing your own string to App parser. +func NewAppFromString(app string) (*App, error) { + var ( + name string + labels map[types.ACIdentifier]string + ) + + preparedApp, err := prepareAppString(app) + if err != nil { + return nil, err + } + v, err := url.ParseQuery(preparedApp) + if err != nil { + return nil, err + } + labels = make(map[types.ACIdentifier]string, 0) + for key, val := range v { + if len(val) > 1 { + return nil, fmt.Errorf("label %s with multiple values %q", key, val) + } + if key == "name" { + name = val[0] + continue + } + labelName, err := types.NewACIdentifier(key) + if err != nil { + return nil, err + } + labels[*labelName] = val[0] + } + a, err := NewApp(name, labels) + if err != nil { + return nil, err + } + return a, nil +} + +func prepareAppString(app string) (string, error) { + if err := checkColon(app); err != nil { + return "", err + } + + app = "name=" + strings.Replace(app, ":", ",version=", 1) + return makeQueryString(app) +} + +func checkColon(app string) error { + firstComma := strings.IndexRune(app, ',') + firstColon := strings.IndexRune(app, ':') + if firstColon > firstComma && firstComma > -1 { + return fmt.Errorf("malformed app string - colon may appear only right after the app name") + } + if strings.Count(app, ":") > 1 { + return fmt.Errorf("malformed app string - colon may appear at most once") + } + return nil +} + +func makeQueryString(app string) (string, error) { + parts := strings.Split(app, ",") + escapedParts := make([]string, len(parts)) + for i, s := range parts { + p := strings.SplitN(s, "=", 2) + if len(p) != 2 { + return "", fmt.Errorf("malformed app string - has a label without a value: %s", p[0]) + } + escapedParts[i] = fmt.Sprintf("%s=%s", p[0], url.QueryEscape(p[1])) + } + return strings.Join(escapedParts, "&"), nil +} + +func (a *App) Copy() *App { + ac := &App{ + Name: a.Name, + Labels: make(map[types.ACIdentifier]string, 0), + } + for k, v := range a.Labels { + ac.Labels[k] = v + } + return ac +} + +// String returns the URL-like image name +func (a *App) String() string { + img := a.Name.String() + for n, v := range a.Labels { + img += fmt.Sprintf(",%s=%s", n, v) + } + return img +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/discovery/parse_test.go b/Godeps/_workspace/src/github.com/appc/spec/discovery/parse_test.go new file mode 100644 index 0000000..fe4c596 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/discovery/parse_test.go @@ -0,0 +1,211 @@ +// Copyright 2015 The appc Authors +// +// 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 discovery + +import ( + "reflect" + "testing" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +func TestNewAppFromString(t *testing.T) { + tests := []struct { + in string + + w *App + werr bool + }{ + { + "example.com/reduce-worker:1.0.0", + + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + }, + }, + false, + }, + { + "example.com/reduce-worker,channel=alpha,label=value", + + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{ + "channel": "alpha", + "label": "value", + }, + }, + + false, + }, + { + "example.com/app:1.2.3,special=!*'();@&+$/?#[],channel=beta", + + &App{ + Name: "example.com/app", + Labels: map[types.ACIdentifier]string{ + "version": "1.2.3", + "special": "!*'();@&+$/?#[]", + "channel": "beta", + }, + }, + + false, + }, + + // bad AC name for app + { + "not an app name", + + nil, + true, + }, + + // bad URL escape (bad query) + { + "example.com/garbage%8 939", + + nil, + true, + }, + + // multi-value labels + { + "foo.com/bar,channel=alpha,dog=woof,channel=beta", + + nil, + true, + }, + // colon coming after some label instead of being + // right after the name + { + "example.com/app,channel=beta:1.2.3", + + nil, + true, + }, + // two colons in string + { + "example.com/app:3.2.1,channel=beta:1.2.3", + + nil, + true, + }, + // two version labels, one implicit, one explicit + { + "example.com/app:3.2.1,version=1.2.3", + + nil, + true, + }, + } + for i, tt := range tests { + g, err := NewAppFromString(tt.in) + gerr := (err != nil) + if !reflect.DeepEqual(g, tt.w) { + t.Errorf("#%d: got %v, want %v", i, g, tt.w) + } + if gerr != tt.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err) + } + } +} + +func TestAppString(t *testing.T) { + tests := []struct { + a *App + out string + }{ + { + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{}, + }, + "example.com/reduce-worker", + }, + { + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + }, + }, + "example.com/reduce-worker:1.0.0", + }, + { + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{ + "channel": "alpha", + "label": "value", + }, + }, + "example.com/reduce-worker,channel=alpha,label=value", + }, + } + for i, tt := range tests { + appString := tt.a.String() + + g, err := NewAppFromString(appString) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !reflect.DeepEqual(g, tt.a) { + t.Errorf("#%d: got %#v, want %#v", i, g, tt.a) + } + } +} + +func TestAppCopy(t *testing.T) { + tests := []struct { + a *App + out string + }{ + { + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{}, + }, + "example.com/reduce-worker", + }, + { + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + }, + }, + "example.com/reduce-worker:1.0.0", + }, + { + &App{ + Name: "example.com/reduce-worker", + Labels: map[types.ACIdentifier]string{ + "channel": "alpha", + "label": "value", + }, + }, + "example.com/reduce-worker,channel=alpha,label=value", + }, + } + for i, tt := range tests { + appCopy := tt.a.Copy() + if !reflect.DeepEqual(appCopy, tt.a) { + t.Errorf("#%d: got %#v, want %#v", i, appCopy, tt.a) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/pkg/device/device_posix.go b/Godeps/_workspace/src/github.com/appc/spec/pkg/device/device_posix.go new file mode 100644 index 0000000..a0bdd77 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/pkg/device/device_posix.go @@ -0,0 +1,57 @@ +// Copyright 2015 The appc Authors +// +// 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. + +// +build linux freebsd netbsd openbsd darwin + +package device + +/* +#define _BSD_SOURCE +#define _DEFAULT_SOURCE +#include + +unsigned int +my_major(dev_t dev) +{ + return major(dev); +} + +unsigned int +my_minor(dev_t dev) +{ + return minor(dev); +} + +dev_t +my_makedev(unsigned int maj, unsigned int min) +{ + return makedev(maj, min); +} +*/ +import "C" + +func Major(rdev uint64) uint { + major := C.my_major(C.dev_t(rdev)) + return uint(major) +} + +func Minor(rdev uint64) uint { + minor := C.my_minor(C.dev_t(rdev)) + return uint(minor) +} + +func Makedev(maj uint, min uint) uint64 { + dev := C.my_makedev(C.uint(maj), C.uint(min)) + return uint64(dev) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/doc.go b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/doc.go new file mode 100644 index 0000000..047a0c0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/doc.go @@ -0,0 +1,17 @@ +// Copyright 2015 The appc Authors +// +// 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 tarheader contains a simple abstraction to accurately create +// tar.Headers on different operating systems. +package tarheader diff --git a/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_darwin.go b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_darwin.go new file mode 100644 index 0000000..8f68ee7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_darwin.go @@ -0,0 +1,39 @@ +// Copyright 2015 The appc Authors +// +// 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. + +//+build darwin + +package tarheader + +import ( + "archive/tar" + "os" + "syscall" + "time" +) + +func init() { + populateHeaderStat = append(populateHeaderStat, populateHeaderCtime) +} + +func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return + } + + sec, nsec := st.Ctimespec.Unix() + ctime := time.Unix(sec, nsec) + h.ChangeTime = ctime +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_linux.go b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_linux.go new file mode 100644 index 0000000..2055024 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_linux.go @@ -0,0 +1,39 @@ +// Copyright 2015 The appc Authors +// +// 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. + +// +build linux + +package tarheader + +import ( + "archive/tar" + "os" + "syscall" + "time" +) + +func init() { + populateHeaderStat = append(populateHeaderStat, populateHeaderCtime) +} + +func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return + } + + sec, nsec := st.Ctim.Unix() + ctime := time.Unix(sec, nsec) + h.ChangeTime = ctime +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix.go b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix.go new file mode 100644 index 0000000..da4111e --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix.go @@ -0,0 +1,50 @@ +// Copyright 2015 The appc Authors +// +// 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. + +// +build linux freebsd netbsd openbsd + +package tarheader + +import ( + "archive/tar" + "os" + "syscall" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/pkg/device" +) + +func init() { + populateHeaderStat = append(populateHeaderStat, populateHeaderUnix) +} + +func populateHeaderUnix(h *tar.Header, fi os.FileInfo, seen map[uint64]string) { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return + } + h.Uid = int(st.Uid) + h.Gid = int(st.Gid) + if st.Mode&syscall.S_IFMT == syscall.S_IFBLK || st.Mode&syscall.S_IFMT == syscall.S_IFCHR { + h.Devminor = int64(device.Minor(uint64(st.Rdev))) + h.Devmajor = int64(device.Major(uint64(st.Rdev))) + } + // If we have already seen this inode, generate a hardlink + p, ok := seen[uint64(st.Ino)] + if ok { + h.Linkname = p + h.Typeflag = tar.TypeLink + } else { + seen[uint64(st.Ino)] = h.Name + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix_test.go b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix_test.go new file mode 100644 index 0000000..af75dc1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/pop_posix_test.go @@ -0,0 +1,77 @@ +// Copyright 2015 The appc Authors +// +// 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. + +// +build linux freebsd netbsd openbsd + +package tarheader + +import ( + "archive/tar" + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" +) + +// mknod requires privilege ... +func TestHeaderUnixDev(t *testing.T) { + hExpect := tar.Header{ + Name: "./dev/test0", + Size: 0, + Typeflag: tar.TypeBlock, + Devminor: 5, + Devmajor: 233, + } + // make our test block device + var path string + { + var err error + path, err = ioutil.TempDir("", "tarheader-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(path) + if err := os.Mkdir(filepath.Join(path, "dev"), os.FileMode(0755)); err != nil { + t.Fatal(err) + } + mode := uint32(hExpect.Mode&07777) | syscall.S_IFBLK + dev := uint32(((hExpect.Devminor & 0xfff00) << 12) | ((hExpect.Devmajor & 0xfff) << 8) | (hExpect.Devminor & 0xff)) + if err := syscall.Mknod(filepath.Join(path, hExpect.Name), mode, int(dev)); err != nil { + if err == syscall.EPERM { + t.Skip("no permission to CAP_MKNOD") + } + t.Fatal(err) + } + } + fi, err := os.Stat(filepath.Join(path, hExpect.Name)) + if err != nil { + t.Fatal(err) + } + + hGot := tar.Header{ + Name: "./dev/test0", + Size: 0, + Typeflag: tar.TypeBlock, + } + + seen := map[uint64]string{} + populateHeaderUnix(&hGot, fi, seen) + if hGot.Devminor != hExpect.Devminor { + t.Errorf("dev minor: got %d, expected %d", hGot.Devminor, hExpect.Devminor) + } + if hGot.Devmajor != hExpect.Devmajor { + t.Errorf("dev major: got %d, expected %d", hGot.Devmajor, hExpect.Devmajor) + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/tarheader.go b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/tarheader.go new file mode 100644 index 0000000..dc16c33 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/pkg/tarheader/tarheader.go @@ -0,0 +1,28 @@ +// Copyright 2015 The appc Authors +// +// 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 tarheader + +import ( + "archive/tar" + "os" +) + +var populateHeaderStat []func(h *tar.Header, fi os.FileInfo, seen map[uint64]string) + +func Populate(h *tar.Header, fi os.FileInfo, seen map[uint64]string) { + for _, pop := range populateHeaderStat { + pop(h, fi, seen) + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/doc.go b/Godeps/_workspace/src/github.com/appc/spec/schema/doc.go new file mode 100644 index 0000000..ba38154 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/doc.go @@ -0,0 +1,25 @@ +// Copyright 2015 The appc Authors +// +// 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 schema provides definitions for the JSON schema of the different +// manifests in the App Container Specification. The manifests are canonically +// represented in their respective structs: +// - `ImageManifest` +// - `PodManifest` +// +// Validation is performed through serialization: if a blob of JSON data will +// unmarshal to one of the *Manifests, it is considered a valid implementation +// of the standard. Similarly, if a constructed *Manifest struct marshals +// successfully to JSON, it must be valid. +package schema diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/image.go b/Godeps/_workspace/src/github.com/appc/spec/schema/image.go new file mode 100644 index 0000000..87922fc --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/image.go @@ -0,0 +1,95 @@ +// Copyright 2015 The appc Authors +// +// 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 schema + +import ( + "encoding/json" + "errors" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +const ( + ACIExtension = ".aci" + ImageManifestKind = types.ACKind("ImageManifest") +) + +type ImageManifest struct { + ACKind types.ACKind `json:"acKind"` + ACVersion types.SemVer `json:"acVersion"` + Name types.ACIdentifier `json:"name"` + Labels types.Labels `json:"labels,omitempty"` + App *types.App `json:"app,omitempty"` + Annotations types.Annotations `json:"annotations,omitempty"` + Dependencies types.Dependencies `json:"dependencies,omitempty"` + PathWhitelist []string `json:"pathWhitelist,omitempty"` +} + +// imageManifest is a model to facilitate extra validation during the +// unmarshalling of the ImageManifest +type imageManifest ImageManifest + +func BlankImageManifest() *ImageManifest { + return &ImageManifest{ACKind: ImageManifestKind, ACVersion: AppContainerVersion} +} + +func (im *ImageManifest) UnmarshalJSON(data []byte) error { + a := imageManifest(*im) + err := json.Unmarshal(data, &a) + if err != nil { + return err + } + nim := ImageManifest(a) + if err := nim.assertValid(); err != nil { + return err + } + *im = nim + return nil +} + +func (im ImageManifest) MarshalJSON() ([]byte, error) { + if err := im.assertValid(); err != nil { + return nil, err + } + return json.Marshal(imageManifest(im)) +} + +var imKindError = types.InvalidACKindError(ImageManifestKind) + +// assertValid performs extra assertions on an ImageManifest to ensure that +// fields are set appropriately, etc. It is used exclusively when marshalling +// and unmarshalling an ImageManifest. Most field-specific validation is +// performed through the individual types being marshalled; assertValid() +// should only deal with higher-level validation. +func (im *ImageManifest) assertValid() error { + if im.ACKind != ImageManifestKind { + return imKindError + } + if im.ACVersion.Empty() { + return errors.New(`acVersion must be set`) + } + if im.Name.Empty() { + return errors.New(`name must be set`) + } + return nil +} + +func (im *ImageManifest) GetLabel(name string) (val string, ok bool) { + return im.Labels.Get(name) +} + +func (im *ImageManifest) GetAnnotation(name string) (val string, ok bool) { + return im.Annotations.Get(name) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/image_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/image_test.go new file mode 100644 index 0000000..891fd99 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/image_test.go @@ -0,0 +1,62 @@ +// Copyright 2015 The appc Authors +// +// 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 schema + +import "testing" + +func TestEmptyApp(t *testing.T) { + imj := ` + { + "acKind": "ImageManifest", + "acVersion": "0.7.1", + "name": "example.com/test" + } + ` + + var im ImageManifest + + err := im.UnmarshalJSON([]byte(imj)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Marshal and Unmarshal to verify that no "app": {} is generated on + // Marshal and converted to empty struct on Unmarshal + buf, err := im.MarshalJSON() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + err = im.UnmarshalJSON(buf) + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestImageManifestMerge(t *testing.T) { + imj := `{"name": "example.com/test"}` + im := &ImageManifest{} + + if im.UnmarshalJSON([]byte(imj)) == nil { + t.Fatal("Manifest JSON without acKind and acVersion unmarshalled successfully") + } + + im = BlankImageManifest() + + err := im.UnmarshalJSON([]byte(imj)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/kind.go b/Godeps/_workspace/src/github.com/appc/spec/schema/kind.go new file mode 100644 index 0000000..a4c547c --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/kind.go @@ -0,0 +1,42 @@ +// Copyright 2015 The appc Authors +// +// 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 schema + +import ( + "encoding/json" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +type Kind struct { + ACVersion types.SemVer `json:"acVersion"` + ACKind types.ACKind `json:"acKind"` +} + +type kind Kind + +func (k *Kind) UnmarshalJSON(data []byte) error { + nk := kind{} + err := json.Unmarshal(data, &nk) + if err != nil { + return err + } + *k = Kind(nk) + return nil +} + +func (k Kind) MarshalJSON() ([]byte, error) { + return json.Marshal(kind(k)) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/common_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/common_test.go new file mode 100644 index 0000000..517d958 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/common_test.go @@ -0,0 +1,57 @@ +// Copyright 2015 The appc Authors +// +// 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 lastditch + +import ( + "fmt" + "strings" +) + +// extJ returns a JSON snippet describing an extra field with a given +// name +func extJ(name string) string { + return fmt.Sprintf(`"%s": [],`, name) +} + +// labsJ returns a labels array JSON snippet with given labels +func labsJ(labels ...string) string { + return fmt.Sprintf("[%s]", strings.Join(labels, ",")) +} + +// labsI returns a labels array instance with given labels +func labsI(labels ...Label) Labels { + if labels == nil { + return Labels{} + } + return labels +} + +// labJ returns a label JSON snippet with given name and value +func labJ(name, value, extra string) string { + return fmt.Sprintf(` + { + %s + "name": "%s", + "value": "%s" + }`, extra, name, value) +} + +// labI returns a label instance with given name and value +func labI(name, value string) Label { + return Label{ + Name: name, + Value: value, + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/doc.go b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/doc.go new file mode 100644 index 0000000..9cc5734 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/doc.go @@ -0,0 +1,28 @@ +// Copyright 2015 The appc Authors +// +// 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 lastditch provides fallback redefinitions of parts of +// schemas provided by schema package. +// +// Almost no validation of schemas is done (besides checking if data +// really is `JSON`-encoded and kind is either `ImageManifest` or +// `PodManifest`. This is to get as much data as possible from an +// invalid manifest. The main aim of the package is to be used for the +// better error reporting. The another aim might be to force some +// operation (like removing a pod), which would otherwise fail because +// of an invalid manifest. +// +// To avoid validation during deserialization, types provided by this +// package use plain strings. +package lastditch diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/image.go b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/image.go new file mode 100644 index 0000000..3a54893 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/image.go @@ -0,0 +1,45 @@ +// Copyright 2015 The appc Authors +// +// 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 lastditch + +import ( + "encoding/json" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema" + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +type ImageManifest struct { + ACVersion string `json:"acVersion"` + ACKind string `json:"acKind"` + Name string `json:"name"` + Labels Labels `json:"labels,omitempty"` +} + +// a type just to avoid a recursion during unmarshalling +type imageManifest ImageManifest + +func (im *ImageManifest) UnmarshalJSON(data []byte) error { + i := imageManifest(*im) + err := json.Unmarshal(data, &i) + if err != nil { + return err + } + if i.ACKind != string(schema.ImageManifestKind) { + return types.InvalidACKindError(schema.ImageManifestKind) + } + *im = ImageManifest(i) + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/image_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/image_test.go new file mode 100644 index 0000000..a3a7cb4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/image_test.go @@ -0,0 +1,118 @@ +// Copyright 2015 The appc Authors +// +// 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 lastditch + +import ( + "fmt" + "reflect" + "testing" +) + +func TestInvalidImageManifest(t *testing.T) { + tests := []struct { + desc string + json string + expected ImageManifest + }{ + { + desc: "Check an empty image manifest", + json: imgJ("", labsJ(), ""), + expected: imgI("", labsI()), + }, + { + desc: "Check an image manifest with an empty label", + json: imgJ("example.com/test!", labsJ(labJ("", "", "")), ""), + expected: imgI("example.com/test!", labsI(labI("", ""))), + }, + { + desc: "Check an image manifest with an invalid name", + json: imgJ("example.com/test!", labsJ(), ""), + expected: imgI("example.com/test!", labsI()), + }, + { + desc: "Check an image manifest with labels with invalid names", + json: imgJ("im", labsJ(labJ("!n1", "v1", ""), labJ("N2~", "v2", "")), ""), + expected: imgI("im", labsI(labI("!n1", "v1"), labI("N2~", "v2"))), + }, + { + desc: "Check an image manifest with duplicated labels", + json: imgJ("im", labsJ(labJ("n1", "v1", ""), labJ("n1", "v2", "")), ""), + expected: imgI("im", labsI(labI("n1", "v1"), labI("n1", "v2"))), + }, + { + desc: "Check an image manifest with some extra fields", + json: imgJ("im", labsJ(), extJ("stuff")), + expected: imgI("im", labsI()), + }, + { + desc: "Check an image manifest with a label containing some extra fields", + json: imgJ("im", labsJ(labJ("n1", "v1", extJ("clutter"))), extJ("stuff")), + expected: imgI("im", labsI(labI("n1", "v1"))), + }, + } + for _, tt := range tests { + got := ImageManifest{} + if err := got.UnmarshalJSON([]byte(tt.json)); err != nil { + t.Errorf("%s: unexpected error during unmarshalling image manifest: %v", tt.desc, err) + } + if !reflect.DeepEqual(tt.expected, got) { + t.Errorf("%s: did not get expected image manifest, got:\n %#v\nexpected:\n %#v", tt.desc, got, tt.expected) + } + } +} + +func TestBogusImageManifest(t *testing.T) { + bogus := []string{` + { + "acKind": "Bogus", + "acVersion": "0.7.1", + } + `, ` + + + Certainly not a JSON + + `, + } + + for _, str := range bogus { + im := ImageManifest{} + if im.UnmarshalJSON([]byte(str)) == nil { + t.Errorf("bogus image manifest unmarshalled successfully: %s", str) + } + } +} + +// imgJ returns an image manifest JSON with given name and labels +func imgJ(name, labels, extra string) string { + return fmt.Sprintf(` + { + %s + "acKind": "ImageManifest", + "acVersion": "0.7.1", + "name": "%s", + "labels": %s + }`, extra, name, labels) +} + +// imgI returns an image manifest instance with given name and labels +func imgI(name string, labels Labels) ImageManifest { + return ImageManifest{ + ACVersion: "0.7.1", + ACKind: "ImageManifest", + Name: name, + Labels: labels, + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/labels.go b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/labels.go new file mode 100644 index 0000000..5cf93a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/labels.go @@ -0,0 +1,38 @@ +// Copyright 2015 The appc Authors +// +// 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 lastditch + +import ( + "encoding/json" +) + +type Labels []Label + +// a type just to avoid a recursion during unmarshalling +type labels Labels + +type Label struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func (l *Labels) UnmarshalJSON(data []byte) error { + var jl labels + if err := json.Unmarshal(data, &jl); err != nil { + return err + } + *l = Labels(jl) + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/pod.go b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/pod.go new file mode 100644 index 0000000..f038b4c --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/pod.go @@ -0,0 +1,57 @@ +// Copyright 2015 The appc Authors +// +// 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 lastditch + +import ( + "encoding/json" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema" + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +type PodManifest struct { + ACVersion string `json:"acVersion"` + ACKind string `json:"acKind"` + Apps AppList `json:"apps"` +} + +type AppList []RuntimeApp + +type RuntimeApp struct { + Name string `json:"name"` + Image RuntimeImage `json:"image"` +} + +type RuntimeImage struct { + Name string `json:"name"` + ID string `json:"id"` + Labels Labels `json:"labels,omitempty"` +} + +// a type just to avoid a recursion during unmarshalling +type podManifest PodManifest + +func (pm *PodManifest) UnmarshalJSON(data []byte) error { + p := podManifest(*pm) + err := json.Unmarshal(data, &p) + if err != nil { + return err + } + if p.ACKind != string(schema.PodManifestKind) { + return types.InvalidACKindError(schema.PodManifestKind) + } + *pm = PodManifest(p) + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/pod_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/pod_test.go new file mode 100644 index 0000000..b755ad8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch/pod_test.go @@ -0,0 +1,202 @@ +// Copyright 2015 The appc Authors +// +// 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 lastditch + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +func TestInvalidPodManifest(t *testing.T) { + tests := []struct { + desc string + json string + expected PodManifest + }{ + { + desc: "Check an empty pod manifest", + json: podJ(appsJ(), ""), + expected: podI(appsI()), + }, + { + desc: "Check a pod manifest with an empty app", + json: podJ(appsJ(appJ("", rImgJ("i", "id", labsJ(), ""), "")), ""), + expected: podI(appsI(appI("", rImgI("i", "id", labsI())))), + }, + { + desc: "Check a pod manifest with an app based on an empty image", + json: podJ(appsJ(appJ("a", rImgJ("", "", labsJ(), ""), "")), ""), + expected: podI(appsI(appI("a", rImgI("", "", labsI())))), + }, + { + desc: "Check a pod manifest with an app based on an image containing an empty label", + json: podJ(appsJ(appJ("a", rImgJ("", "", labsJ(labJ("", "", "")), ""), "")), ""), + expected: podI(appsI(appI("a", rImgI("", "", labsI(labI("", "")))))), + }, + { + desc: "Check a pod manifest with an invalid app name", + json: podJ(appsJ(appJ("!", rImgJ("i", "id", labsJ(), ""), "")), ""), + expected: podI(appsI(appI("!", rImgI("i", "id", labsI())))), + }, + { + desc: "Check a pod manifest with duplicated app names", + json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(), ""), ""), appJ("a", rImgJ("", "", labsJ(), ""), "")), ""), + expected: podI(appsI(appI("a", rImgI("i", "id", labsI())), appI("a", rImgI("", "", labsI())))), + }, + { + desc: "Check a pod manifest with an invalid image name and ID", + json: podJ(appsJ(appJ("?", rImgJ("!!!", "&&&", labsJ(), ""), "")), ""), + expected: podI(appsI(appI("?", rImgI("!!!", "&&&", labsI())))), + }, + { + desc: "Check a pod manifest with an app based on an image containing labels with invalid names", + json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(labJ("!n1", "v1", ""), labJ("N2~", "v2", "")), ""), "")), ""), + expected: podI(appsI(appI("a", rImgI("i", "id", labsI(labI("!n1", "v1"), labI("N2~", "v2")))))), + }, + { + desc: "Check a pod manifest with an app based on an image containing repeated labels", + json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(labJ("n1", "v1", ""), labJ("n1", "v2", "")), ""), "")), ""), + expected: podI(appsI(appI("a", rImgI("i", "id", labsI(labI("n1", "v1"), labI("n1", "v2")))))), + }, + { + desc: "Check a pod manifest with some extra fields", + json: podJ(appsJ(), extJ("goblins")), + expected: podI(appsI()), + }, + { + desc: "Check a pod manifest with an app containing some extra fields", + json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(), ""), extJ("trolls"))), extJ("goblins")), + expected: podI(appsI(appI("a", rImgI("i", "id", labsI())))), + }, + { + desc: "Check a pod manifest with an app based on an image containing some extra fields", + json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(), extJ("stuff")), extJ("trolls"))), extJ("goblins")), + expected: podI(appsI(appI("a", rImgI("i", "id", labsI())))), + }, + { + desc: "Check a pod manifest with an app based on an image containing labels with some extra fields", + json: podJ(appsJ(appJ("a", rImgJ("i", "id", labsJ(labJ("n", "v", extJ("color"))), extJ("stuff")), extJ("trolls"))), extJ("goblins")), + expected: podI(appsI(appI("a", rImgI("i", "id", labsI(labI("n", "v")))))), + }, + } + for _, tt := range tests { + got := PodManifest{} + if err := got.UnmarshalJSON([]byte(tt.json)); err != nil { + t.Errorf("%s: unexpected error during unmarshalling pod manifest: %v", tt.desc, err) + } + if !reflect.DeepEqual(tt.expected, got) { + t.Errorf("%s: did not get expected pod manifest, got:\n %#v\nexpected:\n %#v", tt.desc, got, tt.expected) + } + } +} + +func TestBogusPodManifest(t *testing.T) { + bogus := []string{ + ` + { + "acKind": "Bogus", + "acVersion": "0.7.1", + } + `, + ` + + + Certainly not a JSON + + `, + } + + for _, str := range bogus { + pm := PodManifest{} + if pm.UnmarshalJSON([]byte(str)) == nil { + t.Errorf("bogus pod manifest unmarshalled successfully: %s", str) + } + } +} + +// podJ returns a pod manifest JSON with given apps +func podJ(apps, extra string) string { + return fmt.Sprintf(` + { + %s + "acKind": "PodManifest", + "acVersion": "0.7.1", + "apps": %s + }`, extra, apps) +} + +// podI returns a pod manifest instance with given apps +func podI(apps AppList) PodManifest { + return PodManifest{ + ACVersion: "0.7.1", + ACKind: "PodManifest", + Apps: apps, + } +} + +// appsJ returns an applist JSON snippet with given apps +func appsJ(apps ...string) string { + return fmt.Sprintf("[%s]", strings.Join(apps, ",")) +} + +// appsI returns an applist instance with given apps +func appsI(apps ...RuntimeApp) AppList { + if apps == nil { + return AppList{} + } + return apps +} + +// appJ returns an app JSON snippet with given name and image +func appJ(name, image, extra string) string { + return fmt.Sprintf(` + { + %s + "name": "%s", + "image": %s + }`, extra, name, image) +} + +// appI returns an app instance with given name and image +func appI(name string, image RuntimeImage) RuntimeApp { + return RuntimeApp{ + Name: name, + Image: image, + } +} + +// rImgJ returns a runtime image JSON snippet with given name, id and +// labels +func rImgJ(name, id, labels, extra string) string { + return fmt.Sprintf(` + { + %s + "name": "%s", + "id": "%s", + "labels": %s + }`, extra, name, id, labels) +} + +// rImgI returns a runtime image instance with given name, id and +// labels +func rImgI(name, id string, labels Labels) RuntimeImage { + return RuntimeImage{ + Name: name, + ID: id, + Labels: labels, + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/pod.go b/Godeps/_workspace/src/github.com/appc/spec/schema/pod.go new file mode 100644 index 0000000..d0fd149 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/pod.go @@ -0,0 +1,160 @@ +// Copyright 2015 The appc Authors +// +// 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 schema + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +const PodManifestKind = types.ACKind("PodManifest") + +type PodManifest struct { + ACVersion types.SemVer `json:"acVersion"` + ACKind types.ACKind `json:"acKind"` + Apps AppList `json:"apps"` + Volumes []types.Volume `json:"volumes"` + Isolators []types.Isolator `json:"isolators"` + Annotations types.Annotations `json:"annotations"` + Ports []types.ExposedPort `json:"ports"` +} + +// podManifest is a model to facilitate extra validation during the +// unmarshalling of the PodManifest +type podManifest PodManifest + +func BlankPodManifest() *PodManifest { + return &PodManifest{ACKind: PodManifestKind, ACVersion: AppContainerVersion} +} + +func (pm *PodManifest) UnmarshalJSON(data []byte) error { + p := podManifest(*pm) + err := json.Unmarshal(data, &p) + if err != nil { + return err + } + npm := PodManifest(p) + if err := npm.assertValid(); err != nil { + return err + } + *pm = npm + return nil +} + +func (pm PodManifest) MarshalJSON() ([]byte, error) { + if err := pm.assertValid(); err != nil { + return nil, err + } + return json.Marshal(podManifest(pm)) +} + +var pmKindError = types.InvalidACKindError(PodManifestKind) + +// assertValid performs extra assertions on an PodManifest to +// ensure that fields are set appropriately, etc. It is used exclusively when +// marshalling and unmarshalling an PodManifest. Most +// field-specific validation is performed through the individual types being +// marshalled; assertValid() should only deal with higher-level validation. +func (pm *PodManifest) assertValid() error { + if pm.ACKind != PodManifestKind { + return pmKindError + } + return nil +} + +type AppList []RuntimeApp + +type appList AppList + +func (al *AppList) UnmarshalJSON(data []byte) error { + a := appList{} + err := json.Unmarshal(data, &a) + if err != nil { + return err + } + nal := AppList(a) + if err := nal.assertValid(); err != nil { + return err + } + *al = nal + return nil +} + +func (al AppList) MarshalJSON() ([]byte, error) { + if err := al.assertValid(); err != nil { + return nil, err + } + return json.Marshal(appList(al)) +} + +func (al AppList) assertValid() error { + seen := map[types.ACName]bool{} + for _, a := range al { + if _, ok := seen[a.Name]; ok { + return fmt.Errorf(`duplicate apps of name %q`, a.Name) + } + seen[a.Name] = true + } + return nil +} + +// Get retrieves an app by the specified name from the AppList; if there is +// no such app, nil is returned. The returned *RuntimeApp MUST be considered +// read-only. +func (al AppList) Get(name types.ACName) *RuntimeApp { + for _, a := range al { + if name.Equals(a.Name) { + aa := a + return &aa + } + } + return nil +} + +// Mount describes the mapping between a volume and the path it is mounted +// inside of an app's filesystem. +type Mount struct { + Volume types.ACName `json:"volume"` + Path string `json:"path"` +} + +func (r Mount) assertValid() error { + if r.Volume.Empty() { + return errors.New("volume must be set") + } + if r.Path == "" { + return errors.New("path must be set") + } + return nil +} + +// RuntimeApp describes an application referenced in a PodManifest +type RuntimeApp struct { + Name types.ACName `json:"name"` + Image RuntimeImage `json:"image"` + App *types.App `json:"app,omitempty"` + Mounts []Mount `json:"mounts,omitempty"` + Annotations types.Annotations `json:"annotations,omitempty"` +} + +// RuntimeImage describes an image referenced in a RuntimeApp +type RuntimeImage struct { + Name *types.ACIdentifier `json:"name,omitempty"` + ID types.Hash `json:"id"` + Labels types.Labels `json:"labels,omitempty"` +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/pod_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/pod_test.go new file mode 100644 index 0000000..cbafb68 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/pod_test.go @@ -0,0 +1,73 @@ +// Copyright 2015 The appc Authors +// +// 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 schema + +import ( + "testing" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +func TestPodManifestMerge(t *testing.T) { + pmj := `{}` + pm := &PodManifest{} + + if pm.UnmarshalJSON([]byte(pmj)) == nil { + t.Fatal("Manifest JSON without acKind and acVersion unmarshalled successfully") + } + + pm = BlankPodManifest() + + err := pm.UnmarshalJSON([]byte(pmj)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestAppList(t *testing.T) { + ri := RuntimeImage{ + ID: *types.NewHashSHA512([]byte{}), + } + al := AppList{ + RuntimeApp{ + Name: "foo", + Image: ri, + }, + RuntimeApp{ + Name: "bar", + Image: ri, + }, + } + if _, err := al.MarshalJSON(); err != nil { + t.Errorf("want err=nil, got %v", err) + } + dal := AppList{ + RuntimeApp{ + Name: "foo", + Image: ri, + }, + RuntimeApp{ + Name: "bar", + Image: ri, + }, + RuntimeApp{ + Name: "foo", + Image: ri, + }, + } + if _, err := dal.MarshalJSON(); err == nil { + t.Errorf("want err, got nil") + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/acidentifier.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acidentifier.go new file mode 100644 index 0000000..904eda5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acidentifier.go @@ -0,0 +1,145 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + "regexp" + "strings" +) + +var ( + // ValidACIdentifier is a regular expression that defines a valid ACIdentifier + ValidACIdentifier = regexp.MustCompile("^[a-z0-9]+([-._~/][a-z0-9]+)*$") + + invalidACIdentifierChars = regexp.MustCompile("[^a-z0-9-._~/]") + invalidACIdentifierEdges = regexp.MustCompile("(^[-._~/]+)|([-._~/]+$)") + + ErrEmptyACIdentifier = ACIdentifierError("ACIdentifier cannot be empty") + ErrInvalidEdgeInACIdentifier = ACIdentifierError("ACIdentifier must start and end with only lower case " + + "alphanumeric characters") + ErrInvalidCharInACIdentifier = ACIdentifierError("ACIdentifier must contain only lower case " + + `alphanumeric characters plus "-._~/"`) +) + +// ACIdentifier (an App-Container Identifier) is a format used by keys in image names +// and image labels of the App Container Standard. An ACIdentifier is restricted to numeric +// and lowercase URI unreserved characters defined in URI RFC[1]; all alphabetical characters +// must be lowercase only. Furthermore, the first and last character ("edges") must be +// alphanumeric, and an ACIdentifier cannot be empty. Programmatically, an ACIdentifier must +// conform to the regular expression ValidACIdentifier. +// +// [1] http://tools.ietf.org/html/rfc3986#section-2.3 +type ACIdentifier string + +func (n ACIdentifier) String() string { + return string(n) +} + +// Set sets the ACIdentifier to the given value, if it is valid; if not, +// an error is returned. +func (n *ACIdentifier) Set(s string) error { + nn, err := NewACIdentifier(s) + if err == nil { + *n = *nn + } + return err +} + +// Equals checks whether a given ACIdentifier is equal to this one. +func (n ACIdentifier) Equals(o ACIdentifier) bool { + return strings.ToLower(string(n)) == strings.ToLower(string(o)) +} + +// Empty returns a boolean indicating whether this ACIdentifier is empty. +func (n ACIdentifier) Empty() bool { + return n.String() == "" +} + +// NewACIdentifier generates a new ACIdentifier from a string. If the given string is +// not a valid ACIdentifier, nil and an error are returned. +func NewACIdentifier(s string) (*ACIdentifier, error) { + n := ACIdentifier(s) + if err := n.assertValid(); err != nil { + return nil, err + } + return &n, nil +} + +// MustACIdentifier generates a new ACIdentifier from a string, If the given string is +// not a valid ACIdentifier, it panics. +func MustACIdentifier(s string) *ACIdentifier { + n, err := NewACIdentifier(s) + if err != nil { + panic(err) + } + return n +} + +func (n ACIdentifier) assertValid() error { + s := string(n) + if len(s) == 0 { + return ErrEmptyACIdentifier + } + if invalidACIdentifierChars.MatchString(s) { + return ErrInvalidCharInACIdentifier + } + if invalidACIdentifierEdges.MatchString(s) { + return ErrInvalidEdgeInACIdentifier + } + return nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (n *ACIdentifier) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + nn, err := NewACIdentifier(s) + if err != nil { + return err + } + *n = *nn + return nil +} + +// MarshalJSON implements the json.Marshaler interface +func (n ACIdentifier) MarshalJSON() ([]byte, error) { + if err := n.assertValid(); err != nil { + return nil, err + } + return json.Marshal(n.String()) +} + +// SanitizeACIdentifier replaces every invalid ACIdentifier character in s with an underscore +// making it a legal ACIdentifier string. If the character is an upper case letter it +// replaces it with its lower case. It also removes illegal edge characters +// (hyphens, period, underscore, tilde and slash). +// +// This is a helper function and its algorithm is not part of the spec. It +// should not be called without the user explicitly asking for a suggestion. +func SanitizeACIdentifier(s string) (string, error) { + s = strings.ToLower(s) + s = invalidACIdentifierChars.ReplaceAllString(s, "_") + s = invalidACIdentifierEdges.ReplaceAllString(s, "") + + if s == "" { + return "", errors.New("must contain at least one valid character") + } + + return s, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/acidentifier_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acidentifier_test.go new file mode 100644 index 0000000..b662705 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acidentifier_test.go @@ -0,0 +1,279 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "reflect" + "testing" +) + +var ( + goodIdentifiers = []string{ + "asdf", + "foo-bar-baz", + "zab_rab_oof", + "database", + "my~database", + "example.com/database", + "example.com/~bob/database", + "example.com/ourapp-1.0.0", + "sub-domain.example.com/org/product/release-1.0.0", + "sub-domain.example.com/org/product/~alice/release-1.0.0", + } + badIdentifiers = []string{ + "", + "BAR", + "foo#", + "EXAMPLE.com", + "foo.com/BAR", + "/app", + "app/", + "-app", + "app-", + ".app", + "app.", + "_app", + "app_", + "~app", + "app~", + } +) + +func TestNewACIdentifier(t *testing.T) { + for i, in := range goodIdentifiers { + l, err := NewACIdentifier(in) + if err != nil { + t.Errorf("#%d: got err=%v, want nil", i, err) + } + if l == nil { + t.Errorf("#%d: got l=nil, want non-nil", i) + } + } +} + +func TestNewACIdentifierBad(t *testing.T) { + for i, in := range badIdentifiers { + l, err := NewACIdentifier(in) + if l != nil { + t.Errorf("#%d: got l=%v, want nil", i, l) + } + if err == nil { + t.Errorf("#%d: got err=nil, want non-nil", i) + } + } +} + +func TestMustACIdentifier(t *testing.T) { + for i, in := range goodIdentifiers { + l := MustACIdentifier(in) + if l == nil { + t.Errorf("#%d: got l=nil, want non-nil", i) + } + } +} + +func expectPanicMustACIdentifier(i int, in string, t *testing.T) { + defer func() { + recover() + }() + _ = MustACIdentifier(in) + t.Errorf("#%d: panic expected", i) +} + +func TestMustACIdentifierBad(t *testing.T) { + for i, in := range badIdentifiers { + expectPanicMustACIdentifier(i, in, t) + } +} + +func TestSanitizeACIdentifier(t *testing.T) { + tests := map[string]string{ + "foo#": "foo", + "FOO": "foo", + "EXAMPLE.com": "example.com", + "foo.com/BAR": "foo.com/bar", + "/app": "app", + "app/": "app", + "-app": "app", + "app-": "app", + ".app": "app", + "app.": "app", + "_app": "app", + "app_": "app", + "~app": "app", + "app~": "app", + "app///": "app", + "-/.app..": "app", + "-/app.name-test/-/": "app.name-test", + "sub-domain.example.com/org/product/~alice/release-1.0.0": "sub-domain.example.com/org/product/~alice/release-1.0.0", + } + for in, ex := range tests { + o, err := SanitizeACIdentifier(in) + if err != nil { + t.Errorf("got err=%v, want nil", err) + } + if o != ex { + t.Errorf("got l=%s, want %s", o, ex) + } + } +} + +func TestACIdentifierSetGood(t *testing.T) { + tests := map[string]ACIdentifier{ + "blargh": ACIdentifier("blargh"), + "example-ourapp-1-0-0": ACIdentifier("example-ourapp-1-0-0"), + } + for in, w := range tests { + // Ensure an empty name is set appropriately + var a ACIdentifier + err := a.Set(in) + if err != nil { + t.Errorf("%v: got err=%v, want nil", in, err) + continue + } + if !reflect.DeepEqual(a, w) { + t.Errorf("%v: a=%v, want %v", in, a, w) + } + + // Ensure an existing name is overwritten + var b ACIdentifier = ACIdentifier("orig") + err = b.Set(in) + if err != nil { + t.Errorf("%v: got err=%v, want nil", in, err) + continue + } + if !reflect.DeepEqual(b, w) { + t.Errorf("%v: b=%v, want %v", in, b, w) + } + } +} + +func TestACIdentifierSetBad(t *testing.T) { + for i, in := range badIdentifiers { + // Ensure an empty name stays empty + var a ACIdentifier + err := a.Set(in) + if err == nil { + t.Errorf("#%d: err=%v, want nil", i, err) + continue + } + if w := ACIdentifier(""); !reflect.DeepEqual(a, w) { + t.Errorf("%d: a=%v, want %v", i, a, w) + } + + // Ensure an existing name is not overwritten + var b ACIdentifier = ACIdentifier("orig") + err = b.Set(in) + if err == nil { + t.Errorf("#%d: err=%v, want nil", i, err) + continue + } + if w := ACIdentifier("orig"); !reflect.DeepEqual(b, w) { + t.Errorf("%d: b=%v, want %v", i, b, w) + } + } +} + +func TestSanitizeACIdentifierBad(t *testing.T) { + tests := []string{ + "__", + "..", + "//", + "", + ".//-"} + for i, in := range tests { + l, err := SanitizeACIdentifier(in) + if l != "" { + t.Errorf("#%d: got l=%v, want nil", i, l) + } + if err == nil { + t.Errorf("#%d: got err=nil, want non-nil", i) + } + } +} + +func TestACIdentifierUnmarshalBad(t *testing.T) { + tests := []string{ + "", + "garbage", + `""`, + `"EXAMPLE"`, + `"example.com/app#1"`, + `"~example.com/app1"`, + } + for i, in := range tests { + var a, b ACIdentifier + err := a.UnmarshalJSON([]byte(in)) + if err == nil { + t.Errorf("#%d: err=nil, want non-nil", i) + } else if !reflect.DeepEqual(a, b) { + t.Errorf("#%d: a=%v, want empty", i, a) + } + } +} + +func TestACIdentifierUnmarshalGood(t *testing.T) { + tests := map[string]ACIdentifier{ + `"example"`: ACIdentifier("example"), + `"foo-bar"`: ACIdentifier("foo-bar"), + } + for in, w := range tests { + var a ACIdentifier + err := json.Unmarshal([]byte(in), &a) + if err != nil { + t.Errorf("%v: err=%v, want nil", in, err) + } else if !reflect.DeepEqual(a, w) { + t.Errorf("%v: a=%v, want %v", in, a, w) + } + } +} + +func TestACIdentifierMarshalBad(t *testing.T) { + tests := map[string]error{ + "Foo": ErrInvalidCharInACIdentifier, + "foo#": ErrInvalidCharInACIdentifier, + "-foo": ErrInvalidEdgeInACIdentifier, + "example-": ErrInvalidEdgeInACIdentifier, + "": ErrEmptyACIdentifier, + } + for in, werr := range tests { + a := ACIdentifier(in) + b, gerr := json.Marshal(a) + if b != nil { + t.Errorf("ACIdentifier(%q): want b=nil, got %v", in, b) + } + if jerr, ok := gerr.(*json.MarshalerError); !ok { + t.Errorf("expected JSONMarshalerError") + } else { + if e := jerr.Err; e != werr { + t.Errorf("err=%#v, want %#v", e, werr) + } + } + } +} + +func TestACIdentifierMarshalGood(t *testing.T) { + for i, in := range goodIdentifiers { + a := ACIdentifier(in) + b, err := json.Marshal(a) + if !reflect.DeepEqual(b, []byte(`"`+in+`"`)) { + t.Errorf("#%d: marshalled=%v, want %v", i, b, []byte(in)) + } + if err != nil { + t.Errorf("#%d: err=%v, want nil", i, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind.go new file mode 100644 index 0000000..1793ca8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind.go @@ -0,0 +1,67 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "fmt" +) + +var ( + ErrNoACKind = ACKindError("ACKind must be set") +) + +// ACKind wraps a string to define a field which must be set with one of +// several ACKind values. If it is unset, or has an invalid value, the field +// will refuse to marshal/unmarshal. +type ACKind string + +func (a ACKind) String() string { + return string(a) +} + +func (a ACKind) assertValid() error { + s := a.String() + switch s { + case "ImageManifest", "PodManifest": + return nil + case "": + return ErrNoACKind + default: + msg := fmt.Sprintf("bad ACKind: %s", s) + return ACKindError(msg) + } +} + +func (a ACKind) MarshalJSON() ([]byte, error) { + if err := a.assertValid(); err != nil { + return nil, err + } + return json.Marshal(a.String()) +} + +func (a *ACKind) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + na := ACKind(s) + if err := na.assertValid(); err != nil { + return err + } + *a = na + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind_test.go new file mode 100644 index 0000000..6522adb --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/ackind_test.go @@ -0,0 +1,93 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestACKindMarshalBad(t *testing.T) { + tests := map[string]error{ + "Foo": ACKindError("bad ACKind: Foo"), + "ApplicationManifest": ACKindError("bad ACKind: ApplicationManifest"), + "": ErrNoACKind, + } + for in, werr := range tests { + a := ACKind(in) + b, gerr := json.Marshal(a) + if b != nil { + t.Errorf("ACKind(%q): want b=nil, got %v", in, b) + } + if jerr, ok := gerr.(*json.MarshalerError); !ok { + t.Errorf("expected JSONMarshalerError") + } else { + if e := jerr.Err; e != werr { + t.Errorf("err=%#v, want %#v", e, werr) + } + } + } +} + +func TestACKindMarshalGood(t *testing.T) { + for i, in := range []string{ + "ImageManifest", + "PodManifest", + } { + a := ACKind(in) + b, err := json.Marshal(a) + if !reflect.DeepEqual(b, []byte(`"`+in+`"`)) { + t.Errorf("#%d: marshalled=%v, want %v", i, b, []byte(in)) + } + if err != nil { + t.Errorf("#%d: err=%v, want nil", i, err) + } + } +} + +func TestACKindUnmarshalBad(t *testing.T) { + tests := []string{ + "ImageManifest", // Not a valid JSON-encoded string + `"garbage"`, + `"AppManifest"`, + `""`, + } + for i, in := range tests { + var a, b ACKind + err := a.UnmarshalJSON([]byte(in)) + if err == nil { + t.Errorf("#%d: err=nil, want non-nil", i) + } else if !reflect.DeepEqual(a, b) { + t.Errorf("#%d: a=%v, want empty", i, a) + } + } +} + +func TestACKindUnmarshalGood(t *testing.T) { + tests := map[string]ACKind{ + `"PodManifest"`: ACKind("PodManifest"), + `"ImageManifest"`: ACKind("ImageManifest"), + } + for in, w := range tests { + var a ACKind + err := json.Unmarshal([]byte(in), &a) + if err != nil { + t.Errorf("%v: err=%v, want nil", in, err) + } else if !reflect.DeepEqual(a, w) { + t.Errorf("%v: a=%v, want %v", in, a, w) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/acname.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acname.go new file mode 100644 index 0000000..5ececff --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acname.go @@ -0,0 +1,145 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + "regexp" + "strings" +) + +var ( + // ValidACName is a regular expression that defines a valid ACName + ValidACName = regexp.MustCompile("^[a-z0-9]+([-][a-z0-9]+)*$") + + invalidACNameChars = regexp.MustCompile("[^a-z0-9-]") + invalidACNameEdges = regexp.MustCompile("(^[-]+)|([-]+$)") + + ErrEmptyACName = ACNameError("ACName cannot be empty") + ErrInvalidEdgeInACName = ACNameError("ACName must start and end with only lower case " + + "alphanumeric characters") + ErrInvalidCharInACName = ACNameError("ACName must contain only lower case " + + `alphanumeric characters plus "-"`) +) + +// ACName (an App-Container Name) is a format used by keys in different formats +// of the App Container Standard. An ACName is restricted to numeric and lowercase +// characters accepted by the DNS RFC[1] plus "-"; all alphabetical characters must +// be lowercase only. Furthermore, the first and last character ("edges") must be +// alphanumeric, and an ACName cannot be empty. Programmatically, an ACName must +// conform to the regular expression ValidACName. +// +// [1] http://tools.ietf.org/html/rfc1123#page-13 +type ACName string + +func (n ACName) String() string { + return string(n) +} + +// Set sets the ACName to the given value, if it is valid; if not, +// an error is returned. +func (n *ACName) Set(s string) error { + nn, err := NewACName(s) + if err == nil { + *n = *nn + } + return err +} + +// Equals checks whether a given ACName is equal to this one. +func (n ACName) Equals(o ACName) bool { + return strings.ToLower(string(n)) == strings.ToLower(string(o)) +} + +// Empty returns a boolean indicating whether this ACName is empty. +func (n ACName) Empty() bool { + return n.String() == "" +} + +// NewACName generates a new ACName from a string. If the given string is +// not a valid ACName, nil and an error are returned. +func NewACName(s string) (*ACName, error) { + n := ACName(s) + if err := n.assertValid(); err != nil { + return nil, err + } + return &n, nil +} + +// MustACName generates a new ACName from a string, If the given string is +// not a valid ACName, it panics. +func MustACName(s string) *ACName { + n, err := NewACName(s) + if err != nil { + panic(err) + } + return n +} + +func (n ACName) assertValid() error { + s := string(n) + if len(s) == 0 { + return ErrEmptyACName + } + if invalidACNameChars.MatchString(s) { + return ErrInvalidCharInACName + } + if invalidACNameEdges.MatchString(s) { + return ErrInvalidEdgeInACName + } + return nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (n *ACName) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + nn, err := NewACName(s) + if err != nil { + return err + } + *n = *nn + return nil +} + +// MarshalJSON implements the json.Marshaler interface +func (n ACName) MarshalJSON() ([]byte, error) { + if err := n.assertValid(); err != nil { + return nil, err + } + return json.Marshal(n.String()) +} + +// SanitizeACName replaces every invalid ACName character in s with a dash +// making it a legal ACName string. If the character is an upper case letter it +// replaces it with its lower case. It also removes illegal edge characters +// (hyphens). +// +// This is a helper function and its algorithm is not part of the spec. It +// should not be called without the user explicitly asking for a suggestion. +func SanitizeACName(s string) (string, error) { + s = strings.ToLower(s) + s = invalidACNameChars.ReplaceAllString(s, "-") + s = invalidACNameEdges.ReplaceAllString(s, "") + + if s == "" { + return "", errors.New("must contain at least one valid character") + } + + return s, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/acname_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acname_test.go new file mode 100644 index 0000000..11b8d2c --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/acname_test.go @@ -0,0 +1,266 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "reflect" + "testing" +) + +var ( + goodNames = []string{ + "asdf", + "foo-bar-baz", + "database", + } + badNames = []string{ + "", + "foo#", + "example.com", + "EXAMPLE.com", + "example/database", + "example/database-1.0.0", + "foo.com/BAR", + "example.com/app_1", + "/app", + "app/", + "-app", + "app-", + ".app", + "app.", + } +) + +func TestNewACName(t *testing.T) { + for i, in := range goodNames { + l, err := NewACName(in) + if err != nil { + t.Errorf("#%d: got err=%v, want nil", i, err) + } + if l == nil { + t.Errorf("#%d: got l=nil, want non-nil", i) + } + } +} + +func TestNewACNameBad(t *testing.T) { + for i, in := range badNames { + l, err := NewACName(in) + if l != nil { + t.Errorf("#%d: got l=%v, want nil", i, l) + } + if err == nil { + t.Errorf("#%d: got err=nil, want non-nil", i) + } + } +} + +func TestMustACName(t *testing.T) { + for i, in := range goodNames { + l := MustACName(in) + if l == nil { + t.Errorf("#%d: got l=nil, want non-nil", i) + } + } +} + +func expectPanicMustACName(i int, in string, t *testing.T) { + defer func() { + recover() + }() + _ = MustACName(in) + t.Errorf("#%d: panic expected", i) +} + +func TestMustACNameBad(t *testing.T) { + for i, in := range badNames { + expectPanicMustACName(i, in, t) + } +} + +func TestSanitizeACName(t *testing.T) { + tests := map[string]string{ + "foo#": "foo", + "EXAMPLE.com": "example-com", + "foo.com/BAR": "foo-com-bar", + "example.com/app_1": "example-com-app-1", + "/app": "app", + "app/": "app", + "-app": "app", + "app-": "app", + ".app": "app", + "app.": "app", + "app///": "app", + "-/.app..": "app", + "-/app.name-test/-/": "app-name-test", + "sub-domain.example.com/org/product/release-1.0.0": "sub-domain-example-com-org-product-release-1-0-0", + } + for in, ex := range tests { + o, err := SanitizeACName(in) + if err != nil { + t.Errorf("got err=%v, want nil", err) + } + if o != ex { + t.Errorf("got l=%s, want %s", o, ex) + } + } +} + +func TestACNameSetGood(t *testing.T) { + tests := map[string]ACName{ + "blargh": ACName("blargh"), + "example-ourapp-1-0-0": ACName("example-ourapp-1-0-0"), + } + for in, w := range tests { + // Ensure an empty name is set appropriately + var a ACName + err := a.Set(in) + if err != nil { + t.Errorf("%v: got err=%v, want nil", in, err) + continue + } + if !reflect.DeepEqual(a, w) { + t.Errorf("%v: a=%v, want %v", in, a, w) + } + + // Ensure an existing name is overwritten + var b ACName = ACName("orig") + err = b.Set(in) + if err != nil { + t.Errorf("%v: got err=%v, want nil", in, err) + continue + } + if !reflect.DeepEqual(b, w) { + t.Errorf("%v: b=%v, want %v", in, b, w) + } + } +} + +func TestACNameSetBad(t *testing.T) { + for i, in := range badNames { + // Ensure an empty name stays empty + var a ACName + err := a.Set(in) + if err == nil { + t.Errorf("#%d: err=%v, want nil", i, err) + continue + } + if w := ACName(""); !reflect.DeepEqual(a, w) { + t.Errorf("%d: a=%v, want %v", i, a, w) + } + + // Ensure an existing name is not overwritten + var b ACName = ACName("orig") + err = b.Set(in) + if err == nil { + t.Errorf("#%d: err=%v, want nil", i, err) + continue + } + if w := ACName("orig"); !reflect.DeepEqual(b, w) { + t.Errorf("%d: b=%v, want %v", i, b, w) + } + } +} + +func TestSanitizeACNameBad(t *testing.T) { + tests := []string{ + "__", + "..", + "//", + "", + ".//-"} + for i, in := range tests { + l, err := SanitizeACName(in) + if l != "" { + t.Errorf("#%d: got l=%v, want nil", i, l) + } + if err == nil { + t.Errorf("#%d: got err=nil, want non-nil", i) + } + } +} + +func TestACNameUnmarshalBad(t *testing.T) { + tests := []string{ + "", + "garbage", + `""`, + `"EXAMPLE"`, + `"example.com/app_1"`, + } + for i, in := range tests { + var a, b ACName + err := a.UnmarshalJSON([]byte(in)) + if err == nil { + t.Errorf("#%d: err=nil, want non-nil", i) + } else if !reflect.DeepEqual(a, b) { + t.Errorf("#%d: a=%v, want empty", i, a) + } + } +} + +func TestACNameUnmarshalGood(t *testing.T) { + tests := map[string]ACName{ + `"example"`: ACName("example"), + `"foo-bar"`: ACName("foo-bar"), + } + for in, w := range tests { + var a ACName + err := json.Unmarshal([]byte(in), &a) + if err != nil { + t.Errorf("%v: err=%v, want nil", in, err) + } else if !reflect.DeepEqual(a, w) { + t.Errorf("%v: a=%v, want %v", in, a, w) + } + } +} + +func TestACNameMarshalBad(t *testing.T) { + tests := map[string]error{ + "Foo": ErrInvalidCharInACName, + "foo#": ErrInvalidCharInACName, + "-foo": ErrInvalidEdgeInACName, + "example-": ErrInvalidEdgeInACName, + "": ErrEmptyACName, + } + for in, werr := range tests { + a := ACName(in) + b, gerr := json.Marshal(a) + if b != nil { + t.Errorf("ACName(%q): want b=nil, got %v", in, b) + } + if jerr, ok := gerr.(*json.MarshalerError); !ok { + t.Errorf("expected JSONMarshalerError") + } else { + if e := jerr.Err; e != werr { + t.Errorf("err=%#v, want %#v", e, werr) + } + } + } +} + +func TestACNameMarshalGood(t *testing.T) { + for i, in := range goodNames { + a := ACName(in) + b, err := json.Marshal(a) + if !reflect.DeepEqual(b, []byte(`"`+in+`"`)) { + t.Errorf("#%d: marshalled=%v, want %v", i, b, []byte(in)) + } + if err != nil { + t.Errorf("#%d: err=%v, want nil", i, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations.go new file mode 100644 index 0000000..ce7743b --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations.go @@ -0,0 +1,106 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "fmt" +) + +type Annotations []Annotation + +type annotations Annotations + +type Annotation struct { + Name ACIdentifier `json:"name"` + Value string `json:"value"` +} + +func (a Annotations) assertValid() error { + seen := map[ACIdentifier]string{} + for _, anno := range a { + _, ok := seen[anno.Name] + if ok { + return fmt.Errorf(`duplicate annotations of name %q`, anno.Name) + } + seen[anno.Name] = anno.Value + } + if c, ok := seen["created"]; ok { + if _, err := NewDate(c); err != nil { + return err + } + } + if h, ok := seen["homepage"]; ok { + if _, err := NewURL(h); err != nil { + return err + } + } + if d, ok := seen["documentation"]; ok { + if _, err := NewURL(d); err != nil { + return err + } + } + + return nil +} + +func (a Annotations) MarshalJSON() ([]byte, error) { + if err := a.assertValid(); err != nil { + return nil, err + } + return json.Marshal(annotations(a)) +} + +func (a *Annotations) UnmarshalJSON(data []byte) error { + var ja annotations + if err := json.Unmarshal(data, &ja); err != nil { + return err + } + na := Annotations(ja) + if err := na.assertValid(); err != nil { + return err + } + *a = na + return nil +} + +// Retrieve the value of an annotation by the given name from Annotations, if +// it exists. +func (a Annotations) Get(name string) (val string, ok bool) { + for _, anno := range a { + if anno.Name.String() == name { + return anno.Value, true + } + } + return "", false +} + +// Set sets the value of an annotation by the given name, overwriting if one already exists. +func (a *Annotations) Set(name ACIdentifier, value string) { + for i, anno := range *a { + if anno.Name.Equals(name) { + (*a)[i] = Annotation{ + Name: name, + Value: value, + } + return + } + } + anno := Annotation{ + Name: name, + Value: value, + } + *a = append(*a, anno) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations_test.go new file mode 100644 index 0000000..358fd18 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/annotations_test.go @@ -0,0 +1,233 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "reflect" + "testing" +) + +func makeAnno(n, v string) Annotation { + name, err := NewACIdentifier(n) + if err != nil { + panic(err) + } + return Annotation{ + Name: *name, + Value: v, + } +} + +func TestAnnotationsAssertValid(t *testing.T) { + tests := []struct { + in []Annotation + werr bool + }{ + // duplicate names should fail + { + []Annotation{ + makeAnno("foo", "bar"), + makeAnno("foo", "baz"), + }, + true, + }, + // bad created should fail + { + []Annotation{ + makeAnno("created", "garbage"), + }, + true, + }, + // bad homepage should fail + { + []Annotation{ + makeAnno("homepage", "not-A$@#URL"), + }, + true, + }, + // bad documentation should fail + { + []Annotation{ + makeAnno("documentation", "ftp://isnotallowed.com"), + }, + true, + }, + // good cases + { + []Annotation{ + makeAnno("created", "2004-05-14T23:11:14+00:00"), + makeAnno("documentation", "http://example.com/docs"), + }, + false, + }, + { + []Annotation{ + makeAnno("foo", "bar"), + makeAnno("homepage", "https://homepage.com"), + }, + false, + }, + // empty is OK + { + []Annotation{}, + false, + }, + } + for i, tt := range tests { + a := Annotations(tt.in) + err := a.assertValid() + if gerr := (err != nil); gerr != tt.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err) + } + } +} + +func TestAnnotationsMarshal(t *testing.T) { + for i, tt := range []struct { + in []Annotation + wb []byte + werr bool + }{ + { + []Annotation{ + makeAnno("foo", "bar"), + makeAnno("foo", "baz"), + makeAnno("website", "http://example.com/anno"), + }, + nil, + true, + }, + { + []Annotation{ + makeAnno("a", "b"), + }, + []byte(`[{"name":"a","value":"b"}]`), + false, + }, + { + []Annotation{ + makeAnno("foo", "bar"), + makeAnno("website", "http://example.com/anno"), + }, + []byte(`[{"name":"foo","value":"bar"},{"name":"website","value":"http://example.com/anno"}]`), + false, + }, + } { + a := Annotations(tt.in) + b, err := a.MarshalJSON() + if !reflect.DeepEqual(b, tt.wb) { + t.Errorf("#%d: b=%s, want %s", i, b, tt.wb) + } + gerr := err != nil + if gerr != tt.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err) + } + } +} + +func TestAnnotationsUnmarshal(t *testing.T) { + tests := []struct { + in string + wann *Annotations + werr bool + }{ + { + `garbage`, + &Annotations{}, + true, + }, + { + `[{"name":"a","value":"b"},{"name":"a","value":"b"}]`, + &Annotations{}, + true, + }, + { + `[{"name":"a","value":"b"}]`, + &Annotations{ + makeAnno("a", "b"), + }, + false, + }, + } + for i, tt := range tests { + a := &Annotations{} + err := a.UnmarshalJSON([]byte(tt.in)) + gerr := err != nil + if gerr != tt.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err) + } + if !reflect.DeepEqual(a, tt.wann) { + t.Errorf("#%d: ann=%#v, want %#v", i, a, tt.wann) + } + } + +} + +func TestAnnotationsGet(t *testing.T) { + for i, tt := range []struct { + in string + wval string + wok bool + }{ + {"foo", "bar", true}, + {"website", "http://example.com/anno", true}, + {"baz", "", false}, + {"wuuf", "", false}, + } { + a := Annotations{ + makeAnno("foo", "bar"), + makeAnno("website", "http://example.com/anno"), + } + gval, gok := a.Get(tt.in) + if gval != tt.wval { + t.Errorf("#%d: val=%v, want %v", i, gval, tt.wval) + } + if gok != tt.wok { + t.Errorf("#%d: ok=%t, want %t", i, gok, tt.wok) + } + } +} + +func TestAnnotationsSet(t *testing.T) { + a := Annotations{} + + a.Set("foo", "bar") + w := Annotations{ + Annotation{ACIdentifier("foo"), "bar"}, + } + if !reflect.DeepEqual(w, a) { + t.Fatalf("want %v, got %v", w, a) + } + + a.Set("dog", "woof") + w = Annotations{ + Annotation{ACIdentifier("foo"), "bar"}, + Annotation{ACIdentifier("dog"), "woof"}, + } + if !reflect.DeepEqual(w, a) { + t.Fatalf("want %v, got %v", w, a) + } + + a.Set("foo", "baz") + a.Set("example.com/foo_bar", "quux") + w = Annotations{ + Annotation{ACIdentifier("foo"), "baz"}, + Annotation{ACIdentifier("dog"), "woof"}, + Annotation{ACIdentifier("example.com/foo_bar"), "quux"}, + } + if !reflect.DeepEqual(w, a) { + t.Fatalf("want %v, got %v", w, a) + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/app.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/app.go new file mode 100644 index 0000000..363b6b5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/app.go @@ -0,0 +1,90 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + "fmt" + "path" +) + +type App struct { + Exec Exec `json:"exec"` + EventHandlers []EventHandler `json:"eventHandlers,omitempty"` + User string `json:"user"` + Group string `json:"group"` + SupplementaryGIDs []int `json:"supplementaryGIDs,omitempty"` + WorkingDirectory string `json:"workingDirectory,omitempty"` + Environment Environment `json:"environment,omitempty"` + MountPoints []MountPoint `json:"mountPoints,omitempty"` + Ports []Port `json:"ports,omitempty"` + Isolators Isolators `json:"isolators,omitempty"` +} + +// app is a model to facilitate extra validation during the +// unmarshalling of the App +type app App + +func (a *App) UnmarshalJSON(data []byte) error { + ja := app(*a) + err := json.Unmarshal(data, &ja) + if err != nil { + return err + } + na := App(ja) + if err := na.assertValid(); err != nil { + return err + } + if na.Environment == nil { + na.Environment = make(Environment, 0) + } + *a = na + return nil +} + +func (a App) MarshalJSON() ([]byte, error) { + if err := a.assertValid(); err != nil { + return nil, err + } + return json.Marshal(app(a)) +} + +func (a *App) assertValid() error { + if err := a.Exec.assertValid(); err != nil { + return err + } + if a.User == "" { + return errors.New(`User is required`) + } + if a.Group == "" { + return errors.New(`Group is required`) + } + if !path.IsAbs(a.WorkingDirectory) && a.WorkingDirectory != "" { + return errors.New("WorkingDirectory must be an absolute path") + } + eh := make(map[string]bool) + for _, e := range a.EventHandlers { + name := e.Name + if eh[name] { + return fmt.Errorf("Only one eventHandler of name %q allowed", name) + } + eh[name] = true + } + if err := a.Environment.assertValid(); err != nil { + return err + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/app_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/app_test.go new file mode 100644 index 0000000..9a68532 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/app_test.go @@ -0,0 +1,230 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "reflect" + "testing" +) + +func TestAppValid(t *testing.T) { + tests := []App{ + App{ + Exec: []string{"/bin/httpd"}, + User: "0", + Group: "0", + WorkingDirectory: "/tmp", + }, + App{ + Exec: []string{"/app"}, + User: "0", + Group: "0", + EventHandlers: []EventHandler{ + {Name: "pre-start"}, + {Name: "post-stop"}, + }, + Environment: []EnvironmentVariable{ + {Name: "DEBUG", Value: "true"}, + }, + WorkingDirectory: "/tmp", + }, + App{ + Exec: []string{"/app", "arg1", "arg2"}, + User: "0", + Group: "0", + WorkingDirectory: "/tmp", + }, + } + for i, tt := range tests { + if err := tt.assertValid(); err != nil { + t.Errorf("#%d: err == %v, want nil", i, err) + } + } +} + +func TestAppExecInvalid(t *testing.T) { + tests := []App{ + App{ + Exec: nil, + }, + App{ + Exec: []string{}, + User: "0", + Group: "0", + }, + App{ + Exec: []string{"app"}, + User: "0", + Group: "0", + }, + App{ + Exec: []string{"bin/app", "arg1"}, + User: "0", + Group: "0", + }, + } + for i, tt := range tests { + if err := tt.assertValid(); err == nil { + t.Errorf("#%d: err == nil, want non-nil", i) + } + } +} + +func TestAppEventHandlersInvalid(t *testing.T) { + tests := []App{ + App{ + Exec: []string{"/bin/httpd"}, + User: "0", + Group: "0", + EventHandlers: []EventHandler{ + EventHandler{ + Name: "pre-start", + }, + EventHandler{ + Name: "pre-start", + }, + }, + }, + App{ + Exec: []string{"/bin/httpd"}, + User: "0", + Group: "0", + EventHandlers: []EventHandler{ + EventHandler{ + Name: "post-stop", + }, + EventHandler{ + Name: "pre-start", + }, + EventHandler{ + Name: "post-stop", + }, + }, + }, + } + for i, tt := range tests { + if err := tt.assertValid(); err == nil { + t.Errorf("#%d: err == nil, want non-nil", i) + } + } +} + +func TestUserGroupInvalid(t *testing.T) { + tests := []App{ + App{ + Exec: []string{"/app"}, + }, + App{ + Exec: []string{"/app"}, + User: "0", + }, + App{ + Exec: []string{"app"}, + Group: "0", + }, + } + for i, tt := range tests { + if err := tt.assertValid(); err == nil { + t.Errorf("#%d: err == nil, want non-nil", i) + } + } +} + +func TestAppWorkingDirectoryInvalid(t *testing.T) { + tests := []App{ + App{ + Exec: []string{"/app"}, + User: "foo", + Group: "bar", + WorkingDirectory: "stuff", + }, + App{ + Exec: []string{"/app"}, + User: "foo", + Group: "bar", + WorkingDirectory: "../home/fred", + }, + } + for i, tt := range tests { + if err := tt.assertValid(); err == nil { + t.Errorf("#%d: err == nil, want non-nil", i) + } + } +} + +func TestAppEnvironmentInvalid(t *testing.T) { + tests := []App{ + App{ + Exec: []string{"/app"}, + User: "foo", + Group: "bar", + Environment: Environment{ + EnvironmentVariable{"0DEBUG", "true"}, + }, + }, + } + for i, tt := range tests { + if err := tt.assertValid(); err == nil { + t.Errorf("#%d: err == nil, want non-nil", i) + } + } +} + +func TestAppUnmarshal(t *testing.T) { + tests := []struct { + in string + wann *App + werr bool + }{ + { + `garbage`, + &App{}, + true, + }, + { + `{"Exec":"not a list"}`, + &App{}, + true, + }, + { + `{"Exec":["notfullyqualified"]}`, + &App{}, + true, + }, + { + `{"Exec":["/a"],"User":"0","Group":"0"}`, + &App{ + Exec: Exec{ + "/a", + }, + User: "0", + Group: "0", + Environment: make(Environment, 0), + }, + false, + }, + } + for i, tt := range tests { + a := &App{} + err := a.UnmarshalJSON([]byte(tt.in)) + gerr := err != nil + if gerr != tt.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err) + } + if !reflect.DeepEqual(a, tt.wann) { + t.Errorf("#%d: ann=%#v, want %#v", i, a, tt.wann) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/date.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/date.go new file mode 100644 index 0000000..4458bf4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/date.go @@ -0,0 +1,60 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "fmt" + "time" +) + +// Date wraps time.Time to marshal/unmarshal to/from JSON strings in strict +// accordance with RFC3339 +// TODO(jonboulle): golang's implementation seems slightly buggy here; +// according to http://tools.ietf.org/html/rfc3339#section-5.6 , applications +// may choose to separate the date and time with a space instead of a T +// character (for example, `date --rfc-3339` on GNU coreutils) - but this is +// considered an error by go's parser. File a bug? +type Date time.Time + +func NewDate(s string) (*Date, error) { + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return nil, fmt.Errorf("bad Date: %v", err) + } + d := Date(t) + return &d, nil +} + +func (d Date) String() string { + return time.Time(d).Format(time.RFC3339) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + nd, err := NewDate(s) + if err != nil { + return err + } + *d = *nd + return nil +} + +func (d Date) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/date_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/date_test.go new file mode 100644 index 0000000..9eb1183 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/date_test.go @@ -0,0 +1,80 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "testing" + "time" +) + +var ( + pst = time.FixedZone("Pacific", -8*60*60) +) + +func TestUnmarshalDate(t *testing.T) { + tests := []struct { + in string + + wt time.Time + }{ + { + `"2004-05-14T23:11:14+00:00"`, + + time.Date(2004, 05, 14, 23, 11, 14, 0, time.UTC), + }, + { + `"2001-02-03T04:05:06Z"`, + + time.Date(2001, 02, 03, 04, 05, 06, 0, time.UTC), + }, + { + `"2014-11-14T17:36:54-08:00"`, + + time.Date(2014, 11, 14, 17, 36, 54, 0, pst), + }, + { + `"2004-05-14T23:11:14+00:00"`, + + time.Date(2004, 05, 14, 23, 11, 14, 0, time.UTC), + }, + } + for i, tt := range tests { + var d Date + if err := json.Unmarshal([]byte(tt.in), &d); err != nil { + t.Errorf("#%d: got err=%v, want nil", i, err) + } + if gt := time.Time(d); !gt.Equal(tt.wt) { + t.Errorf("#%d: got time=%v, want %v", i, gt, tt.wt) + } + } +} + +func TestUnmarshalDateBad(t *testing.T) { + tests := []string{ + `not a json string`, + `2014-11-14T17:36:54-08:00`, + `"garbage"`, + `"1416015188"`, + `"Fri Nov 14 17:53:02 PST 2014"`, + `"2014-11-1417:36:54"`, + } + for i, tt := range tests { + var d Date + if err := json.Unmarshal([]byte(tt), &d); err == nil { + t.Errorf("#%d: unexpected nil err", i) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies.go new file mode 100644 index 0000000..fb399e4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies.go @@ -0,0 +1,58 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" +) + +type Dependencies []Dependency + +type Dependency struct { + ImageName ACIdentifier `json:"imageName"` + ImageID *Hash `json:"imageID,omitempty"` + Labels Labels `json:"labels,omitempty"` + Size uint `json:"size,omitempty"` +} + +type dependency Dependency + +func (d Dependency) assertValid() error { + if len(d.ImageName) < 1 { + return errors.New(`imageName cannot be empty`) + } + return nil +} + +func (d Dependency) MarshalJSON() ([]byte, error) { + if err := d.assertValid(); err != nil { + return nil, err + } + return json.Marshal(dependency(d)) +} + +func (d *Dependency) UnmarshalJSON(data []byte) error { + var jd dependency + if err := json.Unmarshal(data, &jd); err != nil { + return err + } + nd := Dependency(jd) + if err := nd.assertValid(); err != nil { + return err + } + *d = nd + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies_test.go new file mode 100644 index 0000000..ac3f1f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/dependencies_test.go @@ -0,0 +1,40 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import "testing" + +func TestEmptyHash(t *testing.T) { + dj := `{"imageName": "example.com/reduce-worker-base"}` + + var d Dependency + + err := d.UnmarshalJSON([]byte(dj)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Marshal to verify that marshalling works without validation errors + buf, err := d.MarshalJSON() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Unmarshal to verify that the generated json will not create wrong empty hash + err = d.UnmarshalJSON(buf) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/doc.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/doc.go new file mode 100644 index 0000000..9c54085 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/doc.go @@ -0,0 +1,18 @@ +// Copyright 2015 The appc Authors +// +// 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 types contains structs representing the various types in the app +// container specification. It is used by the [schema manifest types](../) +// to enforce validation. +package types diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/environment.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/environment.go new file mode 100644 index 0000000..f152a6b --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/environment.go @@ -0,0 +1,110 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "fmt" + "regexp" +) + +var ( + envPattern = regexp.MustCompile("^[A-Za-z_][A-Za-z_0-9]*$") +) + +type Environment []EnvironmentVariable + +type environment Environment + +type EnvironmentVariable struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func (ev EnvironmentVariable) assertValid() error { + if len(ev.Name) == 0 { + return fmt.Errorf(`environment variable name must not be empty`) + } + if !envPattern.MatchString(ev.Name) { + return fmt.Errorf(`environment variable does not have valid identifier %q`, ev.Name) + } + return nil +} + +func (e Environment) assertValid() error { + seen := map[string]bool{} + for _, env := range e { + if err := env.assertValid(); err != nil { + return err + } + _, ok := seen[env.Name] + if ok { + return fmt.Errorf(`duplicate environment variable of name %q`, env.Name) + } + seen[env.Name] = true + } + + return nil +} + +func (e Environment) MarshalJSON() ([]byte, error) { + if err := e.assertValid(); err != nil { + return nil, err + } + return json.Marshal(environment(e)) +} + +func (e *Environment) UnmarshalJSON(data []byte) error { + var je environment + if err := json.Unmarshal(data, &je); err != nil { + return err + } + ne := Environment(je) + if err := ne.assertValid(); err != nil { + return err + } + *e = ne + return nil +} + +// Retrieve the value of an environment variable by the given name from +// Environment, if it exists. +func (e Environment) Get(name string) (value string, ok bool) { + for _, env := range e { + if env.Name == name { + return env.Value, true + } + } + return "", false +} + +// Set sets the value of an environment variable by the given name, +// overwriting if one already exists. +func (e *Environment) Set(name string, value string) { + for i, env := range *e { + if env.Name == name { + (*e)[i] = EnvironmentVariable{ + Name: name, + Value: value, + } + return + } + } + env := EnvironmentVariable{ + Name: name, + Value: value, + } + *e = append(*e, env) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/environment_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/environment_test.go new file mode 100644 index 0000000..8c51bea --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/environment_test.go @@ -0,0 +1,76 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "testing" +) + +func TestEnvironmentAssertValid(t *testing.T) { + tests := []struct { + env Environment + werr bool + }{ + // duplicate names should fail + { + Environment{ + EnvironmentVariable{"DEBUG", "true"}, + EnvironmentVariable{"DEBUG", "true"}, + }, + true, + }, + // empty name should fail + { + Environment{ + EnvironmentVariable{"", "value"}, + }, + true, + }, + // name beginning with digit should fail + { + Environment{ + EnvironmentVariable{"0DEBUG", "true"}, + }, + true, + }, + // name with non [A-Za-z0-9_] should fail + { + Environment{ + EnvironmentVariable{"VERBOSE-DEBUG", "true"}, + }, + true, + }, + // accepted environment variable forms + { + Environment{ + EnvironmentVariable{"DEBUG", "true"}, + }, + false, + }, + { + Environment{ + EnvironmentVariable{"_0_DEBUG_0_", "true"}, + }, + false, + }, + } + for i, test := range tests { + env := Environment(test.env) + err := env.assertValid() + if gerr := (err != nil); gerr != test.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, test.werr, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/errors.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/errors.go new file mode 100644 index 0000000..bb46515 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/errors.go @@ -0,0 +1,49 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import "fmt" + +// An ACKindError is returned when the wrong ACKind is set in a manifest +type ACKindError string + +func (e ACKindError) Error() string { + return string(e) +} + +func InvalidACKindError(kind ACKind) ACKindError { + return ACKindError(fmt.Sprintf("missing or bad ACKind (must be %#v)", kind)) +} + +// An ACVersionError is returned when a bad ACVersion is set in a manifest +type ACVersionError string + +func (e ACVersionError) Error() string { + return string(e) +} + +// An ACIdentifierError is returned when a bad value is used for an ACIdentifier +type ACIdentifierError string + +func (e ACIdentifierError) Error() string { + return string(e) +} + +// An ACNameError is returned when a bad value is used for an ACName +type ACNameError string + +func (e ACNameError) Error() string { + return string(e) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/event_handler.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/event_handler.go new file mode 100644 index 0000000..f40c642 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/event_handler.go @@ -0,0 +1,61 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + "fmt" +) + +type EventHandler struct { + Name string `json:"name"` + Exec Exec `json:"exec"` +} + +type eventHandler EventHandler + +func (e EventHandler) assertValid() error { + s := e.Name + switch s { + case "pre-start", "post-stop": + return nil + case "": + return errors.New(`eventHandler "name" cannot be empty`) + default: + return fmt.Errorf(`bad eventHandler "name": %q`, s) + } +} + +func (e EventHandler) MarshalJSON() ([]byte, error) { + if err := e.assertValid(); err != nil { + return nil, err + } + return json.Marshal(eventHandler(e)) +} + +func (e *EventHandler) UnmarshalJSON(data []byte) error { + var je eventHandler + err := json.Unmarshal(data, &je) + if err != nil { + return err + } + ne := EventHandler(je) + if err := ne.assertValid(); err != nil { + return err + } + *e = ne + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/exec.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/exec.go new file mode 100644 index 0000000..0ba7b98 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/exec.go @@ -0,0 +1,56 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + "path/filepath" +) + +type Exec []string + +type exec Exec + +func (e Exec) assertValid() error { + if len(e) < 1 { + return errors.New(`Exec cannot be empty`) + } + if !filepath.IsAbs(e[0]) { + return errors.New(`Exec[0] must be absolute path`) + } + return nil +} + +func (e Exec) MarshalJSON() ([]byte, error) { + if err := e.assertValid(); err != nil { + return nil, err + } + return json.Marshal(exec(e)) +} + +func (e *Exec) UnmarshalJSON(data []byte) error { + var je exec + err := json.Unmarshal(data, &je) + if err != nil { + return err + } + ne := Exec(je) + if err := ne.assertValid(); err != nil { + return err + } + *e = ne + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/exec_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/exec_test.go new file mode 100644 index 0000000..326dc5d --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/exec_test.go @@ -0,0 +1,42 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import "testing" + +func TestExecValid(t *testing.T) { + tests := []Exec{ + Exec{"/bin/httpd"}, + Exec{"/app"}, + Exec{"/app", "arg1", "arg2"}, + } + for i, tt := range tests { + if err := tt.assertValid(); err != nil { + t.Errorf("#%d: err == %v, want nil", i, err) + } + } +} + +func TestExecInvalid(t *testing.T) { + tests := []Exec{ + Exec{}, + Exec{"app"}, + } + for i, tt := range tests { + if err := tt.assertValid(); err == nil { + t.Errorf("#%d: err == nil, want non-nil", i) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/hash.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/hash.go new file mode 100644 index 0000000..1c060a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/hash.go @@ -0,0 +1,118 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "crypto/sha512" + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" +) + +const ( + maxHashSize = (sha512.Size / 2) + len("sha512-") +) + +// Hash encodes a hash specified in a string of the form: +// "-" +// for example +// "sha512-06c733b1838136838e6d2d3e8fa5aea4c7905e92[...]" +// Valid types are currently: +// * sha512 +type Hash struct { + typ string + Val string +} + +func NewHash(s string) (*Hash, error) { + elems := strings.Split(s, "-") + if len(elems) != 2 { + return nil, errors.New("badly formatted hash string") + } + nh := Hash{ + typ: elems[0], + Val: elems[1], + } + if err := nh.assertValid(); err != nil { + return nil, err + } + return &nh, nil +} + +func (h Hash) String() string { + return fmt.Sprintf("%s-%s", h.typ, h.Val) +} + +func (h *Hash) Set(s string) error { + nh, err := NewHash(s) + if err == nil { + *h = *nh + } + return err +} + +func (h Hash) Empty() bool { + return reflect.DeepEqual(h, Hash{}) +} + +func (h Hash) assertValid() error { + switch h.typ { + case "sha512": + case "": + return fmt.Errorf("unexpected empty hash type") + default: + return fmt.Errorf("unrecognized hash type: %v", h.typ) + } + if h.Val == "" { + return fmt.Errorf("unexpected empty hash value") + } + return nil +} + +func (h *Hash) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + nh, err := NewHash(s) + if err != nil { + return err + } + *h = *nh + return nil +} + +func (h Hash) MarshalJSON() ([]byte, error) { + if err := h.assertValid(); err != nil { + return nil, err + } + return json.Marshal(h.String()) +} + +func NewHashSHA512(b []byte) *Hash { + h := sha512.New() + h.Write(b) + nh, _ := NewHash(fmt.Sprintf("sha512-%x", h.Sum(nil))) + return nh +} + +func ShortHash(hash string) string { + if len(hash) > maxHashSize { + return hash[:maxHashSize] + } + return hash +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/hash_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/hash_test.go new file mode 100644 index 0000000..dc597ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/hash_test.go @@ -0,0 +1,96 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "testing" +) + +func TestMarshalHash(t *testing.T) { + tests := []struct { + typ string + val string + + wout string + }{ + { + "sha512", + "abcdefghi", + + `"sha512-abcdefghi"`, + }, + { + "sha512", + "06c733b1838136838e6d2d3e8fa5aea4c7905e92", + + `"sha512-06c733b1838136838e6d2d3e8fa5aea4c7905e92"`, + }, + } + for i, tt := range tests { + h := Hash{ + typ: tt.typ, + Val: tt.val, + } + b, err := json.Marshal(h) + if err != nil { + t.Errorf("#%d: unexpected err=%v", i, err) + } + if g := string(b); g != tt.wout { + t.Errorf("#%d: got string=%v, want %v", i, g, tt.wout) + } + } +} + +func TestMarshalHashBad(t *testing.T) { + tests := []struct { + typ string + val string + }{ + { + // empty value + "sha512", + "", + }, + { + // bad type + "sha1", + "abcdef", + }, + { + // empty type + "", + "abcdef", + }, + { + // empty empty + "", + "", + }, + } + for i, tt := range tests { + h := Hash{ + typ: tt.typ, + Val: tt.val, + } + g, err := json.Marshal(h) + if err == nil { + t.Errorf("#%d: unexpected nil err", i) + } + if g != nil { + t.Errorf("#%d: unexpected non-nil bytes: %v", i, g) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator.go new file mode 100644 index 0000000..49c7da9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator.go @@ -0,0 +1,107 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" +) + +var ( + isolatorMap map[ACIdentifier]IsolatorValueConstructor +) + +func init() { + isolatorMap = make(map[ACIdentifier]IsolatorValueConstructor) +} + +type IsolatorValueConstructor func() IsolatorValue + +func AddIsolatorValueConstructor(n ACIdentifier, i IsolatorValueConstructor) { + isolatorMap[n] = i +} + +func AddIsolatorName(n ACIdentifier, ns map[ACIdentifier]struct{}) { + ns[n] = struct{}{} +} + +type Isolators []Isolator + +// GetByName returns the last isolator in the list by the given name. +func (is *Isolators) GetByName(name ACIdentifier) *Isolator { + var i Isolator + for j := len(*is) - 1; j >= 0; j-- { + i = []Isolator(*is)[j] + if i.Name == name { + return &i + } + } + return nil +} + +// Unrecognized returns a set of isolators that are not recognized. +// An isolator is not recognized if it has not had an associated +// constructor registered with AddIsolatorValueConstructor. +func (is *Isolators) Unrecognized() Isolators { + u := Isolators{} + for _, i := range *is { + if i.value == nil { + u = append(u, i) + } + } + return u +} + +type IsolatorValue interface { + UnmarshalJSON(b []byte) error + AssertValid() error +} +type Isolator struct { + Name ACIdentifier `json:"name"` + ValueRaw *json.RawMessage `json:"value"` + value IsolatorValue +} +type isolator Isolator + +func (i *Isolator) Value() IsolatorValue { + return i.value +} + +func (i *Isolator) UnmarshalJSON(b []byte) error { + var ii isolator + err := json.Unmarshal(b, &ii) + if err != nil { + return err + } + + var dst IsolatorValue + con, ok := isolatorMap[ii.Name] + if ok { + dst = con() + err = dst.UnmarshalJSON(*ii.ValueRaw) + if err != nil { + return err + } + err = dst.AssertValid() + if err != nil { + return err + } + } + + i.value = dst + i.ValueRaw = ii.ValueRaw + i.Name = ii.Name + + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_linux_specific.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_linux_specific.go new file mode 100644 index 0000000..23d4653 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_linux_specific.go @@ -0,0 +1,88 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" +) + +const ( + LinuxCapabilitiesRetainSetName = "os/linux/capabilities-retain-set" + LinuxCapabilitiesRevokeSetName = "os/linux/capabilities-remove-set" +) + +var LinuxIsolatorNames = make(map[ACIdentifier]struct{}) + +func init() { + AddIsolatorName(LinuxCapabilitiesRetainSetName, LinuxIsolatorNames) + AddIsolatorName(LinuxCapabilitiesRevokeSetName, LinuxIsolatorNames) + + AddIsolatorValueConstructor(LinuxCapabilitiesRetainSetName, NewLinuxCapabilitiesRetainSet) + AddIsolatorValueConstructor(LinuxCapabilitiesRevokeSetName, NewLinuxCapabilitiesRevokeSet) +} + +type LinuxCapabilitiesSet interface { + Set() []LinuxCapability + AssertValid() error +} + +type LinuxCapability string +type linuxCapabilitiesSetValue struct { + Set []LinuxCapability `json:"set"` +} + +type linuxCapabilitiesSetBase struct { + val linuxCapabilitiesSetValue +} + +func (l linuxCapabilitiesSetBase) AssertValid() error { + if len(l.val.Set) == 0 { + return errors.New("set must be non-empty") + } + return nil +} + +func (l *linuxCapabilitiesSetBase) UnmarshalJSON(b []byte) error { + var v linuxCapabilitiesSetValue + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + + l.val = v + + return err +} + +func (l linuxCapabilitiesSetBase) Set() []LinuxCapability { + return l.val.Set +} + +func NewLinuxCapabilitiesRetainSet() IsolatorValue { + return &LinuxCapabilitiesRetainSet{} +} + +type LinuxCapabilitiesRetainSet struct { + linuxCapabilitiesSetBase +} + +func NewLinuxCapabilitiesRevokeSet() IsolatorValue { + return &LinuxCapabilitiesRevokeSet{} +} + +type LinuxCapabilitiesRevokeSet struct { + linuxCapabilitiesSetBase +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_resources.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_resources.go new file mode 100644 index 0000000..20481a5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_resources.go @@ -0,0 +1,166 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + + "github.com/appc/acpush/Godeps/_workspace/src/k8s.io/kubernetes/pkg/api/resource" +) + +var ( + ErrDefaultTrue = errors.New("default must be false") + ErrDefaultRequired = errors.New("default must be true") + ErrRequestNonEmpty = errors.New("request not supported by this resource, must be empty") + + ResourceIsolatorNames = make(map[ACIdentifier]struct{}) +) + +const ( + ResourceBlockBandwidthName = "resource/block-bandwidth" + ResourceBlockIOPSName = "resource/block-iops" + ResourceCPUName = "resource/cpu" + ResourceMemoryName = "resource/memory" + ResourceNetworkBandwidthName = "resource/network-bandwidth" +) + +func init() { + AddIsolatorName(ResourceBlockBandwidthName, ResourceIsolatorNames) + AddIsolatorName(ResourceBlockIOPSName, ResourceIsolatorNames) + AddIsolatorName(ResourceCPUName, ResourceIsolatorNames) + AddIsolatorName(ResourceMemoryName, ResourceIsolatorNames) + AddIsolatorName(ResourceNetworkBandwidthName, ResourceIsolatorNames) + + AddIsolatorValueConstructor(ResourceBlockBandwidthName, NewResourceBlockBandwidth) + AddIsolatorValueConstructor(ResourceBlockIOPSName, NewResourceBlockIOPS) + AddIsolatorValueConstructor(ResourceCPUName, NewResourceCPU) + AddIsolatorValueConstructor(ResourceMemoryName, NewResourceMemory) + AddIsolatorValueConstructor(ResourceNetworkBandwidthName, NewResourceNetworkBandwidth) +} + +func NewResourceBlockBandwidth() IsolatorValue { + return &ResourceBlockBandwidth{} +} +func NewResourceBlockIOPS() IsolatorValue { + return &ResourceBlockIOPS{} +} +func NewResourceCPU() IsolatorValue { + return &ResourceCPU{} +} +func NewResourceNetworkBandwidth() IsolatorValue { + return &ResourceNetworkBandwidth{} +} +func NewResourceMemory() IsolatorValue { + return &ResourceMemory{} +} + +type Resource interface { + Limit() *resource.Quantity + Request() *resource.Quantity + Default() bool +} + +type ResourceBase struct { + val resourceValue +} + +type resourceValue struct { + Default bool `json:"default"` + Request *resource.Quantity `json:"request"` + Limit *resource.Quantity `json:"limit"` +} + +func (r ResourceBase) Limit() *resource.Quantity { + return r.val.Limit +} +func (r ResourceBase) Request() *resource.Quantity { + return r.val.Request +} +func (r ResourceBase) Default() bool { + return r.val.Default +} + +func (r *ResourceBase) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &r.val) +} + +func (r ResourceBase) AssertValid() error { + return nil +} + +type ResourceBlockBandwidth struct { + ResourceBase +} + +func (r ResourceBlockBandwidth) AssertValid() error { + if r.Default() != true { + return ErrDefaultRequired + } + if r.Request() != nil { + return ErrRequestNonEmpty + } + return nil +} + +type ResourceBlockIOPS struct { + ResourceBase +} + +func (r ResourceBlockIOPS) AssertValid() error { + if r.Default() != true { + return ErrDefaultRequired + } + if r.Request() != nil { + return ErrRequestNonEmpty + } + return nil +} + +type ResourceCPU struct { + ResourceBase +} + +func (r ResourceCPU) AssertValid() error { + if r.Default() != false { + return ErrDefaultTrue + } + return nil +} + +type ResourceMemory struct { + ResourceBase +} + +func (r ResourceMemory) AssertValid() error { + if r.Default() != false { + return ErrDefaultTrue + } + return nil +} + +type ResourceNetworkBandwidth struct { + ResourceBase +} + +func (r ResourceNetworkBandwidth) AssertValid() error { + if r.Default() != true { + return ErrDefaultRequired + } + if r.Request() != nil { + return ErrRequestNonEmpty + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_test.go new file mode 100644 index 0000000..785c4f0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/isolator_test.go @@ -0,0 +1,262 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestIsolatorUnmarshal(t *testing.T) { + tests := []struct { + msg string + werr bool + }{ + { + `{ + "name": "os/linux/capabilities-retain-set", + "value": {"set": ["CAP_KILL"]} + }`, + false, + }, + { + `{ + "name": "os/linux/capabilities-retain-set", + "value": {"set": ["CAP_PONIES"]} + }`, + false, + }, + { + `{ + "name": "os/linux/capabilities-retain-set", + "value": {"set": []} + }`, + true, + }, + { + `{ + "name": "os/linux/capabilities-retain-set", + "value": {"set": "CAP_PONIES"} + }`, + true, + }, + { + `{ + "name": "resource/block-bandwidth", + "value": {"default": true, "limit": "1G"} + }`, + false, + }, + { + `{ + "name": "resource/block-bandwidth", + "value": {"default": false, "limit": "1G"} + }`, + true, + }, + { + `{ + "name": "resource/block-bandwidth", + "value": {"request": "30G", "limit": "1G"} + }`, + true, + }, + { + `{ + "name": "resource/block-iops", + "value": {"default": true, "limit": "1G"} + }`, + false, + }, + { + `{ + "name": "resource/block-iops", + "value": {"default": false, "limit": "1G"} + }`, + true, + }, + { + `{ + "name": "resource/block-iops", + "value": {"request": "30G", "limit": "1G"} + }`, + true, + }, + { + `{ + "name": "resource/cpu", + "value": {"request": "30m", "limit": "1m"} + }`, + false, + }, + { + `{ + "name": "resource/memory", + "value": {"request": "1G", "limit": "2Gi"} + }`, + false, + }, + { + `{ + "name": "resource/memory", + "value": {"default": true, "request": "1G", "limit": "2G"} + }`, + true, + }, + { + `{ + "name": "resource/network-bandwidth", + "value": {"default": true, "limit": "1G"} + }`, + false, + }, + { + `{ + "name": "resource/network-bandwidth", + "value": {"default": false, "limit": "1G"} + }`, + true, + }, + { + `{ + "name": "resource/network-bandwidth", + "value": {"request": "30G", "limit": "1G"} + }`, + true, + }, + } + + for i, tt := range tests { + var ii Isolator + err := ii.UnmarshalJSON([]byte(tt.msg)) + if gerr := (err != nil); gerr != tt.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err) + } + } +} + +func TestIsolatorsGetByName(t *testing.T) { + ex := ` + [ + { + "name": "resource/cpu", + "value": {"request": "30m", "limit": "1m"} + }, + { + "name": "resource/memory", + "value": {"request": "1G", "limit": "2Gi"} + }, + { + "name": "os/linux/capabilities-retain-set", + "value": {"set": ["CAP_KILL"]} + }, + { + "name": "os/linux/capabilities-remove-set", + "value": {"set": ["CAP_KILL"]} + } + ] + ` + + tests := []struct { + name ACIdentifier + wlimit int64 + wrequest int64 + wset []LinuxCapability + }{ + {"resource/cpu", 1, 30, nil}, + {"resource/memory", 2147483648, 1000000000, nil}, + {"os/linux/capabilities-retain-set", 0, 0, []LinuxCapability{"CAP_KILL"}}, + {"os/linux/capabilities-remove-set", 0, 0, []LinuxCapability{"CAP_KILL"}}, + } + + var is Isolators + err := json.Unmarshal([]byte(ex), &is) + if err != nil { + panic(err) + } + + if len(is) < 2 { + t.Fatalf("too few items %v", len(is)) + } + + for i, tt := range tests { + c := is.GetByName(tt.name) + if c == nil { + t.Fatalf("can't find item %v in %v items", tt.name, len(is)) + } + switch v := c.Value().(type) { + case Resource: + var r Resource = v + glimit := r.Limit() + grequest := r.Request() + + var vlimit, vrequest int64 + if tt.name == "resource/cpu" { + vlimit, vrequest = glimit.MilliValue(), grequest.MilliValue() + } else { + vlimit, vrequest = glimit.Value(), grequest.Value() + } + + if vlimit != tt.wlimit { + t.Errorf("#%d: glimit=%v, want %v", i, vlimit, tt.wlimit) + } + if vrequest != tt.wrequest { + t.Errorf("#%d: grequest=%v, want %v", i, vrequest, tt.wrequest) + } + + case LinuxCapabilitiesSet: + var s LinuxCapabilitiesSet = v + if !reflect.DeepEqual(s.Set(), tt.wset) { + t.Errorf("#%d: gset=%v, want %v", i, s.Set(), tt.wset) + } + + default: + panic("unexecpected type") + } + } +} + +func TestIsolatorUnrecognized(t *testing.T) { + msg := ` + [{ + "name": "resource/network-bandwidth", + "value": {"default": true, "limit": "1G"} + }, + { + "name": "resource/network-ponies", + "value": 0 + }]` + + ex := Isolators{ + {Name: "resource/network-ponies"}, + } + + is := Isolators{} + if err := json.Unmarshal([]byte(msg), &is); err != nil { + t.Fatalf("failed to unmarshal isolators: %v", err) + } + + u := is.Unrecognized() + if len(u) != len(ex) { + t.Errorf("unrecognized isolator list is wrong len: want %v, got %v", len(ex), len(u)) + } + + for i, e := range ex { + if e.Name != u[i].Name { + t.Errorf("unrecognized isolator list mismatch: want %v, got %v", e.Name, u[i].Name) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/labels.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/labels.go new file mode 100644 index 0000000..ebd2bb1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/labels.go @@ -0,0 +1,134 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "fmt" + "sort" +) + +var ValidOSArch = map[string][]string{ + "linux": {"amd64", "i386", "aarch64", "aarch64_be", "armv6l", "armv7l", "armv7b"}, + "freebsd": {"amd64", "i386", "arm"}, + "darwin": {"x86_64", "i386"}, +} + +type Labels []Label + +type labels Labels + +type Label struct { + Name ACIdentifier `json:"name"` + Value string `json:"value"` +} + +// IsValidOsArch checks if a OS-architecture combination is valid given a map +// of valid OS-architectures +func IsValidOSArch(labels map[ACIdentifier]string, validOSArch map[string][]string) error { + if os, ok := labels["os"]; ok { + if validArchs, ok := validOSArch[os]; !ok { + // Not a whitelisted OS. TODO: how to warn rather than fail? + validOses := make([]string, 0, len(validOSArch)) + for validOs := range validOSArch { + validOses = append(validOses, validOs) + } + sort.Strings(validOses) + return fmt.Errorf(`bad os %#v (must be one of: %v)`, os, validOses) + } else { + // Whitelisted OS. We check arch here, as arch makes sense only + // when os is defined. + if arch, ok := labels["arch"]; ok { + found := false + for _, validArch := range validArchs { + if arch == validArch { + found = true + break + } + } + if !found { + return fmt.Errorf(`bad arch %#v for %v (must be one of: %v)`, arch, os, validArchs) + } + } + } + } + return nil +} + +func (l Labels) assertValid() error { + seen := map[ACIdentifier]string{} + for _, lbl := range l { + if lbl.Name == "name" { + return fmt.Errorf(`invalid label name: "name"`) + } + _, ok := seen[lbl.Name] + if ok { + return fmt.Errorf(`duplicate labels of name %q`, lbl.Name) + } + seen[lbl.Name] = lbl.Value + } + return IsValidOSArch(seen, ValidOSArch) +} + +func (l Labels) MarshalJSON() ([]byte, error) { + if err := l.assertValid(); err != nil { + return nil, err + } + return json.Marshal(labels(l)) +} + +func (l *Labels) UnmarshalJSON(data []byte) error { + var jl labels + if err := json.Unmarshal(data, &jl); err != nil { + return err + } + nl := Labels(jl) + if err := nl.assertValid(); err != nil { + return err + } + *l = nl + return nil +} + +// Get retrieves the value of the label by the given name from Labels, if it exists +func (l Labels) Get(name string) (val string, ok bool) { + for _, lbl := range l { + if lbl.Name.String() == name { + return lbl.Value, true + } + } + return "", false +} + +// ToMap creates a map[ACIdentifier]string. +func (l Labels) ToMap() map[ACIdentifier]string { + labelsMap := make(map[ACIdentifier]string) + for _, lbl := range l { + labelsMap[lbl.Name] = lbl.Value + } + return labelsMap +} + +// LabelsFromMap creates Labels from a map[ACIdentifier]string +func LabelsFromMap(labelsMap map[ACIdentifier]string) (Labels, error) { + labels := Labels{} + for n, v := range labelsMap { + labels = append(labels, Label{Name: n, Value: v}) + } + if err := labels.assertValid(); err != nil { + return nil, err + } + return labels, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/labels_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/labels_test.go new file mode 100644 index 0000000..9759d88 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/labels_test.go @@ -0,0 +1,108 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestLabels(t *testing.T) { + tests := []struct { + in string + errPrefix string + }{ + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "amd64"}]`, + "", + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "aarch64"}]`, + "", + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "arm64"}]`, + `bad arch "arm64" for linux`, + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "aarch64_be"}]`, + "", + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "arm64_be"}]`, + `bad arch "arm64_be" for linux`, + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "arm"}]`, + `bad arch "arm" for linux`, + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "armv6l"}]`, + "", + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "armv7l"}]`, + "", + }, + { + `[{"name": "os", "value": "linux"}, {"name": "arch", "value": "armv7b"}]`, + "", + }, + { + `[{"name": "os", "value": "freebsd"}, {"name": "arch", "value": "amd64"}]`, + "", + }, + { + `[{"name": "os", "value": "OS/360"}, {"name": "arch", "value": "S/360"}]`, + `bad os "OS/360"`, + }, + { + `[{"name": "os", "value": "freebsd"}, {"name": "arch", "value": "armv7b"}]`, + `bad arch "armv7b" for freebsd`, + }, + { + `[{"name": "name"}]`, + `invalid label name: "name"`, + }, + { + `[{"name": "os", "value": "linux"}, {"name": "os", "value": "freebsd"}]`, + `duplicate labels of name "os"`, + }, + { + `[{"name": "arch", "value": "amd64"}, {"name": "os", "value": "freebsd"}, {"name": "arch", "value": "x86_64"}]`, + `duplicate labels of name "arch"`, + }, + { + `[]`, + "", + }, + } + for i, tt := range tests { + var l Labels + if err := json.Unmarshal([]byte(tt.in), &l); err != nil { + if tt.errPrefix == "" { + t.Errorf("#%d: got err=%v, expected no error", i, err) + } else if !strings.HasPrefix(err.Error(), tt.errPrefix) { + t.Errorf("#%d: got err=%v, expected prefix %#v", i, err, tt.errPrefix) + } + } else { + t.Log(l) + if tt.errPrefix != "" { + t.Errorf("#%d: got no err, expected prefix %#v", i, tt.errPrefix) + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint.go new file mode 100644 index 0000000..69ff114 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint.go @@ -0,0 +1,85 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "errors" + "fmt" + "net/url" + "strconv" + "strings" +) + +type MountPoint struct { + Name ACName `json:"name"` + Path string `json:"path"` + ReadOnly bool `json:"readOnly,omitempty"` +} + +func (mount MountPoint) assertValid() error { + if mount.Name.Empty() { + return errors.New("name must be set") + } + if len(mount.Path) == 0 { + return errors.New("path must be set") + } + return nil +} + +// MountPointFromString takes a command line mountpoint parameter and returns a mountpoint +// +// It is useful for actool patch-manifest --mounts +// +// Example mountpoint parameters: +// database,path=/tmp,readOnly=true +func MountPointFromString(mp string) (*MountPoint, error) { + var mount MountPoint + + mp = "name=" + mp + v, err := url.ParseQuery(strings.Replace(mp, ",", "&", -1)) + if err != nil { + return nil, err + } + for key, val := range v { + if len(val) > 1 { + return nil, fmt.Errorf("label %s with multiple values %q", key, val) + } + + switch key { + case "name": + acn, err := NewACName(val[0]) + if err != nil { + return nil, err + } + mount.Name = *acn + case "path": + mount.Path = val[0] + case "readOnly": + ro, err := strconv.ParseBool(val[0]) + if err != nil { + return nil, err + } + mount.ReadOnly = ro + default: + return nil, fmt.Errorf("unknown mountpoint parameter %q", key) + } + } + err = mount.assertValid() + if err != nil { + return nil, err + } + + return &mount, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint_test.go new file mode 100644 index 0000000..f7b216e --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/mountpoint_test.go @@ -0,0 +1,83 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "reflect" + "testing" +) + +func TestMountPointFromString(t *testing.T) { + tests := []struct { + s string + mount MountPoint + }{ + { + "foobar,path=/tmp", + MountPoint{ + Name: "foobar", + Path: "/tmp", + ReadOnly: false, + }, + }, + { + "foobar,path=/tmp,readOnly=false", + MountPoint{ + Name: "foobar", + Path: "/tmp", + ReadOnly: false, + }, + }, + { + "foobar,path=/tmp,readOnly=true", + MountPoint{ + Name: "foobar", + Path: "/tmp", + ReadOnly: true, + }, + }, + } + for i, tt := range tests { + mount, err := MountPointFromString(tt.s) + if err != nil { + t.Errorf("#%d: got err=%v, want nil", i, err) + } + if !reflect.DeepEqual(*mount, tt.mount) { + t.Errorf("#%d: mount=%v, want %v", i, *mount, tt.mount) + } + } +} + +func TestMountPointFromStringBad(t *testing.T) { + tests := []string{ + "#foobar,path=/tmp", + "foobar,path=/tmp,readOnly=true,asdf=asdf", + "foobar,path=/tmp,readOnly=maybe", + "foobar,path=/tmp,readOnly=", + "foobar,path=", + "foobar", + "", + ",path=/", + } + for i, in := range tests { + l, err := MountPointFromString(in) + if l != nil { + t.Errorf("#%d: got l=%v, want nil", i, l) + } + if err == nil { + t.Errorf("#%d: got err=nil, want non-nil", i) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/port.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/port.go new file mode 100644 index 0000000..519025e --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/port.go @@ -0,0 +1,133 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + "fmt" + "net/url" + "strconv" + "strings" +) + +type Port struct { + Name ACName `json:"name"` + Protocol string `json:"protocol"` + Port uint `json:"port"` + Count uint `json:"count"` + SocketActivated bool `json:"socketActivated"` +} + +type ExposedPort struct { + Name ACName `json:"name"` + HostPort uint `json:"hostPort"` +} + +type port Port + +func (p *Port) UnmarshalJSON(data []byte) error { + var pp port + if err := json.Unmarshal(data, &pp); err != nil { + return err + } + np := Port(pp) + if err := np.assertValid(); err != nil { + return err + } + if np.Count == 0 { + np.Count = 1 + } + *p = np + return nil +} + +func (p Port) MarshalJSON() ([]byte, error) { + if err := p.assertValid(); err != nil { + return nil, err + } + return json.Marshal(port(p)) +} + +func (p Port) assertValid() error { + // Although there are no guarantees, most (if not all) + // transport protocols use 16 bit ports + if p.Port > 65535 || p.Port < 1 { + return errors.New("port must be in 1-65535 range") + } + if p.Port+p.Count > 65536 { + return errors.New("end of port range must be in 1-65535 range") + } + return nil +} + +// PortFromString takes a command line port parameter and returns a port +// +// It is useful for actool patch-manifest --ports +// +// Example port parameters: +// health-check,protocol=udp,port=8000 +// query,protocol=tcp,port=8080,count=1,socketActivated=true +func PortFromString(pt string) (*Port, error) { + var port Port + + pt = "name=" + pt + v, err := url.ParseQuery(strings.Replace(pt, ",", "&", -1)) + if err != nil { + return nil, err + } + for key, val := range v { + if len(val) > 1 { + return nil, fmt.Errorf("label %s with multiple values %q", key, val) + } + + switch key { + case "name": + acn, err := NewACName(val[0]) + if err != nil { + return nil, err + } + port.Name = *acn + case "protocol": + port.Protocol = val[0] + case "port": + p, err := strconv.ParseUint(val[0], 10, 16) + if err != nil { + return nil, err + } + port.Port = uint(p) + case "count": + cnt, err := strconv.ParseUint(val[0], 10, 16) + if err != nil { + return nil, err + } + port.Count = uint(cnt) + case "socketActivated": + sa, err := strconv.ParseBool(val[0]) + if err != nil { + return nil, err + } + port.SocketActivated = sa + default: + return nil, fmt.Errorf("unknown port parameter %q", key) + } + } + err = port.assertValid() + if err != nil { + return nil, err + } + + return &port, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/port_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/port_test.go new file mode 100644 index 0000000..3462c0e --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/port_test.go @@ -0,0 +1,48 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "testing" +) + +func TestGoodPort(t *testing.T) { + p := Port{ + Port: 32456, + Count: 100, + } + if err := p.assertValid(); err != nil { + t.Errorf("good port assertion failed: %v", err) + } +} + +func TestBadPort(t *testing.T) { + p := Port{ + Port: 88888, + } + if p.assertValid() == nil { + t.Errorf("bad port asserted valid") + } +} + +func TestBadRange(t *testing.T) { + p := Port{ + Port: 32456, + Count: 45678, + } + if p.assertValid() == nil { + t.Errorf("bad port range asserted valid") + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/semver.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/semver.go new file mode 100644 index 0000000..fbe21de --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/semver.go @@ -0,0 +1,91 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/coreos/go-semver/semver" +) + +var ( + ErrNoZeroSemVer = ACVersionError("SemVer cannot be zero") + ErrBadSemVer = ACVersionError("SemVer is bad") +) + +// SemVer implements the Unmarshaler interface to define a field that must be +// a semantic version string +// TODO(jonboulle): extend upstream instead of wrapping? +type SemVer semver.Version + +// NewSemVer generates a new SemVer from a string. If the given string does +// not represent a valid SemVer, nil and an error are returned. +func NewSemVer(s string) (*SemVer, error) { + nsv, err := semver.NewVersion(s) + if err != nil { + return nil, ErrBadSemVer + } + v := SemVer(*nsv) + if v.Empty() { + return nil, ErrNoZeroSemVer + } + return &v, nil +} + +func (sv SemVer) LessThanMajor(versionB SemVer) bool { + majorA := semver.Version(sv).Major + majorB := semver.Version(versionB).Major + if majorA < majorB { + return true + } + return false +} + +func (sv SemVer) LessThanExact(versionB SemVer) bool { + vA := semver.Version(sv) + vB := semver.Version(versionB) + return vA.LessThan(vB) +} + +func (sv SemVer) String() string { + s := semver.Version(sv) + return s.String() +} + +func (sv SemVer) Empty() bool { + return semver.Version(sv) == semver.Version{} +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (sv *SemVer) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + v, err := NewSemVer(s) + if err != nil { + return err + } + *sv = *v + return nil +} + +// MarshalJSON implements the json.Marshaler interface +func (sv SemVer) MarshalJSON() ([]byte, error) { + if sv.Empty() { + return nil, ErrNoZeroSemVer + } + return json.Marshal(sv.String()) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/semver_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/semver_test.go new file mode 100644 index 0000000..fdcf6ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/semver_test.go @@ -0,0 +1,129 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/coreos/go-semver/semver" +) + +func TestMarshalSemver(t *testing.T) { + tests := []struct { + sv SemVer + + wd []byte + }{ + { + SemVer(semver.Version{Major: 1}), + + []byte(`"1.0.0"`), + }, + { + SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1}), + + []byte(`"3.2.1"`), + }, + { + SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1, PreRelease: "foo"}), + + []byte(`"3.2.1-foo"`), + }, + { + SemVer(semver.Version{Major: 1, Minor: 2, Patch: 3, PreRelease: "alpha", Metadata: "git"}), + + []byte(`"1.2.3-alpha+git"`), + }, + } + for i, tt := range tests { + d, err := json.Marshal(tt.sv) + if !reflect.DeepEqual(d, tt.wd) { + t.Errorf("#%d: d=%v, want %v", i, string(d), string(tt.wd)) + } + if err != nil { + t.Errorf("#%d: err=%v, want nil", i, err) + } + } +} + +func TestUnmarshalSemver(t *testing.T) { + tests := []struct { + d []byte + + wsv SemVer + werr bool + }{ + { + []byte(`"1.0.0"`), + + SemVer(semver.Version{Major: 1}), + false, + }, + { + []byte(`"3.2.1"`), + SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1}), + + false, + }, + { + []byte(`"3.2.1-foo"`), + + SemVer(semver.Version{Major: 3, Minor: 2, Patch: 1, PreRelease: "foo"}), + false, + }, + { + []byte(`"1.2.3-alpha+git"`), + + SemVer(semver.Version{Major: 1, Minor: 2, Patch: 3, PreRelease: "alpha", Metadata: "git"}), + false, + }, + { + []byte(`"1"`), + + SemVer{}, + true, + }, + { + []byte(`"1.2.3.4"`), + + SemVer{}, + true, + }, + { + []byte(`1.2.3`), + + SemVer{}, + true, + }, + { + []byte(`"v1.2.3"`), + + SemVer{}, + true, + }, + } + for i, tt := range tests { + var sv SemVer + err := json.Unmarshal(tt.d, &sv) + if !reflect.DeepEqual(sv, tt.wsv) { + t.Errorf("#%d: semver=%#v, want %#v", i, sv, tt.wsv) + } + if gerr := (err != nil); gerr != tt.werr { + t.Errorf("#%d: err==%v, want errstate %t", i, err, tt.werr) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/url.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/url.go new file mode 100644 index 0000000..d4f8f33 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/url.go @@ -0,0 +1,71 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "fmt" + "net/url" +) + +// URL wraps url.URL to marshal/unmarshal to/from JSON strings and enforce +// that the scheme is HTTP/HTTPS only +type URL url.URL + +func NewURL(s string) (*URL, error) { + uu, err := url.Parse(s) + if err != nil { + return nil, fmt.Errorf("bad URL: %v", err) + } + nu := URL(*uu) + if err := nu.assertValidScheme(); err != nil { + return nil, err + } + return &nu, nil +} + +func (u URL) String() string { + uu := url.URL(u) + return uu.String() +} + +func (u URL) assertValidScheme() error { + switch u.Scheme { + case "http", "https": + return nil + default: + return fmt.Errorf("bad URL scheme, must be http/https") + } +} + +func (u *URL) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + nu, err := NewURL(s) + if err != nil { + return err + } + *u = *nu + return nil +} + +func (u URL) MarshalJSON() ([]byte, error) { + if err := u.assertValidScheme(); err != nil { + return nil, err + } + return json.Marshal(u.String()) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/url_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/url_test.go new file mode 100644 index 0000000..fa1e65d --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/url_test.go @@ -0,0 +1,137 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "net/url" + "reflect" + "testing" +) + +func mustParseURL(t *testing.T, s string) url.URL { + u, err := url.Parse(s) + if err != nil { + t.Fatalf("error parsing URL: %v", err) + } + return *u +} + +func TestMarshalURL(t *testing.T) { + tests := []struct { + u url.URL + + w string + }{ + { + mustParseURL(t, "http://foo.com"), + + `"http://foo.com"`, + }, + { + mustParseURL(t, "http://foo.com/huh/what?is=this"), + + `"http://foo.com/huh/what?is=this"`, + }, + { + mustParseURL(t, "https://example.com/bar"), + + `"https://example.com/bar"`, + }, + } + for i, tt := range tests { + u := URL(tt.u) + b, err := json.Marshal(u) + if g := string(b); g != tt.w { + t.Errorf("#%d: got %q, want %q", i, g, tt.w) + } + if err != nil { + t.Errorf("#%d: err=%v, want nil", i, err) + } + + } +} + +func TestMarshalURLBad(t *testing.T) { + tests := []url.URL{ + mustParseURL(t, "ftp://foo.com"), + mustParseURL(t, "unix:///hello"), + } + for i, tt := range tests { + u := URL(tt) + b, err := json.Marshal(u) + if b != nil { + t.Errorf("#%d: got %v, want nil", i, b) + } + if err == nil { + t.Errorf("#%d: got unexpected err=nil", i) + } + } +} + +func TestUnmarshalURL(t *testing.T) { + tests := []struct { + in string + + w URL + }{ + { + `"http://foo.com"`, + + URL(mustParseURL(t, "http://foo.com")), + }, + { + `"http://yis.com/hello?goodbye=yes"`, + + URL(mustParseURL(t, "http://yis.com/hello?goodbye=yes")), + }, + { + `"https://ohai.net"`, + + URL(mustParseURL(t, "https://ohai.net")), + }, + } + for i, tt := range tests { + var g URL + err := json.Unmarshal([]byte(tt.in), &g) + if err != nil { + t.Errorf("#%d: want err=nil, got %v", i, err) + } + if !reflect.DeepEqual(g, tt.w) { + t.Errorf("#%d: got url=%v, want %v", i, g, tt.w) + } + } +} + +func TestUnmarshalURLBad(t *testing.T) { + var empty = URL{} + tests := []string{ + "badjson", + "http://google.com", + `"ftp://example.com"`, + `"unix://file.net"`, + `"not a url"`, + } + for i, tt := range tests { + var g URL + err := json.Unmarshal([]byte(tt), &g) + if err == nil { + t.Errorf("#%d: want err, got nil", i) + } + if !reflect.DeepEqual(g, empty) { + t.Errorf("#%d: got %v, want %v", i, g, empty) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid.go new file mode 100644 index 0000000..4925b76 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid.go @@ -0,0 +1,92 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" +) + +var ( + ErrNoEmptyUUID = errors.New("UUID cannot be empty") +) + +// UUID encodes an RFC4122-compliant UUID, marshaled to/from a string +// TODO(jonboulle): vendor a package for this? +// TODO(jonboulle): consider more flexibility in input string formats. +// Right now, we only accept: +// "6733C088-A507-4694-AABF-EDBE4FC5266F" +// "6733C088A5074694AABFEDBE4FC5266F" +type UUID [16]byte + +func (u UUID) String() string { + return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:16]) +} + +func (u *UUID) Set(s string) error { + nu, err := NewUUID(s) + if err == nil { + *u = *nu + } + return err +} + +// NewUUID generates a new UUID from the given string. If the string does not +// represent a valid UUID, nil and an error are returned. +func NewUUID(s string) (*UUID, error) { + s = strings.Replace(s, "-", "", -1) + if len(s) != 32 { + return nil, errors.New("bad UUID length != 32") + } + dec, err := hex.DecodeString(s) + if err != nil { + return nil, err + } + var u UUID + for i, b := range dec { + u[i] = b + } + return &u, nil +} + +func (u UUID) Empty() bool { + return reflect.DeepEqual(u, UUID{}) +} + +func (u *UUID) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + uu, err := NewUUID(s) + if uu.Empty() { + return ErrNoEmptyUUID + } + if err == nil { + *u = *uu + } + return err +} + +func (u UUID) MarshalJSON() ([]byte, error) { + if u.Empty() { + return nil, ErrNoEmptyUUID + } + return json.Marshal(u.String()) +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid_test.go new file mode 100644 index 0000000..03c9320 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/uuid_test.go @@ -0,0 +1,73 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import "testing" + +func TestNewUUID(t *testing.T) { + tests := []struct { + in string + ws string + }{ + { + "6733C088-A507-4694-AABF-EDBE4FC5266F", + + "6733c088-a507-4694-aabf-edbe4fc5266f", + }, + { + "6733C088A5074694AABFEDBE4FC5266F", + + "6733c088-a507-4694-aabf-edbe4fc5266f", + }, + { + "0aaf0a79-1a39-4d59-abbf-1bebca8209d2", + + "0aaf0a79-1a39-4d59-abbf-1bebca8209d2", + }, + { + "0aaf0a791a394d59abbf1bebca8209d2", + + "0aaf0a79-1a39-4d59-abbf-1bebca8209d2", + }, + } + for i, tt := range tests { + gu, err := NewUUID(tt.in) + if err != nil { + t.Errorf("#%d: err=%v, want %v", i, err, nil) + } + if gs := gu.String(); gs != tt.ws { + t.Errorf("#%d: String()=%v, want %v", i, gs, tt.ws) + } + } +} + +func TestNewUUIDBad(t *testing.T) { + tests := []string{ + "asdf", + "0AAF0A79-1A39-4D59-ABBF-1BEBCA8209D2ABC", + "", + } + for i, tt := range tests { + g, err := NewUUID(tt) + if err == nil { + t.Errorf("#%d: err=nil, want non-nil", i) + } + if g != nil { + t.Errorf("#%d: err=%v, want %v", i, g, nil) + + } + } + +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/volume.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/volume.go new file mode 100644 index 0000000..eeee3a6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/volume.go @@ -0,0 +1,137 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "encoding/json" + "errors" + "fmt" + "net/url" + "path/filepath" + "strconv" + "strings" +) + +// Volume encapsulates a volume which should be mounted into the filesystem +// of all apps in a PodManifest +type Volume struct { + Name ACName `json:"name"` + Kind string `json:"kind"` + + // currently used only by "host" + // TODO(jonboulle): factor out? + Source string `json:"source,omitempty"` + ReadOnly *bool `json:"readOnly,omitempty"` +} + +type volume Volume + +func (v Volume) assertValid() error { + if v.Name.Empty() { + return errors.New("name must be set") + } + + switch v.Kind { + case "empty": + if v.Source != "" { + return errors.New("source for empty volume must be empty") + } + return nil + case "host": + if v.Source == "" { + return errors.New("source for host volume cannot be empty") + } + if !filepath.IsAbs(v.Source) { + return errors.New("source for host volume must be absolute path") + } + return nil + default: + return errors.New(`unrecognized volume kind: should be one of "empty", "host"`) + } +} + +func (v *Volume) UnmarshalJSON(data []byte) error { + var vv volume + if err := json.Unmarshal(data, &vv); err != nil { + return err + } + nv := Volume(vv) + if err := nv.assertValid(); err != nil { + return err + } + *v = nv + return nil +} + +func (v Volume) MarshalJSON() ([]byte, error) { + if err := v.assertValid(); err != nil { + return nil, err + } + return json.Marshal(volume(v)) +} + +func (v Volume) String() string { + s := fmt.Sprintf("%s,kind=%s,readOnly=%t", v.Name, v.Kind, *v.ReadOnly) + if v.Source != "" { + s = s + fmt.Sprintf("source=%s", v.Source) + } + return s +} + +// VolumeFromString takes a command line volume parameter and returns a volume +// +// Example volume parameters: +// database,kind=host,source=/tmp,readOnly=true +func VolumeFromString(vp string) (*Volume, error) { + var vol Volume + + vp = "name=" + vp + v, err := url.ParseQuery(strings.Replace(vp, ",", "&", -1)) + if err != nil { + return nil, err + } + for key, val := range v { + if len(val) > 1 { + return nil, fmt.Errorf("label %s with multiple values %q", key, val) + } + + switch key { + case "name": + acn, err := NewACName(val[0]) + if err != nil { + return nil, err + } + vol.Name = *acn + case "kind": + vol.Kind = val[0] + case "source": + vol.Source = val[0] + case "readOnly": + ro, err := strconv.ParseBool(val[0]) + if err != nil { + return nil, err + } + vol.ReadOnly = &ro + default: + return nil, fmt.Errorf("unknown volume parameter %q", key) + } + } + err = vol.assertValid() + if err != nil { + return nil, err + } + + return &vol, nil +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/types/volume_test.go b/Godeps/_workspace/src/github.com/appc/spec/schema/types/volume_test.go new file mode 100644 index 0000000..8094793 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/types/volume_test.go @@ -0,0 +1,99 @@ +// Copyright 2015 The appc Authors +// +// 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 types + +import ( + "reflect" + "testing" +) + +func TestVolumeFromString(t *testing.T) { + trueVar := true + falseVar := false + tests := []struct { + s string + v Volume + }{ + { + "foobar,kind=host,source=/tmp", + Volume{ + Name: "foobar", + Kind: "host", + Source: "/tmp", + ReadOnly: nil, + }, + }, + { + "foobar,kind=host,source=/tmp,readOnly=false", + Volume{ + Name: "foobar", + Kind: "host", + Source: "/tmp", + ReadOnly: &falseVar, + }, + }, + { + "foobar,kind=host,source=/tmp,readOnly=true", + Volume{ + Name: "foobar", + Kind: "host", + Source: "/tmp", + ReadOnly: &trueVar, + }, + }, + { + "foobar,kind=empty", + Volume{ + Name: "foobar", + Kind: "empty", + ReadOnly: nil, + }, + }, + { + "foobar,kind=empty,readOnly=true", + Volume{ + Name: "foobar", + Kind: "empty", + ReadOnly: &trueVar, + }, + }, + } + for i, tt := range tests { + v, err := VolumeFromString(tt.s) + if err != nil { + t.Errorf("#%d: got err=%v, want nil", i, err) + } + if !reflect.DeepEqual(*v, tt.v) { + t.Errorf("#%d: v=%v, want %v", i, *v, tt.v) + } + } +} + +func TestVolumeFromStringBad(t *testing.T) { + tests := []string{ + "#foobar,kind=host,source=/tmp", + "foobar,kind=host,source=/tmp,readOnly=true,asdf=asdf", + "foobar,kind=empty,source=/tmp", + } + for i, in := range tests { + l, err := VolumeFromString(in) + if l != nil { + t.Errorf("#%d: got l=%v, want nil", i, l) + } + if err == nil { + t.Errorf("#%d: got err=nil, want non-nil", i) + } + } +} diff --git a/Godeps/_workspace/src/github.com/appc/spec/schema/version.go b/Godeps/_workspace/src/github.com/appc/spec/schema/version.go new file mode 100644 index 0000000..ae95af9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/appc/spec/schema/version.go @@ -0,0 +1,39 @@ +// Copyright 2015 The appc Authors +// +// 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 schema + +import ( + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +const ( + // version represents the canonical version of the appc spec and tooling. + // For now, the schema and tooling is coupled with the spec itself, so + // this must be kept in sync with the VERSION file in the root of the repo. + version string = "0.7.1+git" +) + +var ( + // AppContainerVersion is the SemVer representation of version + AppContainerVersion types.SemVer +) + +func init() { + v, err := types.NewSemVer(version) + if err != nil { + panic(err) + } + AppContainerVersion = *v +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver.go b/Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver.go new file mode 100644 index 0000000..4e10221 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver.go @@ -0,0 +1,202 @@ +package semver + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" +) + +type Version struct { + Major int64 + Minor int64 + Patch int64 + PreRelease PreRelease + Metadata string +} + +type PreRelease string + +func splitOff(input *string, delim string) (val string) { + parts := strings.SplitN(*input, delim, 2) + + if len(parts) == 2 { + *input = parts[0] + val = parts[1] + } + + return val +} + +func NewVersion(version string) (*Version, error) { + v := Version{} + + dotParts := strings.SplitN(version, ".", 3) + + if len(dotParts) != 3 { + return nil, errors.New(fmt.Sprintf("%s is not in dotted-tri format", version)) + } + + v.Metadata = splitOff(&dotParts[2], "+") + v.PreRelease = PreRelease(splitOff(&dotParts[2], "-")) + + parsed := make([]int64, 3, 3) + + for i, v := range dotParts[:3] { + val, err := strconv.ParseInt(v, 10, 64) + parsed[i] = val + if err != nil { + return nil, err + } + } + + v.Major = parsed[0] + v.Minor = parsed[1] + v.Patch = parsed[2] + + return &v, nil +} + +func (v *Version) String() string { + var buffer bytes.Buffer + + base := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) + buffer.WriteString(base) + + if v.PreRelease != "" { + buffer.WriteString(fmt.Sprintf("-%s", v.PreRelease)) + } + + if v.Metadata != "" { + buffer.WriteString(fmt.Sprintf("+%s", v.Metadata)) + } + + return buffer.String() +} + +func (v *Version) LessThan(versionB Version) bool { + versionA := *v + cmp := recursiveCompare(versionA.Slice(), versionB.Slice()) + + if cmp == 0 { + cmp = preReleaseCompare(versionA, versionB) + } + + if cmp == -1 { + return true + } + + return false +} + +/* Slice converts the comparable parts of the semver into a slice of strings */ +func (v *Version) Slice() []int64 { + return []int64{v.Major, v.Minor, v.Patch} +} + +func (p *PreRelease) Slice() []string { + preRelease := string(*p) + return strings.Split(preRelease, ".") +} + +func preReleaseCompare(versionA Version, versionB Version) int { + a := versionA.PreRelease + b := versionB.PreRelease + + /* Handle the case where if two versions are otherwise equal it is the + * one without a PreRelease that is greater */ + if len(a) == 0 && (len(b) > 0) { + return 1 + } else if len(b) == 0 && (len(a) > 0) { + return -1 + } + + // If there is a prelease, check and compare each part. + return recursivePreReleaseCompare(a.Slice(), b.Slice()) +} + +func recursiveCompare(versionA []int64, versionB []int64) int { + if len(versionA) == 0 { + return 0 + } + + a := versionA[0] + b := versionB[0] + + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursiveCompare(versionA[1:], versionB[1:]) +} + +func recursivePreReleaseCompare(versionA []string, versionB []string) int { + // Handle slice length disparity. + if len(versionA) == 0 { + // Nothing to compare too, so we return 0 + return 0 + } else if len(versionB) == 0 { + // We're longer than versionB so return 1. + return 1 + } + + a := versionA[0] + b := versionB[0] + + aInt := false; bInt := false + + aI, err := strconv.Atoi(versionA[0]) + if err == nil { + aInt = true + } + + bI, err := strconv.Atoi(versionB[0]) + if err == nil { + bInt = true + } + + // Handle Integer Comparison + if aInt && bInt { + if aI > bI { + return 1 + } else if aI < bI { + return -1 + } + } + + // Handle String Comparison + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursivePreReleaseCompare(versionA[1:], versionB[1:]) +} + +// BumpMajor increments the Major field by 1 and resets all other fields to their default values +func (v *Version) BumpMajor() { + v.Major += 1 + v.Minor = 0 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" +} + +// BumpMinor increments the Minor field by 1 and resets all other fields to their default values +func (v *Version) BumpMinor() { + v.Minor += 1 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" +} + +// BumpPatch increments the Patch field by 1 and resets all other fields to their default values +func (v *Version) BumpPatch() { + v.Patch += 1 + v.PreRelease = PreRelease("") + v.Metadata = "" +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver_test.go b/Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver_test.go new file mode 100644 index 0000000..de09cd7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-semver/semver/semver_test.go @@ -0,0 +1,187 @@ +package semver + +import ( + "math/rand" + "testing" + "time" +) + +type fixture struct { + greaterVersion string + lesserVersion string +} + +var fixtures = []fixture{ + fixture{"0.0.0", "0.0.0-foo"}, + fixture{"0.0.1", "0.0.0"}, + fixture{"1.0.0", "0.9.9"}, + fixture{"0.10.0", "0.9.0"}, + fixture{"0.99.0", "0.10.0"}, + fixture{"2.0.0", "1.2.3"}, + fixture{"0.0.0", "0.0.0-foo"}, + fixture{"0.0.1", "0.0.0"}, + fixture{"1.0.0", "0.9.9"}, + fixture{"0.10.0", "0.9.0"}, + fixture{"0.99.0", "0.10.0"}, + fixture{"2.0.0", "1.2.3"}, + fixture{"0.0.0", "0.0.0-foo"}, + fixture{"0.0.1", "0.0.0"}, + fixture{"1.0.0", "0.9.9"}, + fixture{"0.10.0", "0.9.0"}, + fixture{"0.99.0", "0.10.0"}, + fixture{"2.0.0", "1.2.3"}, + fixture{"1.2.3", "1.2.3-asdf"}, + fixture{"1.2.3", "1.2.3-4"}, + fixture{"1.2.3", "1.2.3-4-foo"}, + fixture{"1.2.3-5-foo", "1.2.3-5"}, + fixture{"1.2.3-5", "1.2.3-4"}, + fixture{"1.2.3-5-foo", "1.2.3-5-Foo"}, + fixture{"3.0.0", "2.7.2+asdf"}, + fixture{"3.0.0+foobar", "2.7.2"}, + fixture{"1.2.3-a.10", "1.2.3-a.5"}, + fixture{"1.2.3-a.b", "1.2.3-a.5"}, + fixture{"1.2.3-a.b", "1.2.3-a"}, + fixture{"1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"}, + fixture{"1.0.0", "1.0.0-rc.1"}, + fixture{"1.0.0-rc.2", "1.0.0-rc.1"}, + fixture{"1.0.0-rc.1", "1.0.0-beta.11"}, + fixture{"1.0.0-beta.11", "1.0.0-beta.2"}, + fixture{"1.0.0-beta.2", "1.0.0-beta"}, + fixture{"1.0.0-beta", "1.0.0-alpha.beta"}, + fixture{"1.0.0-alpha.beta", "1.0.0-alpha.1"}, + fixture{"1.0.0-alpha.1", "1.0.0-alpha"}, +} + +func TestCompare(t *testing.T) { + for _, v := range fixtures { + gt, err := NewVersion(v.greaterVersion) + if err != nil { + t.Error(err) + } + + lt, err := NewVersion(v.lesserVersion) + if err != nil { + t.Error(err) + } + + if gt.LessThan(*lt) == true { + t.Errorf("%s should not be less than %s", gt, lt) + } + } +} + +func testString(t *testing.T, orig string, version *Version) { + if orig != version.String() { + t.Errorf("%s != %s", orig, version) + } +} + +func TestString(t *testing.T) { + for _, v := range fixtures { + gt, err := NewVersion(v.greaterVersion) + if err != nil { + t.Error(err) + } + testString(t, v.greaterVersion, gt) + + lt, err := NewVersion(v.lesserVersion) + if err != nil { + t.Error(err) + } + testString(t, v.lesserVersion, lt) + } +} + +func shuffleStringSlice(src []string) []string { + dest := make([]string, len(src)) + rand.Seed(time.Now().Unix()) + perm := rand.Perm(len(src)) + for i, v := range perm { + dest[v] = src[i] + } + return dest +} + +func TestSort(t *testing.T) { + sortedVersions := []string{"1.0.0", "1.0.2", "1.2.0", "3.1.1"} + unsortedVersions := shuffleStringSlice(sortedVersions) + + semvers := []*Version{} + for _, v := range unsortedVersions { + sv, err := NewVersion(v) + if err != nil { + t.Fatal(err) + } + semvers = append(semvers, sv) + } + + Sort(semvers) + + for idx, sv := range semvers { + if sv.String() != sortedVersions[idx] { + t.Fatalf("incorrect sort at index %v", idx) + } + } +} + +func TestBumpMajor(t *testing.T) { + version, _ := NewVersion("1.0.0") + version.BumpMajor() + if version.Major != 2 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + version, _ = NewVersion("1.5.2") + version.BumpMajor() + if version.Minor != 0 && version.Patch != 0 { + t.Fatalf("bumping major on 1.5.2 resulted in %v", version) + } + + version, _ = NewVersion("1.0.0+build.1-alpha.1") + version.BumpMajor() + if version.PreRelease != "" && version.PreRelease != "" { + t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version) + } +} + +func TestBumpMinor(t *testing.T) { + version, _ := NewVersion("1.0.0") + version.BumpMinor() + + if version.Major != 1 { + t.Fatalf("bumping minor on 1.0.0 resulted in %v", version) + } + + if version.Minor != 1 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + version, _ = NewVersion("1.0.0+build.1-alpha.1") + version.BumpMinor() + if version.PreRelease != "" && version.PreRelease != "" { + t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version) + } +} + +func TestBumpPatch(t *testing.T) { + version, _ := NewVersion("1.0.0") + version.BumpPatch() + + if version.Major != 1 { + t.Fatalf("bumping minor on 1.0.0 resulted in %v", version) + } + + if version.Minor != 0 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + if version.Patch != 1 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + version, _ = NewVersion("1.0.0+build.1-alpha.1") + version.BumpPatch() + if version.PreRelease != "" && version.PreRelease != "" { + t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-semver/semver/sort.go b/Godeps/_workspace/src/github.com/coreos/go-semver/semver/sort.go new file mode 100644 index 0000000..8620300 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-semver/semver/sort.go @@ -0,0 +1,24 @@ +package semver + +import ( + "sort" +) + +type Versions []*Version + +func (s Versions) Len() int { + return len(s) +} + +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s Versions) Less(i, j int) bool { + return s[i].LessThan(*s[j]) +} + +// Sort sorts the given slice of Version +func Sort(versions []*Version) { + sort.Sort(Versions(versions)) +} diff --git a/Godeps/_workspace/src/github.com/coreos/ioprogress/LICENSE b/Godeps/_workspace/src/github.com/coreos/ioprogress/LICENSE new file mode 100644 index 0000000..2298515 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ioprogress/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Mitchell Hashimoto + +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. diff --git a/Godeps/_workspace/src/github.com/coreos/ioprogress/README.md b/Godeps/_workspace/src/github.com/coreos/ioprogress/README.md new file mode 100644 index 0000000..3d291e9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ioprogress/README.md @@ -0,0 +1,42 @@ +# ioprogress + +ioprogress is a Go (golang) library with implementations of `io.Reader` +and `io.Writer` that draws progress bars. The primary use case for these +are for CLI applications but alternate progress bar writers can be supplied +for alternate environments. + +## Example + +![Progress](http://g.recordit.co/GO5HxT16QH.gif) + +## Installation + +Standard `go get`: + +``` +$ go get github.com/mitchellh/ioprogress +``` + +## Usage + +Here is an example of outputting a basic progress bar to the CLI as +we're "downloading" from some other `io.Reader` (perhaps from a network +connection): + +```go +// Imagine this came from some external source, such as a network connection, +// and that we have the full size of it, such as from a Content-Length HTTP +// header. +var r io.Reader + +// Create the progress reader +progressR := &ioprogress.Reader{ + Reader: r, + Size: rSize, +} + +// Copy all of the reader to some local file f. As it copies, the +// progressR will write progress to the terminal on os.Stdout. This is +// customizable. +io.Copy(f, progressR) +``` diff --git a/Godeps/_workspace/src/github.com/coreos/ioprogress/draw.go b/Godeps/_workspace/src/github.com/coreos/ioprogress/draw.go new file mode 100644 index 0000000..3cf4243 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ioprogress/draw.go @@ -0,0 +1,135 @@ +package ioprogress + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/appc/acpush/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal" +) + +// DrawFunc is the callback type for drawing progress. +type DrawFunc func(int64, int64) error + +// DrawTextFormatFunc is a callback used by DrawFuncs that draw text in +// order to format the text into some more human friendly format. +type DrawTextFormatFunc func(int64, int64) string + +var defaultDrawFunc DrawFunc + +func init() { + defaultDrawFunc = DrawTerminal(os.Stdout) +} + +// isTerminal returns True when w is going to a tty, and false otherwise. +func isTerminal(w io.Writer) bool { + if f, ok := w.(*os.File); ok { + return terminal.IsTerminal(int(f.Fd())) + } + return false +} + +// DrawTerminal returns a DrawFunc that draws a progress bar to an io.Writer +// that is assumed to be a terminal (and therefore respects carriage returns). +func DrawTerminal(w io.Writer) DrawFunc { + return DrawTerminalf(w, func(progress, total int64) string { + return fmt.Sprintf("%d/%d", progress, total) + }) +} + +// DrawTerminalf returns a DrawFunc that draws a progress bar to an io.Writer +// that is formatted with the given formatting function. +func DrawTerminalf(w io.Writer, f DrawTextFormatFunc) DrawFunc { + var maxLength int + + return func(progress, total int64) error { + if progress == -1 && total == -1 { + _, err := fmt.Fprintf(w, "\n") + return err + } + + // Make sure we pad it to the max length we've ever drawn so that + // we don't have trailing characters. + line := f(progress, total) + if len(line) < maxLength { + line = fmt.Sprintf( + "%s%s", + line, + strings.Repeat(" ", maxLength-len(line))) + } + maxLength = len(line) + + terminate := "\r" + if !isTerminal(w) { + terminate = "\n" + } + _, err := fmt.Fprint(w, line+terminate) + return err + } +} + +var byteUnits = []string{"B", "KB", "MB", "GB", "TB", "PB"} + +// DrawTextFormatBytes is a DrawTextFormatFunc that formats the progress +// and total into human-friendly byte formats. +func DrawTextFormatBytes(progress, total int64) string { + return fmt.Sprintf("%s/%s", ByteUnitStr(progress), ByteUnitStr(total)) +} + +// DrawTextFormatBar returns a DrawTextFormatFunc that draws a progress +// bar with the given width (in characters). This can be used in conjunction +// with another DrawTextFormatFunc to create a progress bar with bytes, for +// example: +// +// bar := DrawTextFormatBar(20) +// func(progress, total int64) string { +// return fmt.Sprintf( +// "%s %s", +// bar(progress, total), +// DrawTextFormatBytes(progress, total)) +// } +// +func DrawTextFormatBar(width int64) DrawTextFormatFunc { + return DrawTextFormatBarForW(width, nil) +} + +// DrawTextFormatBarForW returns a DrawTextFormatFunc as described in the docs +// for DrawTextFormatBar, however if the io.Writer passed in is not a tty then +// the returned function will always return "". +func DrawTextFormatBarForW(width int64, w io.Writer) DrawTextFormatFunc { + if w != nil && !isTerminal(w) { + return func(progress, total int64) string { + return "" + } + } + + width -= 2 + + return func(progress, total int64) string { + current := int64((float64(progress) / float64(total)) * float64(width)) + if current < 0 || current > width { + return fmt.Sprintf("[%s]", strings.Repeat(" ", int(width))) + } + return fmt.Sprintf( + "[%s%s]", + strings.Repeat("=", int(current)), + strings.Repeat(" ", int(width-current))) + } +} + +// ByteUnitStr pretty prints a number of bytes. +func ByteUnitStr(n int64) string { + var unit string + size := float64(n) + for i := 1; i < len(byteUnits); i++ { + if size < 1000 { + unit = byteUnits[i-1] + break + } + + size = size / 1000 + } + + return fmt.Sprintf("%.3g %s", size, unit) +} diff --git a/Godeps/_workspace/src/github.com/coreos/ioprogress/draw_test.go b/Godeps/_workspace/src/github.com/coreos/ioprogress/draw_test.go new file mode 100644 index 0000000..609257f --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ioprogress/draw_test.go @@ -0,0 +1,90 @@ +package ioprogress + +import ( + "bytes" + "testing" +) + +func TestDrawTerminal(t *testing.T) { + var buf bytes.Buffer + fn := DrawTerminal(&buf) + if err := fn(0, 100); err != nil { + t.Fatalf("err: %s", err) + } + if err := fn(20, 100); err != nil { + t.Fatalf("err: %s", err) + } + if err := fn(-1, -1); err != nil { + t.Fatalf("err: %s", err) + } + + if buf.String() != drawTerminalStr { + t.Fatalf("bad:\n\n%#v", buf.String()) + } +} + +func TestDrawTextFormatBar(t *testing.T) { + var actual, expected string + f := DrawTextFormatBar(10) + + actual = f(5, 10) + expected = "[==== ]" + if actual != expected { + t.Fatalf("bad: %s", actual) + } + + actual = f(2, 10) + expected = "[= ]" + if actual != expected { + t.Fatalf("bad: %s", actual) + } + + actual = f(10, 10) + expected = "[========]" + if actual != expected { + t.Fatalf("bad: %s", actual) + } + + actual = f(0, 0) + expected = "[ ]" + if actual != expected { + t.Fatalf("bad: %s", actual) + } + + actual = f(-10, 10) + expected = "[ ]" + if actual != expected { + t.Fatalf("bad: %s", actual) + } + + actual = f(20, 10) + expected = "[ ]" + if actual != expected { + t.Fatalf("bad: %s", actual) + } +} + +func TestDrawTextFormatBytes(t *testing.T) { + cases := []struct { + P, T int64 + Output string + }{ + { + 100, 500, + "100 B/500 B", + }, + { + 1500, 5000, + "1.5 KB/5 KB", + }, + } + + for _, tc := range cases { + output := DrawTextFormatBytes(tc.P, tc.T) + if output != tc.Output { + t.Fatalf("Input: %d, %d\n\n%s", tc.P, tc.T, output) + } + } +} + +const drawTerminalStr = "0/100\n20/100\n\n" diff --git a/Godeps/_workspace/src/github.com/coreos/ioprogress/reader.go b/Godeps/_workspace/src/github.com/coreos/ioprogress/reader.go new file mode 100644 index 0000000..7d52731 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ioprogress/reader.go @@ -0,0 +1,107 @@ +package ioprogress + +import ( + "io" + "time" +) + +// Reader is an implementation of io.Reader that draws the progress of +// reading some data. +type Reader struct { + // Reader is the underlying reader to read from + Reader io.Reader + + // Size is the total size of the data coming out of the reader. + Size int64 + + // DrawFunc is the callback to invoke to draw the progress bar. By + // default, this will be DrawTerminal(os.Stdout). + // + // DrawInterval is the minimum time to wait between reads to update the + // progress bar. + DrawFunc DrawFunc + DrawInterval time.Duration + + progress int64 + lastDraw time.Time +} + +// Read reads from the underlying reader and invokes the DrawFunc if +// appropriate. The DrawFunc is executed when there is data that is +// read (progress is made) and at least DrawInterval time has passed. +func (r *Reader) Read(p []byte) (int, error) { + // If we haven't drawn before, initialize the progress bar + if r.lastDraw.IsZero() { + r.initProgress() + } + + // Read from the underlying source + n, err := r.Reader.Read(p) + + // Always increment the progress even if there was an error + r.progress += int64(n) + + // If we don't have any errors, then draw the progress. If we are + // at the end of the data, then finish the progress. + if err == nil { + // Only draw if we read data or we've never read data before (to + // initialize the progress bar). + if n > 0 { + r.drawProgress() + } + } + if err == io.EOF { + r.finishProgress() + } + + return n, err +} + +func (r *Reader) drawProgress() { + // If we've drawn before, then make sure that the draw interval + // has passed before we draw again. + interval := r.DrawInterval + if interval == 0 { + interval = time.Second + } + if !r.lastDraw.IsZero() { + nextDraw := r.lastDraw.Add(interval) + if time.Now().Before(nextDraw) { + return + } + } + + // Draw + f := r.drawFunc() + f(r.progress, r.Size) + + // Record this draw so that we don't draw again really quickly + r.lastDraw = time.Now() +} + +func (r *Reader) finishProgress() { + f := r.drawFunc() + f(r.progress, r.Size) + + // Print a newline + f(-1, -1) + + // Reset lastDraw so we don't finish again + var zeroDraw time.Time + r.lastDraw = zeroDraw +} + +func (r *Reader) initProgress() { + var zeroDraw time.Time + r.lastDraw = zeroDraw + r.drawProgress() + r.lastDraw = zeroDraw +} + +func (r *Reader) drawFunc() DrawFunc { + if r.DrawFunc == nil { + return defaultDrawFunc + } + + return r.DrawFunc +} diff --git a/Godeps/_workspace/src/github.com/coreos/ioprogress/reader_test.go b/Godeps/_workspace/src/github.com/coreos/ioprogress/reader_test.go new file mode 100644 index 0000000..ac15944 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ioprogress/reader_test.go @@ -0,0 +1,62 @@ +package ioprogress + +import ( + "bytes" + "io" + "io/ioutil" + "testing" + "time" +) + +func TestReader(t *testing.T) { + testR := &testReader{ + Data: [][]byte{ + []byte("ab"), + []byte("cd"), + []byte("ef"), + }, + } + + var buf bytes.Buffer + r := &Reader{ + Reader: testR, + Size: testR.Size(), + DrawFunc: DrawTerminal(&buf), + DrawInterval: time.Microsecond, + } + io.Copy(ioutil.Discard, r) + + if buf.String() != drawReaderStr { + t.Fatalf("bad:\n\n%#v", buf.String()) + } +} + +const drawReaderStr = "0/6\n2/6\n4/6\n6/6\n6/6\n\n" + +// testReader is a test structure to help with testing the Reader by +// returning fixed slices of data. +type testReader struct { + Data [][]byte + i int +} + +func (r *testReader) Read(p []byte) (int, error) { + // This is just so that our interval will fire properly + time.Sleep(5 * time.Microsecond) + + if r.i == len(r.Data) { + return 0, io.EOF + } + + copy(p, r.Data[r.i]) + r.i += 1 + return len(r.Data[r.i-1]), nil +} + +func (r *testReader) Size() (n int64) { + for _, d := range r.Data { + n += int64(len(d)) + } + + return +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/common/apps/apps.go b/Godeps/_workspace/src/github.com/coreos/rkt/common/apps/apps.go new file mode 100644 index 0000000..82eedcc --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/common/apps/apps.go @@ -0,0 +1,103 @@ +// Copyright 2015 The rkt Authors +// +// 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. + +//+build linux + +package apps + +import ( + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +type App struct { + Image string // the image reference as supplied by the user on the cli + Args []string // any arguments the user supplied for this app + Asc string // signature file override for image verification (if fetching occurs) + Exec string // exec override for image + + // TODO(jonboulle): These images are partially-populated hashes, this should be clarified. + ImageID types.Hash // resolved image identifier +} + +type Apps struct { + apps []App +} + +// Reset creates a new slice for al.apps, needed by tests +func (al *Apps) Reset() { + al.apps = make([]App, 0) +} + +// Count returns the number of apps in al +func (al *Apps) Count() int { + return len(al.apps) +} + +// Create creates a new app in al and returns a pointer to it +func (al *Apps) Create(img string) { + al.apps = append(al.apps, App{Image: img}) +} + +// Last returns a pointer to the top app in al +func (al *Apps) Last() *App { + if len(al.apps) == 0 { + return nil + } + return &al.apps[len(al.apps)-1] +} + +// Walk iterates on al.apps calling f for each app +// walking stops if f returns an error, the error is simply returned +func (al *Apps) Walk(f func(*App) error) error { + for i, _ := range al.apps { + // XXX(vc): note we supply f() with a pointer to the app instance in al.apps to enable modification by f() + if err := f(&al.apps[i]); err != nil { + return err + } + } + return nil +} + +// these convenience functions just return typed lists containing just the named member +// TODO(vc): these probably go away when we just pass Apps to stage0 + +// GetImages returns a list of the images in al, one per app. +// The order reflects the app order in al. +func (al *Apps) GetImages() []string { + il := []string{} + for _, a := range al.apps { + il = append(il, a.Image) + } + return il +} + +// GetArgs returns a list of lists of arguments in al, one list of args per app. +// The order reflects the app order in al. +func (al *Apps) GetArgs() [][]string { + aal := [][]string{} + for _, a := range al.apps { + aal = append(aal, a.Args) + } + return aal +} + +// GetImageIDs returns a list of the imageIDs in al, one per app. +// The order reflects the app order in al. +func (al *Apps) GetImageIDs() []types.Hash { + hl := []types.Hash{} + for _, a := range al.apps { + hl = append(hl, a.ImageID) + } + return hl +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup/cgroup.go b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup/cgroup.go new file mode 100644 index 0000000..ef4897d --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup/cgroup.go @@ -0,0 +1,412 @@ +// Copyright 2015 The rkt Authors +// +// 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. + +//+build linux + +package cgroup + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + + "github.com/appc/acpush/Godeps/_workspace/src/k8s.io/kubernetes/pkg/api/resource" + "github.com/coreos/go-systemd/unit" +) + +type addIsolatorFunc func(opts []*unit.UnitOption, limit *resource.Quantity) ([]*unit.UnitOption, error) + +var ( + isolatorFuncs = map[string]addIsolatorFunc{ + "cpu": addCpuLimit, + "memory": addMemoryLimit, + } + cgroupControllerRWFiles = map[string][]string{ + "memory": []string{"memory.limit_in_bytes"}, + "cpu": []string{"cpu.cfs_quota_us"}, + } +) + +func addCpuLimit(opts []*unit.UnitOption, limit *resource.Quantity) ([]*unit.UnitOption, error) { + if limit.Value() > resource.MaxMilliValue { + return nil, fmt.Errorf("cpu limit exceeds the maximum millivalue: %v", limit.String()) + } + quota := strconv.Itoa(int(limit.MilliValue()/10)) + "%" + opts = append(opts, unit.NewUnitOption("Service", "CPUQuota", quota)) + return opts, nil +} + +func addMemoryLimit(opts []*unit.UnitOption, limit *resource.Quantity) ([]*unit.UnitOption, error) { + opts = append(opts, unit.NewUnitOption("Service", "MemoryLimit", strconv.Itoa(int(limit.Value())))) + return opts, nil +} + +// MaybeAddIsolator considers the given isolator; if the type is known +// (i.e. IsIsolatorSupported is true) and the limit is non-nil, the supplied +// opts will be extended with an appropriate option implementing the desired +// isolation. +func MaybeAddIsolator(opts []*unit.UnitOption, isolator string, limit *resource.Quantity) ([]*unit.UnitOption, error) { + var err error + if limit == nil { + return opts, nil + } + if IsIsolatorSupported(isolator) { + opts, err = isolatorFuncs[isolator](opts, limit) + if err != nil { + return nil, err + } + } else { + fmt.Fprintf(os.Stderr, "warning: resource/%s isolator set but support disabled in the kernel, skipping\n", isolator) + } + return opts, nil +} + +// IsIsolatorSupported returns whether an isolator is supported in the kernel +func IsIsolatorSupported(isolator string) bool { + if files, ok := cgroupControllerRWFiles[isolator]; ok { + for _, f := range files { + isolatorPath := filepath.Join("/sys/fs/cgroup/", isolator, f) + if _, err := os.Stat(isolatorPath); os.IsNotExist(err) { + return false + } + } + return true + } + return false +} + +func parseCgroups(f io.Reader) (map[int][]string, error) { + sc := bufio.NewScanner(f) + + // skip first line since it is a comment + sc.Scan() + + cgroups := make(map[int][]string) + for sc.Scan() { + var controller string + var hierarchy int + var num int + var enabled int + fmt.Sscanf(sc.Text(), "%s %d %d %d", &controller, &hierarchy, &num, &enabled) + + if enabled == 1 { + if _, ok := cgroups[hierarchy]; !ok { + cgroups[hierarchy] = []string{controller} + } else { + cgroups[hierarchy] = append(cgroups[hierarchy], controller) + } + } + } + + if err := sc.Err(); err != nil { + return nil, err + } + + return cgroups, nil +} + +// GetEnabledCgroups returns a map with the enabled cgroup controllers grouped by +// hierarchy +func GetEnabledCgroups() (map[int][]string, error) { + cgroupsFile, err := os.Open("/proc/cgroups") + if err != nil { + return nil, err + } + defer cgroupsFile.Close() + + cgroups, err := parseCgroups(cgroupsFile) + if err != nil { + return nil, fmt.Errorf("error parsing /proc/cgroups: %v", err) + } + + return cgroups, nil +} + +// GetControllerDirs takes a map with the enabled cgroup controllers grouped by +// hierarchy and returns the directory names as they should be in +// /sys/fs/cgroup +func GetControllerDirs(cgroups map[int][]string) []string { + var controllers []string + for _, cs := range cgroups { + controllers = append(controllers, strings.Join(cs, ",")) + } + + return controllers +} + +func getControllerSymlinks(cgroups map[int][]string) map[string]string { + symlinks := make(map[string]string) + + for _, cs := range cgroups { + if len(cs) > 1 { + tgt := strings.Join(cs, ",") + for _, ln := range cs { + symlinks[ln] = tgt + } + } + } + + return symlinks +} + +func getControllerRWFiles(controller string) []string { + parts := strings.Split(controller, ",") + for _, p := range parts { + if files, ok := cgroupControllerRWFiles[p]; ok { + // cgroup.procs always needs to be RW for allowing systemd to add + // processes to the controller + files = append(files, "cgroup.procs") + return files + } + } + + return nil +} + +func parseOwnCgroupController(controller string) ([]string, error) { + cgroupPath := "/proc/self/cgroup" + cg, err := os.Open(cgroupPath) + if err != nil { + return nil, fmt.Errorf("error opening /proc/self/cgroup: %v", err) + } + defer cg.Close() + + s := bufio.NewScanner(cg) + for s.Scan() { + parts := strings.SplitN(s.Text(), ":", 3) + if len(parts) < 3 { + return nil, fmt.Errorf("error parsing /proc/self/cgroup") + } + controllerParts := strings.Split(parts[1], ",") + for _, c := range controllerParts { + if c == controller { + return parts, nil + } + } + } + + return nil, fmt.Errorf("controller %q not found", controller) +} + +// GetOwnCgroupPath returns the cgroup path of this process in controller +// hierarchy +func GetOwnCgroupPath(controller string) (string, error) { + parts, err := parseOwnCgroupController(controller) + if err != nil { + return "", err + } + return parts[2], nil +} + +// JoinCgroup makes the calling process join the subcgroup hierarchy on a +// particular controller +func JoinSubcgroup(controller string, subcgroup string) error { + subcgroupPath := filepath.Join("/sys/fs/cgroup", controller, subcgroup) + if err := os.MkdirAll(subcgroupPath, 0600); err != nil { + return fmt.Errorf("error creating %q subcgroup: %v", subcgroup, err) + } + pidBytes := []byte(strconv.Itoa(os.Getpid())) + if err := ioutil.WriteFile(filepath.Join(subcgroupPath, "cgroup.procs"), pidBytes, 0600); err != nil { + return fmt.Errorf("error adding ourselves to the %q subcgroup: %v", subcgroup, err) + } + + return nil +} + +// If /system.slice does not exist in the cpuset controller, create it and +// configure it. +// Since this is a workaround, we ignore errors +func fixCpusetKnobs(cpusetPath string) { + cgroupPathFix := filepath.Join(cpusetPath, "system.slice") + _ = os.MkdirAll(cgroupPathFix, 0755) + knobs := []string{"cpuset.mems", "cpuset.cpus"} + for _, knob := range knobs { + parentFile := filepath.Join(filepath.Dir(cgroupPathFix), knob) + childFile := filepath.Join(cgroupPathFix, knob) + + data, err := ioutil.ReadFile(childFile) + if err != nil { + continue + } + // If the file is already configured, don't change it + if strings.TrimSpace(string(data)) != "" { + continue + } + + data, err = ioutil.ReadFile(parentFile) + if err == nil { + // Workaround: just write twice to workaround the kernel bug fixed by this commit: + // https://github.com/torvalds/linux/commit/24ee3cf89bef04e8bc23788aca4e029a3f0f06d9 + ioutil.WriteFile(childFile, data, 0644) + ioutil.WriteFile(childFile, data, 0644) + } + } +} + +// IsControllerMounted returns whether a controller is mounted by checking that +// cgroup.procs is accessible +func IsControllerMounted(c string) bool { + cgroupProcsPath := filepath.Join("/sys/fs/cgroup", c, "cgroup.procs") + if _, err := os.Stat(cgroupProcsPath); err != nil { + return false + } + + return true +} + +// CreateCgroups mounts the cgroup controllers hierarchy in /sys/fs/cgroup +// under root +func CreateCgroups(root string, enabledCgroups map[int][]string) error { + controllers := GetControllerDirs(enabledCgroups) + var flags uintptr + + sys := filepath.Join(root, "/sys") + if err := os.MkdirAll(sys, 0700); err != nil { + return err + } + flags = syscall.MS_NOSUID | + syscall.MS_NOEXEC | + syscall.MS_NODEV + // If we're mounting the host cgroups, /sys is probably mounted so we + // ignore EBUSY + if err := syscall.Mount("sysfs", sys, "sysfs", flags, ""); err != nil && err != syscall.EBUSY { + return fmt.Errorf("error mounting %q: %v", sys, err) + } + + cgroupTmpfs := filepath.Join(root, "/sys/fs/cgroup") + if err := os.MkdirAll(cgroupTmpfs, 0700); err != nil { + return err + } + flags = syscall.MS_NOSUID | + syscall.MS_NOEXEC | + syscall.MS_NODEV | + syscall.MS_STRICTATIME + if err := syscall.Mount("tmpfs", cgroupTmpfs, "tmpfs", flags, "mode=755"); err != nil { + return fmt.Errorf("error mounting %q: %v", cgroupTmpfs, err) + } + + // Mount controllers + for _, c := range controllers { + cPath := filepath.Join(root, "/sys/fs/cgroup", c) + if err := os.MkdirAll(cPath, 0700); err != nil { + return err + } + + flags = syscall.MS_NOSUID | + syscall.MS_NOEXEC | + syscall.MS_NODEV + if err := syscall.Mount("cgroup", cPath, "cgroup", flags, c); err != nil { + return fmt.Errorf("error mounting %q: %v", cPath, err) + } + } + + // Create symlinks for combined controllers + symlinks := getControllerSymlinks(enabledCgroups) + for ln, tgt := range symlinks { + lnPath := filepath.Join(cgroupTmpfs, ln) + if err := os.Symlink(tgt, lnPath); err != nil { + return fmt.Errorf("error creating symlink: %v", err) + } + } + + systemdControllerPath := filepath.Join(root, "/sys/fs/cgroup/systemd") + if err := os.MkdirAll(systemdControllerPath, 0700); err != nil { + return err + } + + // Bind-mount cgroup tmpfs filesystem read-only + flags = syscall.MS_BIND | + syscall.MS_REMOUNT | + syscall.MS_NOSUID | + syscall.MS_NOEXEC | + syscall.MS_NODEV | + syscall.MS_RDONLY + if err := syscall.Mount(cgroupTmpfs, cgroupTmpfs, "", flags, ""); err != nil { + return fmt.Errorf("error remounting RO %q: %v", cgroupTmpfs, err) + } + + return nil +} + +// RemountCgroupsRO remounts the cgroup hierarchy under root read-only, leaving +// the needed knobs in the subcgroup for each app read-write so the systemd +// inside stage1 can apply isolators to them +func RemountCgroupsRO(root string, enabledCgroups map[int][]string, subcgroup string, serviceNames []string) error { + controllers := GetControllerDirs(enabledCgroups) + cgroupTmpfs := filepath.Join(root, "/sys/fs/cgroup") + sysPath := filepath.Join(root, "/sys") + + var flags uintptr + + // Mount RW knobs we need to make the enabled isolators work + for _, c := range controllers { + cPath := filepath.Join(cgroupTmpfs, c) + subcgroupPath := filepath.Join(cPath, subcgroup) + + // Workaround for https://github.com/coreos/rkt/issues/1210 + if c == "cpuset" { + fixCpusetKnobs(cPath) + } + + // Create cgroup directories and mount the files we need over + // themselves so they stay read-write + for _, serviceName := range serviceNames { + appCgroup := filepath.Join(subcgroupPath, serviceName) + if err := os.MkdirAll(appCgroup, 0755); err != nil { + return err + } + for _, f := range getControllerRWFiles(c) { + cgroupFilePath := filepath.Join(appCgroup, f) + // the file may not be there if kernel doesn't support the + // feature, skip it in that case + if _, err := os.Stat(cgroupFilePath); os.IsNotExist(err) { + continue + } + if err := syscall.Mount(cgroupFilePath, cgroupFilePath, "", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("error bind mounting %q: %v", cgroupFilePath, err) + } + } + } + + // Re-mount controller read-only to prevent the container modifying host controllers + flags = syscall.MS_BIND | + syscall.MS_REMOUNT | + syscall.MS_NOSUID | + syscall.MS_NOEXEC | + syscall.MS_NODEV | + syscall.MS_RDONLY + if err := syscall.Mount(cPath, cPath, "", flags, ""); err != nil { + return fmt.Errorf("error remounting RO %q: %v", cPath, err) + } + } + + // Bind-mount sys filesystem read-only + flags = syscall.MS_BIND | + syscall.MS_REMOUNT | + syscall.MS_NOSUID | + syscall.MS_NOEXEC | + syscall.MS_NODEV | + syscall.MS_RDONLY + if err := syscall.Mount(sysPath, sysPath, "", flags, ""); err != nil { + return fmt.Errorf("error remounting RO %q: %v", sysPath, err) + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup/cgroup_test.go b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup/cgroup_test.go new file mode 100644 index 0000000..55d8dd5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup/cgroup_test.go @@ -0,0 +1,112 @@ +// Copyright 2015 The rkt Authors +// +// 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. + +//+build linux + +package cgroup + +import ( + "io" + "reflect" + "strings" + "testing" +) + +func TestParseCgroups(t *testing.T) { + cg1 := `#subsys_name hierarchy num_cgroups enabled +cpuset 2 1 1 +cpu 3 1 1 +cpuacct 3 1 1 +blkio 4 1 1 +memory 6 1 1 +devices 7 47 1 +freezer 8 1 1 +net_cls 5 1 1` + + cg2 := `#subsys_name hierarchy num_cgroups enabled +cpuset 8 441 1 +cpu 4 31 1 +cpuacct 4 31 1 +blkio 2 13 1 +memory 0 1 0 +devices 3 88 1 +freezer 7 432 1 +net_cls 6 432 1 +perf_event 5 432 1 +net_prio 6 432 1` + + cg3 := `#subsys_name hierarchy num_cgroups enabled +cpuset 1 441 1 +cpu 4 31 1 +cpuacct 4 31 0 +blkio 2 13 1 +memory 0 1 0 +devices 3 88 1 +freezer 7 432 1 +net_cls 6 432 1 +perf_event 5 432 0 +net_prio 6 432 1` + + tests := []struct { + input io.Reader + output map[int][]string + }{ + { + input: strings.NewReader(cg1), + output: map[int][]string{ + 2: []string{"cpuset"}, + 3: []string{"cpu", "cpuacct"}, + 4: []string{"blkio"}, + 6: []string{"memory"}, + 7: []string{"devices"}, + 8: []string{"freezer"}, + 5: []string{"net_cls"}, + }, + }, + { + input: strings.NewReader(cg2), + output: map[int][]string{ + 8: []string{"cpuset"}, + 4: []string{"cpu", "cpuacct"}, + 2: []string{"blkio"}, + 3: []string{"devices"}, + 7: []string{"freezer"}, + 6: []string{"net_cls", "net_prio"}, + 5: []string{"perf_event"}, + }, + }, + { + input: strings.NewReader(cg3), + output: map[int][]string{ + 1: []string{"cpuset"}, + 4: []string{"cpu"}, + 2: []string{"blkio"}, + 3: []string{"devices"}, + 7: []string{"freezer"}, + 6: []string{"net_cls", "net_prio"}, + }, + }, + } + + for i, tt := range tests { + o, err := parseCgroups(tt.input) + if err != nil { + t.Errorf("#%d: unexpected error `%v`", i, err) + } + eq := reflect.DeepEqual(o, tt.output) + if !eq { + t.Errorf("#%d: expected `%v` got `%v`", i, tt.output, o) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup_util.go b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup_util.go new file mode 100644 index 0000000..7a00276 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup_util.go @@ -0,0 +1,183 @@ +// Copyright 2014 The rkt Authors +// +// 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. + +//+build linux + +package common + +// adapted from systemd/src/shared/cgroup-util.c +// TODO this should be moved to go-systemd + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" +) + +const ( + unitNameMax = 256 +) + +var ( + validChars = regexp.MustCompile(`[a-zA-Z0-9:\-_\.\\]+`) +) + +// cgEscape implements very minimal escaping for names to be used as file names +// in the cgroup tree: any name which might conflict with a kernel name or is +// prefixed with '_' is prefixed with a '_'. That way, when reading cgroup +// names it is sufficient to remove a single prefixing underscore if there is +// one. +func cgEscape(p string) string { + needPrefix := false + + switch { + case strings.HasPrefix(p, "_"): + fallthrough + case strings.HasPrefix(p, "."): + fallthrough + case p == "notify_on_release": + fallthrough + case p == "release_agent": + fallthrough + case p == "tasks": + needPrefix = true + case strings.Contains(p, "."): + sp := strings.Split(p, ".") + if sp[0] == "cgroup" { + needPrefix = true + } else { + n := sp[0] + if checkHierarchy(n) { + needPrefix = true + } + } + } + + if needPrefix { + return "_" + p + } + + return p +} + +func filenameIsValid(p string) bool { + switch { + case p == "", p == ".", p == "..", strings.Contains(p, "/"): + return false + default: + return true + } +} + +func checkHierarchy(p string) bool { + if !filenameIsValid(p) { + return true + } + + cc := filepath.Join("/sys/fs/cgroup", p) + if _, err := os.Stat(cc); os.IsNotExist(err) { + return false + } + + return true +} + +func cgUnescape(p string) string { + if p[0] == '_' { + return p[1:] + } + + return p +} + +func sliceNameIsValid(n string) bool { + if n == "" { + return false + } + + if len(n) >= unitNameMax { + return false + } + + if !strings.Contains(n, ".") { + return false + } + + if validChars.FindString(n) != n { + return false + } + + if strings.Contains(n, "@") { + return false + } + + return true +} + +// SliceToPath explodes a slice name to its corresponding path in the cgroup +// hierarchy. For example, a slice named "foo-bar-baz.slice" corresponds to the +// path "foo.slice/foo-bar.slice/foo-bar-baz.slice". See systemd.slice(5) +func SliceToPath(unit string) (string, error) { + if unit == "-.slice" { + return "", nil + } + + if !strings.HasSuffix(unit, ".slice") { + return "", fmt.Errorf("not a slice") + } + + if !sliceNameIsValid(unit) { + return "", fmt.Errorf("invalid slice name") + } + + prefix := unitnameToPrefix(unit) + + // don't allow initial dashes + if prefix[0] == '-' { + return "", fmt.Errorf("initial dash") + } + + prefixParts := strings.Split(prefix, "-") + + var curSlice string + var slicePath string + for _, slicePart := range prefixParts { + if slicePart == "" { + return "", fmt.Errorf("trailing or double dash") + } + + if curSlice != "" { + curSlice = curSlice + "-" + } + curSlice = curSlice + slicePart + + curSliceDir := curSlice + ".slice" + escaped := cgEscape(curSliceDir) + + slicePath = filepath.Join(slicePath, escaped) + } + + return slicePath, nil +} + +func unitnameToPrefix(unit string) string { + idx := strings.Index(unit, "@") + if idx == -1 { + idx = strings.LastIndex(unit, ".") + } + + return unit[:idx] +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup_util_test.go b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup_util_test.go new file mode 100644 index 0000000..c24e23e --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/common/cgroup_util_test.go @@ -0,0 +1,133 @@ +// Copyright 2015 The rkt Authors +// +// 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. + +//+build linux + +package common + +import ( + "testing" +) + +func TestCgEscape(t *testing.T) { + tests := []struct { + input string + output string + }{ + { + input: "", + output: "", + }, + { + input: "_abc", + output: "__abc", + }, + { + input: ".abc", + output: "_.abc", + }, + { + input: "notify_on_release", + output: "_notify_on_release", + }, + { + input: "tasks", + output: "_tasks", + }, + { + input: "cgroup.abc.slice", + output: "_cgroup.abc.slice", + }, + { + input: "abc.mount", + output: "abc.mount", + }, + { + input: "a/bc.mount", + output: "_a/bc.mount", + }, + } + + for i, tt := range tests { + o := cgEscape(tt.input) + if o != tt.output { + t.Errorf("#%d: expected `%v` got `%v`", i, tt.output, o) + } + } +} + +func TestSliceToPath(t *testing.T) { + tests := []struct { + input string + output string + werr bool + }{ + { + input: "", + output: "", + werr: true, + }, + { + input: "abc.service", + output: "", + werr: true, + }, + { + input: "ab/c.slice", + output: "", + werr: true, + }, + { + input: "-abc.slice", + output: "", + werr: true, + }, + { + input: "ab--c.slice", + output: "", + werr: true, + }, + { + input: "abc-.slice", + output: "", + werr: true, + }, + { + input: "abc.slice", + output: "abc.slice", + werr: false, + }, + { + input: "foo-bar.slice", + output: "foo.slice/foo-bar.slice", + werr: false, + }, + { + input: "foo-bar-baz.slice", + output: "foo.slice/foo-bar.slice/foo-bar-baz.slice", + werr: false, + }, + } + + for i, tt := range tests { + o, err := SliceToPath(tt.input) + gerr := (err != nil) + if o != tt.output { + t.Errorf("#%d: expected `%v` got `%v`", i, tt.output, o) + } + if gerr != tt.werr { + t.Errorf("#%d: gerr=%t, want %t (err=%v)", i, gerr, tt.werr, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/common/common.go b/Godeps/_workspace/src/github.com/coreos/rkt/common/common.go new file mode 100644 index 0000000..22cded7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/common/common.go @@ -0,0 +1,253 @@ +// Copyright 2014 The rkt Authors +// +// 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 common defines values shared by different parts +// of rkt (e.g. stage0 and stage1) +package common + +import ( + "bufio" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/aci" + "github.com/appc/acpush/Godeps/_workspace/src/github.com/appc/spec/schema/types" +) + +const ( + sharedVolumesDir = "/sharedVolumes" + stage1Dir = "/stage1" + stage2Dir = "/opt/stage2" + AppsInfoDir = "/appsinfo" + + EnvLockFd = "RKT_LOCK_FD" + SELinuxContext = "RKT_SELINUX_CONTEXT" + Stage1TreeStoreIDFilename = "stage1TreeStoreID" + AppTreeStoreIDFilename = "treeStoreID" + OverlayPreparedFilename = "overlay-prepared" + PrivateUsersPreparedFilename = "private-users-prepared" + + PrepareLock = "prepareLock" + + MetadataServicePort = 18112 + MetadataServiceRegSock = "/run/rkt/metadata-svc.sock" + + DefaultLocalConfigDir = "/etc/rkt" + DefaultSystemConfigDir = "/usr/lib/rkt" +) + +// Stage1ImagePath returns the path where the stage1 app image (unpacked ACI) is rooted, +// (i.e. where its contents are extracted during stage0). +func Stage1ImagePath(root string) string { + return filepath.Join(root, stage1Dir) +} + +// Stage1RootfsPath returns the path to the stage1 rootfs +func Stage1RootfsPath(root string) string { + return filepath.Join(Stage1ImagePath(root), aci.RootfsDir) +} + +// Stage1ManifestPath returns the path to the stage1's manifest file inside the expanded ACI. +func Stage1ManifestPath(root string) string { + return filepath.Join(Stage1ImagePath(root), aci.ManifestFile) +} + +// PodManifestPath returns the path in root to the Pod Manifest +func PodManifestPath(root string) string { + return filepath.Join(root, "pod") +} + +// AppsPath returns the path where the apps within a pod live. +func AppsPath(root string) string { + return filepath.Join(Stage1RootfsPath(root), stage2Dir) +} + +// AppPath returns the path to an app's rootfs. +func AppPath(root string, appName types.ACName) string { + return filepath.Join(AppsPath(root), appName.String()) +} + +// AppRootfsPath returns the path to an app's rootfs. +func AppRootfsPath(root string, appName types.ACName) string { + return filepath.Join(AppPath(root, appName), aci.RootfsDir) +} + +// RelAppPath returns the path of an app relative to the stage1 chroot. +func RelAppPath(appName types.ACName) string { + return filepath.Join(stage2Dir, appName.String()) +} + +// RelAppRootfsPath returns the path of an app's rootfs relative to the stage1 chroot. +func RelAppRootfsPath(appName types.ACName) string { + return filepath.Join(RelAppPath(appName), aci.RootfsDir) +} + +// ImageManifestPath returns the path to the app's manifest file of a pod. +func ImageManifestPath(root string, appName types.ACName) string { + return filepath.Join(AppPath(root, appName), aci.ManifestFile) +} + +// AppsInfoPath returns the path to the appsinfo directory of a pod. +func AppsInfoPath(root string) string { + return filepath.Join(root, AppsInfoDir) +} + +// AppInfoPath returns the path to the app's appsinfo directory of a pod. +func AppInfoPath(root string, appName types.ACName) string { + return filepath.Join(AppsInfoPath(root), appName.String()) +} + +// AppTreeStoreIDPath returns the path to the app's treeStoreID file of a pod. +func AppTreeStoreIDPath(root string, appName types.ACName) string { + return filepath.Join(AppInfoPath(root, appName), AppTreeStoreIDFilename) +} + +// SharedVolumesPath returns the path to the shared (empty) volumes of a pod. +func SharedVolumesPath(root string) string { + return filepath.Join(root, sharedVolumesDir) +} + +// MetadataServicePublicURL returns the public URL used to host the metadata service +func MetadataServicePublicURL(ip net.IP, token string) string { + return fmt.Sprintf("http://%v:%v/%v", ip, MetadataServicePort, token) +} + +func GetRktLockFD() (int, error) { + if v := os.Getenv(EnvLockFd); v != "" { + fd, err := strconv.ParseUint(v, 10, 32) + if err != nil { + return -1, err + } + return int(fd), nil + } + return -1, fmt.Errorf("%v env var is not set", EnvLockFd) +} + +// SupportsOverlay returns whether the system supports overlay filesystem +func SupportsOverlay() bool { + exec.Command("modprobe", "overlay").Run() + + f, err := os.Open("/proc/filesystems") + if err != nil { + fmt.Println("error opening /proc/filesystems") + return false + } + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + if s.Text() == "nodev\toverlay" { + return true + } + } + return false +} + +// SupportsUserNS returns whether the kernel has CONFIG_USER_NS set +func SupportsUserNS() bool { + if _, err := os.Stat("/proc/self/uid_map"); err == nil { + return true + } + + return false +} + +// PrivateNetList implements the flag.Value interface to allow specification +// of -private-net with and without values +// Example: --private-net="all,net1:k1=v1;k2=v2,net2:l1=w1" +type PrivateNetList struct { + mapping map[string]string +} + +func (l *PrivateNetList) String() string { + return strings.Join(l.Strings(), ",") +} + +func (l *PrivateNetList) Set(value string) error { + if l.mapping == nil { + l.mapping = make(map[string]string) + } + for _, s := range strings.Split(value, ",") { + netArgsPair := strings.Split(s, ":") + netName := netArgsPair[0] + + if netName == "" { + return fmt.Errorf("netname must not be empty") + } + if _, duplicate := l.mapping[netName]; duplicate { + return fmt.Errorf("found duplicate netname %q", netName) + } + + switch { + case len(netArgsPair) == 1: + l.mapping[netName] = "" + case len(netArgsPair) == 2: + if netName == "all" { + return fmt.Errorf("arguments are not supported by special netname %q", netName) + } + l.mapping[netName] = netArgsPair[1] + case len(netArgsPair) > 2: + return fmt.Errorf("network %q provided with invalid arguments: %v", netName, netArgsPair[1:]) + default: + return fmt.Errorf("unexpected case when processing network %q", s) + } + } + return nil +} + +func (l *PrivateNetList) Type() string { + return "privateNetList" +} + +func (l *PrivateNetList) Strings() []string { + var list []string + for k, v := range l.mapping { + if v == "" { + list = append(list, k) + } else { + list = append(list, fmt.Sprintf("%s:%s", k, v)) + } + } + return list +} + +func (l *PrivateNetList) StringsOnlyNames() []string { + var list []string + for k, _ := range l.mapping { + list = append(list, k) + } + return list +} + +func (l *PrivateNetList) Any() bool { + return len(l.mapping) > 0 +} + +func (l *PrivateNetList) Specific(net string) bool { + _, exists := l.mapping[net] + return exists +} + +func (l *PrivateNetList) SpecificArgs(net string) string { + return l.mapping[net] +} + +func (l *PrivateNetList) All() bool { + return l.Specific("all") +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/auth.go b/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/auth.go new file mode 100644 index 0000000..c0cce1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/auth.go @@ -0,0 +1,180 @@ +// Copyright 2015 The rkt Authors +// +// 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 config + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" +) + +const ( + authHeader string = "Authorization" +) + +type authV1JsonParser struct{} + +type authV1 struct { + Domains []string `json:"domains"` + Type string `json:"type"` + Credentials json.RawMessage `json:"credentials"` +} + +type basicV1 struct { + User string `json:"user"` + Password string `json:"password"` +} + +type oauthV1 struct { + Token string `json:"token"` +} + +type dockerAuthV1JsonParser struct{} + +type dockerAuthV1 struct { + Registries []string `json:"registries"` + Credentials basicV1 `json:"credentials"` +} + +func init() { + addParser("auth", "v1", &authV1JsonParser{}) + addParser("dockerAuth", "v1", &dockerAuthV1JsonParser{}) + registerSubDir("auth.d", []string{"auth", "dockerAuth"}) +} + +type basicAuthHeaderer struct { + user string + password string +} + +func (h *basicAuthHeaderer) Header() http.Header { + headers := make(http.Header) + creds := []byte(fmt.Sprintf("%s:%s", h.user, h.password)) + encodedCreds := base64.StdEncoding.EncodeToString(creds) + headers.Add(authHeader, "Basic "+encodedCreds) + + return headers +} + +type oAuthBearerTokenHeaderer struct { + token string +} + +func (h *oAuthBearerTokenHeaderer) Header() http.Header { + headers := make(http.Header) + headers.Add(authHeader, "Bearer "+h.token) + + return headers +} + +func (p *authV1JsonParser) parse(config *Config, raw []byte) error { + var auth authV1 + if err := json.Unmarshal(raw, &auth); err != nil { + return err + } + if len(auth.Domains) == 0 { + return fmt.Errorf("no domains specified") + } + if len(auth.Type) == 0 { + return fmt.Errorf("no auth type specified") + } + var ( + err error + headerer Headerer + ) + switch auth.Type { + case "basic": + headerer, err = p.getBasicV1Headerer(auth.Credentials) + case "oauth": + headerer, err = p.getOAuthV1Headerer(auth.Credentials) + default: + err = fmt.Errorf("unknown auth type: %q", auth.Type) + } + if err != nil { + return err + } + for _, domain := range auth.Domains { + if _, ok := config.AuthPerHost[domain]; ok { + return fmt.Errorf("auth for domain %q is already specified", domain) + } + config.AuthPerHost[domain] = headerer + } + return nil +} + +func (p *authV1JsonParser) getBasicV1Headerer(raw json.RawMessage) (Headerer, error) { + var basic basicV1 + if err := json.Unmarshal(raw, &basic); err != nil { + return nil, err + } + if err := validateBasicV1(&basic); err != nil { + return nil, err + } + return &basicAuthHeaderer{ + user: basic.User, + password: basic.Password, + }, nil +} + +func (p *authV1JsonParser) getOAuthV1Headerer(raw json.RawMessage) (Headerer, error) { + var oauth oauthV1 + if err := json.Unmarshal(raw, &oauth); err != nil { + return nil, err + } + if len(oauth.Token) == 0 { + return nil, fmt.Errorf("no oauth bearer token specified") + } + return &oAuthBearerTokenHeaderer{ + token: oauth.Token, + }, nil +} + +func (p *dockerAuthV1JsonParser) parse(config *Config, raw []byte) error { + var auth dockerAuthV1 + if err := json.Unmarshal(raw, &auth); err != nil { + return err + } + if len(auth.Registries) == 0 { + return fmt.Errorf("no registries specified") + } + if err := validateBasicV1(&auth.Credentials); err != nil { + return err + } + basic := BasicCredentials{ + User: auth.Credentials.User, + Password: auth.Credentials.Password, + } + for _, registry := range auth.Registries { + if _, ok := config.DockerCredentialsPerRegistry[registry]; ok { + return fmt.Errorf("credentials for docker registry %q are already specified", registry) + } + config.DockerCredentialsPerRegistry[registry] = basic + } + return nil +} + +func validateBasicV1(basic *basicV1) error { + if basic == nil { + return fmt.Errorf("no credentials") + } + if len(basic.User) == 0 { + return fmt.Errorf("user not specified") + } + if len(basic.Password) == 0 { + return fmt.Errorf("password not specified") + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/config.go b/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/config.go new file mode 100644 index 0000000..d979aa0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/config.go @@ -0,0 +1,282 @@ +// Copyright 2015 The rkt Authors +// +// 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 config + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/appc/acpush/Godeps/_workspace/src/github.com/coreos/rkt/common" +) + +// Headerer is an interface for getting additional HTTP headers to use +// when downloading data (images, signatures). +type Headerer interface { + Header() http.Header +} + +type BasicCredentials struct { + User string + Password string +} + +// Config is a single place where configuration for rkt frontend needs +// resides. +type Config struct { + AuthPerHost map[string]Headerer + DockerCredentialsPerRegistry map[string]BasicCredentials +} + +type configParser interface { + parse(config *Config, raw []byte) error +} + +var ( + // configSubDirs is a map saying what kinds of configuration + // (values) are acceptable in a config subdirectory (key) + configSubDirs = make(map[string][]string) + parsersForKind = make(map[string]map[string]configParser) +) + +func addParser(kind, version string, parser configParser) { + if len(kind) == 0 { + panic("empty kind string when registering a config parser") + } + if len(version) == 0 { + panic("empty version string when registering a config parser") + } + if parser == nil { + panic("trying to register a nil parser") + } + if _, err := getParser(kind, version); err == nil { + panic(fmt.Sprintf("A parser for kind %q and version %q already exist", kind, version)) + } + if _, ok := parsersForKind[kind]; !ok { + parsersForKind[kind] = make(map[string]configParser) + } + parsersForKind[kind][version] = parser +} + +func registerSubDir(dir string, kinds []string) { + if len(dir) == 0 { + panic("trying to register empty config subdirectory") + } + if len(kinds) == 0 { + panic("kinds array cannot be empty when registering config subdir") + } + allKinds := toArray(toSet(append(configSubDirs[dir], kinds...))) + sort.Strings(allKinds) + configSubDirs[dir] = allKinds +} + +func toSet(a []string) map[string]struct{} { + s := make(map[string]struct{}) + for _, v := range a { + s[v] = struct{}{} + } + return s +} + +func toArray(s map[string]struct{}) []string { + a := make([]string, len(s)) + for k := range s { + a = append(a, k) + } + return a +} + +// GetConfig gets the Config instance with configuration taken from +// default system path (see common.DefaultSystemConfigDir) overridden +// with configuration from default local path (see +// common.DefaultLocalConfigDir). +func GetConfig() (*Config, error) { + return GetConfigFrom(common.DefaultSystemConfigDir, common.DefaultLocalConfigDir) +} + +// GetConfigFrom gets the Config instance with configuration taken +// from given system path overridden with configuration from given +// local path. +func GetConfigFrom(system, local string) (*Config, error) { + cfg := newConfig() + for _, cd := range []string{system, local} { + subcfg, err := GetConfigFromDir(cd) + if err != nil { + return nil, err + } + mergeConfigs(cfg, subcfg) + } + return cfg, nil +} + +// GetConfigFromDir gets the Config instance with configuration taken +// from given directory. +func GetConfigFromDir(dir string) (*Config, error) { + subcfg := newConfig() + if valid, err := validDir(dir); err != nil { + return nil, err + } else if !valid { + return subcfg, nil + } + if err := readConfigDir(subcfg, dir); err != nil { + return nil, err + } + return subcfg, nil +} + +func newConfig() *Config { + return &Config{ + AuthPerHost: make(map[string]Headerer), + DockerCredentialsPerRegistry: make(map[string]BasicCredentials), + } +} + +func readConfigDir(config *Config, dir string) error { + for csd, kinds := range configSubDirs { + d := filepath.Join(dir, csd) + if valid, err := validDir(d); err != nil { + return err + } else if !valid { + continue + } + configWalker := getConfigWalker(config, kinds, d) + if err := filepath.Walk(d, configWalker); err != nil { + return err + } + } + return nil +} + +func validDir(path string) (bool, error) { + fi, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + if !fi.IsDir() { + return false, fmt.Errorf("expected %q to be a directory", path) + } + return true, nil +} + +func getConfigWalker(config *Config, kinds []string, root string) filepath.WalkFunc { + return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == root { + return nil + } + return readFile(config, info, path, kinds) + } +} + +func readFile(config *Config, info os.FileInfo, path string, kinds []string) error { + if valid, err := validConfigFile(info); err != nil { + return err + } else if !valid { + return nil + } + if err := parseConfigFile(config, path, kinds); err != nil { + return err + } + return nil +} + +func validConfigFile(info os.FileInfo) (bool, error) { + mode := info.Mode() + switch { + case mode.IsDir(): + return false, filepath.SkipDir + case mode.IsRegular(): + return filepath.Ext(info.Name()) == ".json", nil + case mode&os.ModeSymlink == os.ModeSymlink: + // TODO: support symlinks? + return false, nil + default: + return false, nil + } +} + +type configHeader struct { + RktVersion string `json:"rktVersion"` + RktKind string `json:"rktKind"` +} + +func parseConfigFile(config *Config, path string, kinds []string) error { + raw, err := ioutil.ReadFile(path) + if err != nil { + return err + } + var header configHeader + if err := json.Unmarshal(raw, &header); err != nil { + return err + } + if len(header.RktKind) == 0 { + return fmt.Errorf("no rktKind specified in %q", path) + } + if len(header.RktVersion) == 0 { + return fmt.Errorf("no rktVersion specified in %q", path) + } + kindOk := false + for _, kind := range kinds { + if header.RktKind == kind { + kindOk = true + break + } + } + if !kindOk { + dir := filepath.Dir(path) + base := filepath.Base(path) + kindsStr := strings.Join(kinds, `", "`) + return fmt.Errorf("the configuration directory %q expects to have configuration files of kinds %q, but %q has kind of %q", dir, kindsStr, base, header.RktKind) + } + parser, err := getParser(header.RktKind, header.RktVersion) + if err != nil { + return err + } + if err := parser.parse(config, raw); err != nil { + return fmt.Errorf("failed to parse %q: %v", path, err) + } + return nil +} + +func getParser(kind, version string) (configParser, error) { + parsers, ok := parsersForKind[kind] + if !ok { + return nil, fmt.Errorf("no parser available for configuration of kind %q", kind) + } + parser, ok := parsers[version] + if !ok { + return nil, fmt.Errorf("no parser available for configuration of kind %q and version %q", kind, version) + } + return parser, nil +} + +func mergeConfigs(config *Config, subconfig *Config) { + for host, headerer := range subconfig.AuthPerHost { + config.AuthPerHost[host] = headerer + } + for registry, creds := range subconfig.DockerCredentialsPerRegistry { + config.DockerCredentialsPerRegistry[registry] = creds + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/config_test.go b/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/config_test.go new file mode 100644 index 0000000..a244aa1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/rkt/rkt/config/config_test.go @@ -0,0 +1,256 @@ +// Copyright 2015 The rkt Authors +// +// 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 config + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "reflect" + "testing" +) + +const tstprefix = "config-test" + +// tmpConfigFile is based on ioutil.Tempfile. The differences are that +// this function is simpler (no reseeding and whatnot) and, most +// importantly, it returns a file with ".json" extension. +func tmpConfigFile(prefix string) (*os.File, error) { + dir := os.TempDir() + idx := 0 + tries := 10000 + for i := 0; i < tries; i++ { + name := filepath.Join(dir, fmt.Sprintf("%s%d.json", prefix, idx)) + f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if os.IsExist(err) { + idx++ + continue + } + return f, err + } + return nil, fmt.Errorf("Failed to get tmpfile after %d tries", tries) +} + +func TestAuthConfigFormat(t *testing.T) { + tests := []struct { + contents string + expected map[string]http.Header + fail bool + }{ + {"bogus contents", nil, true}, + {`{"bogus": {"foo": "bar"}}`, nil, true}, + {`{"rktKind": "foo"}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "foo"}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1"}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": "foo"}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": []}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"]}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "foo"}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "basic"}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "basic", "credentials": {}}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "basic", "credentials": {"user": ""}}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "basic", "credentials": {"user": "bar"}}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "basic", "credentials": {"user": "bar", "password": ""}}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "basic", "credentials": {"user": "bar", "password": "baz"}}`, map[string]http.Header{"coreos.com": {"Authorization": []string{"Basic YmFyOmJheg=="}}}, false}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "oauth"}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "oauth", "credentials": {}}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "oauth", "credentials": {"token": ""}}`, nil, true}, + {`{"rktKind": "auth", "rktVersion": "v1", "domains": ["coreos.com"], "type": "oauth", "credentials": {"token": "sometoken"}}`, map[string]http.Header{"coreos.com": {"Authorization": []string{"Bearer sometoken"}}}, false}, + } + for _, tt := range tests { + cfg, err := getConfigFromContents(tt.contents, "auth") + if vErr := verifyFailure(tt.fail, tt.contents, err); vErr != nil { + t.Errorf("%v", vErr) + } else if !tt.fail { + result := make(map[string]http.Header) + for k, v := range cfg.AuthPerHost { + result[k] = v.Header() + } + if !reflect.DeepEqual(result, tt.expected) { + t.Error("Got unexpected results\nResult:\n", result, "\n\nExpected:\n", tt.expected) + } + } + } +} + +func TestDockerAuthConfigFormat(t *testing.T) { + tests := []struct { + contents string + expected map[string]BasicCredentials + fail bool + }{ + {"bogus contents", nil, true}, + {`{"bogus": {"foo": "bar"}}`, nil, true}, + {`{"rktKind": "foo"}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "foo"}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1"}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": "foo"}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": []}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": ["coreos.com"]}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": ["coreos.com"], "credentials": {}}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": ["coreos.com"], "credentials": {"user": ""}}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": ["coreos.com"], "credentials": {"user": "bar"}}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": ["coreos.com"], "credentials": {"user": "bar", "password": ""}}`, nil, true}, + {`{"rktKind": "dockerAuth", "rktVersion": "v1", "registries": ["coreos.com"], "credentials": {"user": "bar", "password": "baz"}}`, map[string]BasicCredentials{"coreos.com": BasicCredentials{User: "bar", Password: "baz"}}, false}, + } + for _, tt := range tests { + cfg, err := getConfigFromContents(tt.contents, "dockerAuth") + if vErr := verifyFailure(tt.fail, tt.contents, err); vErr != nil { + t.Errorf("%v", vErr) + } else if !tt.fail { + result := cfg.DockerCredentialsPerRegistry + if !reflect.DeepEqual(result, tt.expected) { + t.Error("Got unexpected results\nResult:\n", result, "\n\nExpected:\n", tt.expected) + } + } + } +} + +func verifyFailure(shouldFail bool, contents string, err error) error { + var vErr error = nil + if err != nil { + if !shouldFail { + vErr = fmt.Errorf("Expected test to succeed, failed unexpectedly (contents: `%s`): %v", contents, err) + } + } else if shouldFail { + vErr = fmt.Errorf("Expected test to fail, succeeded unexpectedly (contents: `%s`)", contents) + } + return vErr +} + +func getConfigFromContents(contents, kind string) (*Config, error) { + f, err := tmpConfigFile(tstprefix) + if err != nil { + panic(fmt.Sprintf("Failed to create tmp config file: %v", err)) + } + // First remove the file, then close it (last deferred item is + // executed first). + defer f.Close() + defer os.Remove(f.Name()) + if _, err := f.Write([]byte(contents)); err != nil { + panic(fmt.Sprintf("Writing config to file failed: %v", err)) + } + fi, err := f.Stat() + if err != nil { + panic(fmt.Sprintf("Stating a tmp config file failed: %v", err)) + } + cfg := newConfig() + return cfg, readFile(cfg, fi, f.Name(), []string{kind}) +} + +func TestConfigLoading(t *testing.T) { + dir, err := ioutil.TempDir("", tstprefix) + if err != nil { + panic(fmt.Sprintf("Failed to create temporary directory: %v", err)) + } + defer os.RemoveAll(dir) + systemAuth := filepath.Join("system", "auth.d") + systemIgnored := filepath.Join(systemAuth, "ignoreddir") + localAuth := filepath.Join("local", "auth.d") + localIgnored := filepath.Join(localAuth, "ignoreddir") + dirs := []string{ + "system", + systemAuth, + systemIgnored, + "local", + localAuth, + localIgnored, + } + for _, d := range dirs { + cd := filepath.Join(dir, d) + if err := os.Mkdir(cd, 0700); err != nil { + panic(fmt.Sprintf("Failed to create configuration directory %q: %v", cd, err)) + } + } + files := []struct { + path string + domain string + user string + pass string + }{ + {filepath.Join(dir, systemAuth, "endocode.json"), "endocode.com", "system_user1", "system_password1"}, + {filepath.Join(dir, systemAuth, "coreos.json"), "coreos.com", "system_user2", "system_password2"}, + {filepath.Join(dir, systemAuth, "ignoredfile"), "example1.com", "ignored_user1", "ignored_password1"}, + {filepath.Join(dir, systemIgnored, "ignoredfile"), "example2.com", "ignored_user2", "ignored_password2"}, + {filepath.Join(dir, systemIgnored, "ignoredanyway.json"), "example3.com", "ignored_user3", "ignored_password3"}, + {filepath.Join(dir, localAuth, "endocode.json"), "endocode.com", "local_user1", "local_password1"}, + {filepath.Join(dir, localAuth, "tectonic.json"), "tectonic.com", "local_user2", "local_password2"}, + {filepath.Join(dir, localAuth, "ignoredfile"), "example4.com", "ignored_user4", "ignored_password4"}, + {filepath.Join(dir, localIgnored, "ignoredfile"), "example5.com", "ignored_user5", "ignored_password5"}, + {filepath.Join(dir, localIgnored, "ignoredanyway.json"), "example6.com", "ignored_user6", "ignored_password6"}, + } + for _, f := range files { + if err := writeBasicConfig(f.path, f.domain, f.user, f.pass); err != nil { + panic(fmt.Sprintf("Failed to write configuration file: %v", err)) + } + } + cfg, err := GetConfigFrom(filepath.Join(dir, "system"), filepath.Join(dir, "local")) + if err != nil { + panic(fmt.Sprintf("Failed to get configuration: %v", err)) + } + result := make(map[string]http.Header) + for d, h := range cfg.AuthPerHost { + result[d] = h.Header() + } + expected := map[string]http.Header{ + "endocode.com": http.Header{ + // local_user1:local_password1 + authHeader: []string{"Basic bG9jYWxfdXNlcjE6bG9jYWxfcGFzc3dvcmQx"}, + }, + "coreos.com": http.Header{ + // system_user2:system_password2 + authHeader: []string{"Basic c3lzdGVtX3VzZXIyOnN5c3RlbV9wYXNzd29yZDI="}, + }, + "tectonic.com": http.Header{ + // local_user2:local_password2 + authHeader: []string{"Basic bG9jYWxfdXNlcjI6bG9jYWxfcGFzc3dvcmQy"}, + }, + } + if !reflect.DeepEqual(result, expected) { + t.Error("Got unexpected results\nResult:\n", result, "\n\nExpected:\n", expected) + } +} + +func writeBasicConfig(path, domain, user, pass string) error { + type basicv1creds struct { + User string `json:"user"` + Password string `json:"password"` + } + type basicv1 struct { + RktVersion string `json:"rktVersion"` + RktKind string `json:"rktKind"` + Domains []string `json:"domains"` + Type string `json:"type"` + Credentials basicv1creds `json:"credentials"` + } + config := &basicv1{ + RktVersion: "v1", + RktKind: "auth", + Domains: []string{domain}, + Type: "basic", + Credentials: basicv1creds{ + User: user, + Password: pass, + }, + } + raw, err := json.Marshal(config) + if err != nil { + return err + } + return ioutil.WriteFile(path, raw, 0600) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/.travis.yml b/Godeps/_workspace/src/github.com/spf13/pflag/.travis.yml new file mode 100644 index 0000000..c4d88e3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/.travis.yml @@ -0,0 +1,8 @@ +sudo: false + +language: go + +go: + - 1.3 + - 1.4 + - tip diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/LICENSE b/Godeps/_workspace/src/github.com/spf13/pflag/LICENSE new file mode 100644 index 0000000..63ed1cf --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 Alex Ogier. All rights reserved. +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. diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/README.md b/Godeps/_workspace/src/github.com/spf13/pflag/README.md new file mode 100644 index 0000000..deee931 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/README.md @@ -0,0 +1,228 @@ +[![Build Status](https://travis-ci.org/spf13/pflag.svg?branch=master)](https://travis-ci.org/spf13/pflag) + +## Description + +pflag is a drop-in replacement for Go's flag package, implementing +POSIX/GNU-style --flags. + +pflag is compatible with the [GNU extensions to the POSIX recommendations +for command-line options][1]. For a more precise description, see the +"Command-line flag syntax" section below. + +[1]: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + +pflag is available under the same style of BSD license as the Go language, +which can be found in the LICENSE file. + +## Installation + +pflag is available using the standard `go get` command. + +Install by running: + + go get github.com/spf13/pflag + +Run tests by running: + + go test github.com/spf13/pflag + +## Usage + +pflag is a drop-in replacement of Go's native flag package. If you import +pflag under the name "flag" then all code should continue to function +with no changes. + +``` go +import flag "github.com/spf13/pflag" +``` + +There is one exception to this: if you directly instantiate the Flag struct +there is one more field "Shorthand" that you will need to set. +Most code never instantiates this struct directly, and instead uses +functions such as String(), BoolVar(), and Var(), and is therefore +unaffected. + +Define flags using flag.String(), Bool(), Int(), etc. + +This declares an integer flag, -flagname, stored in the pointer ip, with type *int. + +``` go +var ip *int = flag.Int("flagname", 1234, "help message for flagname") +``` + +If you like, you can bind the flag to a variable using the Var() functions. + +``` go +var flagvar int +func init() { + flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") +} +``` + +Or you can create custom flags that satisfy the Value interface (with +pointer receivers) and couple them to flag parsing by + +``` go +flag.Var(&flagVal, "name", "help message for flagname") +``` + +For such flags, the default value is just the initial value of the variable. + +After all flags are defined, call + +``` go +flag.Parse() +``` + +to parse the command line into the defined flags. + +Flags may then be used directly. If you're using the flags themselves, +they are all pointers; if you bind to variables, they're values. + +``` go +fmt.Println("ip has value ", *ip) +fmt.Println("flagvar has value ", flagvar) +``` + +There are helpers function to get values later if you have the FlagSet but +it was difficult to keep up with all of the the flag pointers in your code. +If you have a pflag.FlagSet with a flag called 'flagname' of type int you +can use GetInt() to get the int value. But notice that 'flagname' must exist +and it must be an int. GetString("flagname") will fail. + +``` go +i, err := flagset.GetInt("flagname") +``` + +After parsing, the arguments after the flag are available as the +slice flag.Args() or individually as flag.Arg(i). +The arguments are indexed from 0 through flag.NArg()-1. + +The pflag package also defines some new functions that are not in flag, +that give one-letter shorthands for flags. You can use these by appending +'P' to the name of any function that defines a flag. + +``` go +var ip = flag.IntP("flagname", "f", 1234, "help message") +var flagvar bool +func init() { + flag.BoolVarP("boolname", "b", true, "help message") +} +flag.VarP(&flagVar, "varname", "v", 1234, "help message") +``` + +Shorthand letters can be used with single dashes on the command line. +Boolean shorthand flags can be combined with other shorthand flags. + +The default set of command-line flags is controlled by +top-level functions. The FlagSet type allows one to define +independent sets of flags, such as to implement subcommands +in a command-line interface. The methods of FlagSet are +analogous to the top-level functions for the command-line +flag set. + +## Setting no option default values for flags + +After you create a flag it is possible to set the pflag.NoOptDefVal for +the given flag. Doing this changes the meaning of the flag slightly. If +a flag has a NoOptDefVal and the flag is set on the command line without +an option the flag will be set to the NoOptDefVal. For example given: + +``` go +var ip = flag.IntP("flagname", "f", 1234, "help message") +flag.Lookup("flagname").NoOptDefVal = "4321" +``` + +Would result in something like + +| Parsed Arguments | Resulting Value | +| ------------- | ------------- | +| --flagname=1357 | ip=1357 | +| --flagname | ip=4321 | +| [nothing] | ip=1234 | + +## Command line flag syntax + +``` +--flag // boolean flags, or flags with no option default values +--flag x // only on flags without a default value +--flag=x +``` + +Unlike the flag package, a single dash before an option means something +different than a double dash. Single dashes signify a series of shorthand +letters for flags. All but the last shorthand letter must be boolean flags +or a flag with a default value + +``` +// boolean or flags where the 'no option default value' is set +-f +-f=true +-abc +but +-b true is INVALID + +// non-boolean and flags without a 'no option default value' +-n 1234 +-n=1234 +-n1234 + +// mixed +-abcs "hello" +-absd="hello" +-abcs1234 +``` + +Flag parsing stops after the terminator "--". Unlike the flag package, +flags can be interspersed with arguments anywhere on the command line +before this terminator. + +Integer flags accept 1234, 0664, 0x1234 and may be negative. +Boolean flags (in their long form) accept 1, 0, t, f, true, false, +TRUE, FALSE, True, False. +Duration flags accept any input valid for time.ParseDuration. + +## Mutating or "Normalizing" Flag names + +It is possible to set a custom flag name 'normalization function.' It allows flag names to be mutated both when created in the code and when used on the command line to some 'normalized' form. The 'normalized' form is used for comparison. Two examples of using the custom normalization func follow. + +**Example #1**: You want -, _, and . in flags to compare the same. aka --my-flag == --my_flag == --my.flag + +``` go +func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { + from := []string{"-", "_"} + to := "." + for _, sep := range from { + name = strings.Replace(name, sep, to, -1) + } + return pflag.NormalizedName(name) +} + +myFlagSet.SetNormalizeFunc(wordSepNormalizeFunc) +``` + +**Example #2**: You want to alias two flags. aka --old-flag-name == --new-flag-name + +``` go +func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "old-flag-name": + name = "new-flag-name" + break + } + return pflag.NormalizedName(name) +} + +myFlagSet.SetNormalizeFunc(aliasNormalizeFunc) +``` + +## More info + +You can see the full reference documentation of the pflag package +[at godoc.org][3], or through go's standard documentation system by +running `godoc -http=:6060` and browsing to +[http://localhost:6060/pkg/github.com/ogier/pflag][2] after +installation. + +[2]: http://localhost:6060/pkg/github.com/ogier/pflag +[3]: http://godoc.org/github.com/ogier/pflag diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/bool.go b/Godeps/_workspace/src/github.com/spf13/pflag/bool.go new file mode 100644 index 0000000..04c9b5a --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/bool.go @@ -0,0 +1,97 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// optional interface to indicate boolean flags that can be +// supplied without "=value" text +type boolFlag interface { + Value + IsBoolFlag() bool +} + +// -- bool Value +type boolValue bool + +func newBoolValue(val bool, p *bool) *boolValue { + *p = val + return (*boolValue)(p) +} + +func (b *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + *b = boolValue(v) + return err +} + +func (b *boolValue) Type() string { + return "bool" +} + +func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) } + +func (b *boolValue) IsBoolFlag() bool { return true } + +func boolConv(sval string) (interface{}, error) { + return strconv.ParseBool(sval) +} + +// GetBool return the bool value of a flag with the given name +func (f *FlagSet) GetBool(name string) (bool, error) { + val, err := f.getFlagType(name, "bool", boolConv) + if err != nil { + return false, err + } + return val.(bool), nil +} + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) { + f.BoolVarP(p, name, "", value, usage) +} + +// Like BoolVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) { + flag := f.VarPF(newBoolValue(value, p), name, shorthand, usage) + flag.NoOptDefVal = "true" +} + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func BoolVar(p *bool, name string, value bool, usage string) { + BoolVarP(p, name, "", value, usage) +} + +// Like BoolVar, but accepts a shorthand letter that can be used after a single dash. +func BoolVarP(p *bool, name, shorthand string, value bool, usage string) { + flag := CommandLine.VarPF(newBoolValue(value, p), name, shorthand, usage) + flag.NoOptDefVal = "true" +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func (f *FlagSet) Bool(name string, value bool, usage string) *bool { + return f.BoolP(name, "", value, usage) +} + +// Like Bool, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool { + p := new(bool) + f.BoolVarP(p, name, shorthand, value, usage) + return p +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func Bool(name string, value bool, usage string) *bool { + return BoolP(name, "", value, usage) +} + +// Like Bool, but accepts a shorthand letter that can be used after a single dash. +func BoolP(name, shorthand string, value bool, usage string) *bool { + b := CommandLine.BoolP(name, shorthand, value, usage) + return b +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/bool_test.go b/Godeps/_workspace/src/github.com/spf13/pflag/bool_test.go new file mode 100644 index 0000000..febf667 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/bool_test.go @@ -0,0 +1,180 @@ +// Copyright 2009 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 pflag + +import ( + "bytes" + "fmt" + "strconv" + "testing" +) + +// This value can be a boolean ("true", "false") or "maybe" +type triStateValue int + +const ( + triStateFalse triStateValue = 0 + triStateTrue triStateValue = 1 + triStateMaybe triStateValue = 2 +) + +const strTriStateMaybe = "maybe" + +func (v *triStateValue) IsBoolFlag() bool { + return true +} + +func (v *triStateValue) Get() interface{} { + return triStateValue(*v) +} + +func (v *triStateValue) Set(s string) error { + if s == strTriStateMaybe { + *v = triStateMaybe + return nil + } + boolVal, err := strconv.ParseBool(s) + if boolVal { + *v = triStateTrue + } else { + *v = triStateFalse + } + return err +} + +func (v *triStateValue) String() string { + if *v == triStateMaybe { + return strTriStateMaybe + } + return fmt.Sprintf("%v", bool(*v == triStateTrue)) +} + +// The type of the flag as requred by the pflag.Value interface +func (v *triStateValue) Type() string { + return "version" +} + +func setUpFlagSet(tristate *triStateValue) *FlagSet { + f := NewFlagSet("test", ContinueOnError) + *tristate = triStateFalse + flag := f.VarPF(tristate, "tristate", "t", "tristate value (true, maybe or false)") + flag.NoOptDefVal = "true" + return f +} + +func TestExplicitTrue(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + err := f.Parse([]string{"--tristate=true"}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if tristate != triStateTrue { + t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead") + } +} + +func TestImplicitTrue(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + err := f.Parse([]string{"--tristate"}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if tristate != triStateTrue { + t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead") + } +} + +func TestShortFlag(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + err := f.Parse([]string{"-t"}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if tristate != triStateTrue { + t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead") + } +} + +func TestShortFlagExtraArgument(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + // The"maybe"turns into an arg, since short boolean options will only do true/false + err := f.Parse([]string{"-t", "maybe"}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if tristate != triStateTrue { + t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead") + } + args := f.Args() + if len(args) != 1 || args[0] != "maybe" { + t.Fatal("expected an extra 'maybe' argument to stick around") + } +} + +func TestExplicitMaybe(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + err := f.Parse([]string{"--tristate=maybe"}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if tristate != triStateMaybe { + t.Fatal("expected", triStateMaybe, "(triStateMaybe) but got", tristate, "instead") + } +} + +func TestExplicitFalse(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + err := f.Parse([]string{"--tristate=false"}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if tristate != triStateFalse { + t.Fatal("expected", triStateFalse, "(triStateFalse) but got", tristate, "instead") + } +} + +func TestImplicitFalse(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + err := f.Parse([]string{}) + if err != nil { + t.Fatal("expected no error; got", err) + } + if tristate != triStateFalse { + t.Fatal("expected", triStateFalse, "(triStateFalse) but got", tristate, "instead") + } +} + +func TestInvalidValue(t *testing.T) { + var tristate triStateValue + f := setUpFlagSet(&tristate) + var buf bytes.Buffer + f.SetOutput(&buf) + err := f.Parse([]string{"--tristate=invalid"}) + if err == nil { + t.Fatal("expected an error but did not get any, tristate has value", tristate) + } +} + +func TestBoolP(t *testing.T) { + b := BoolP("bool", "b", false, "bool value in CommandLine") + c := BoolP("c", "c", false, "other bool value") + args := []string{"--bool"} + if err := CommandLine.Parse(args); err != nil { + t.Error("expected no error, got ", err) + } + if *b != true { + t.Errorf("expected b=true got b=%s", b) + } + if *c != false { + t.Errorf("expect c=false got c=%s", c) + } +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/duration.go b/Godeps/_workspace/src/github.com/spf13/pflag/duration.go new file mode 100644 index 0000000..382ffd3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/duration.go @@ -0,0 +1,86 @@ +package pflag + +import ( + "time" +) + +// -- time.Duration Value +type durationValue time.Duration + +func newDurationValue(val time.Duration, p *time.Duration) *durationValue { + *p = val + return (*durationValue)(p) +} + +func (d *durationValue) Set(s string) error { + v, err := time.ParseDuration(s) + *d = durationValue(v) + return err +} + +func (d *durationValue) Type() string { + return "duration" +} + +func (d *durationValue) String() string { return (*time.Duration)(d).String() } + +func durationConv(sval string) (interface{}, error) { + return time.ParseDuration(sval) +} + +// GetDuration return the duration value of a flag with the given name +func (f *FlagSet) GetDuration(name string) (time.Duration, error) { + val, err := f.getFlagType(name, "duration", durationConv) + if err != nil { + return 0, err + } + return val.(time.Duration), nil +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + f.VarP(newDurationValue(value, p), name, "", usage) +} + +// Like DurationVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationVarP(p *time.Duration, name, shorthand string, value time.Duration, usage string) { + f.VarP(newDurationValue(value, p), name, shorthand, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + CommandLine.VarP(newDurationValue(value, p), name, "", usage) +} + +// Like DurationVar, but accepts a shorthand letter that can be used after a single dash. +func DurationVarP(p *time.Duration, name, shorthand string, value time.Duration, usage string) { + CommandLine.VarP(newDurationValue(value, p), name, shorthand, usage) +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration { + p := new(time.Duration) + f.DurationVarP(p, name, "", value, usage) + return p +} + +// Like Duration, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationP(name, shorthand string, value time.Duration, usage string) *time.Duration { + p := new(time.Duration) + f.DurationVarP(p, name, shorthand, value, usage) + return p +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func Duration(name string, value time.Duration, usage string) *time.Duration { + return CommandLine.DurationP(name, "", value, usage) +} + +// Like Duration, but accepts a shorthand letter that can be used after a single dash. +func DurationP(name, shorthand string, value time.Duration, usage string) *time.Duration { + return CommandLine.DurationP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/example_test.go b/Godeps/_workspace/src/github.com/spf13/pflag/example_test.go new file mode 100644 index 0000000..5fe3846 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/example_test.go @@ -0,0 +1,77 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// These examples demonstrate more intricate uses of the flag package. +package pflag_test + +import ( + "errors" + "fmt" + "strings" + "time" + + flag "github.com/appc/acpush/Godeps/_workspace/src/github.com/spf13/pflag" +) + +// Example 1: A single string flag called "species" with default value "gopher". +var species = flag.String("species", "gopher", "the species we are studying") + +// Example 2: A flag with a shorthand letter. +var gopherType = flag.StringP("gopher_type", "g", "pocket", "the variety of gopher") + +// Example 3: A user-defined flag type, a slice of durations. +type interval []time.Duration + +// String is the method to format the flag's value, part of the flag.Value interface. +// The String method's output will be used in diagnostics. +func (i *interval) String() string { + return fmt.Sprint(*i) +} + +func (i *interval) Type() string { + return "interval" +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (i *interval) Set(value string) error { + // If we wanted to allow the flag to be set multiple times, + // accumulating values, we would delete this if statement. + // That would permit usages such as + // -deltaT 10s -deltaT 15s + // and other combinations. + if len(*i) > 0 { + return errors.New("interval flag already set") + } + for _, dt := range strings.Split(value, ",") { + duration, err := time.ParseDuration(dt) + if err != nil { + return err + } + *i = append(*i, duration) + } + return nil +} + +// Define a flag to accumulate durations. Because it has a special type, +// we need to use the Var function and therefore create the flag during +// init. + +var intervalFlag interval + +func init() { + // Tie the command-line flag to the intervalFlag variable and + // set a usage message. + flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events") +} + +func Example() { + // All the interesting pieces are with the variables declared above, but + // to enable the flag package to see the flags defined there, one must + // execute, typically at the start of main (not init!): + // flag.Parse() + // We don't run it here because this is not a main function and + // the testing suite has already parsed the flags. +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/export_test.go b/Godeps/_workspace/src/github.com/spf13/pflag/export_test.go new file mode 100644 index 0000000..9318fee --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/export_test.go @@ -0,0 +1,29 @@ +// Copyright 2010 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 pflag + +import ( + "io/ioutil" + "os" +) + +// Additional routines compiled into the package only during testing. + +// ResetForTesting clears all flag state and sets the usage function as directed. +// After calling ResetForTesting, parse errors in flag handling will not +// exit the program. +func ResetForTesting(usage func()) { + CommandLine = &FlagSet{ + name: os.Args[0], + errorHandling: ContinueOnError, + output: ioutil.Discard, + } + Usage = usage +} + +// GetCommandLine returns the default FlagSet. +func GetCommandLine() *FlagSet { + return CommandLine +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/flag.go b/Godeps/_workspace/src/github.com/spf13/pflag/flag.go new file mode 100644 index 0000000..865259b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/flag.go @@ -0,0 +1,749 @@ +// Copyright 2009 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. + +/* + pflag is a drop-in replacement for Go's flag package, implementing + POSIX/GNU-style --flags. + + pflag is compatible with the GNU extensions to the POSIX recommendations + for command-line options. See + http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + + Usage: + + pflag is a drop-in replacement of Go's native flag package. If you import + pflag under the name "flag" then all code should continue to function + with no changes. + + import flag "github.com/ogier/pflag" + + There is one exception to this: if you directly instantiate the Flag struct + there is one more field "Shorthand" that you will need to set. + Most code never instantiates this struct directly, and instead uses + functions such as String(), BoolVar(), and Var(), and is therefore + unaffected. + + Define flags using flag.String(), Bool(), Int(), etc. + + This declares an integer flag, -flagname, stored in the pointer ip, with type *int. + var ip = flag.Int("flagname", 1234, "help message for flagname") + If you like, you can bind the flag to a variable using the Var() functions. + var flagvar int + func init() { + flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") + } + Or you can create custom flags that satisfy the Value interface (with + pointer receivers) and couple them to flag parsing by + flag.Var(&flagVal, "name", "help message for flagname") + For such flags, the default value is just the initial value of the variable. + + After all flags are defined, call + flag.Parse() + to parse the command line into the defined flags. + + Flags may then be used directly. If you're using the flags themselves, + they are all pointers; if you bind to variables, they're values. + fmt.Println("ip has value ", *ip) + fmt.Println("flagvar has value ", flagvar) + + After parsing, the arguments after the flag are available as the + slice flag.Args() or individually as flag.Arg(i). + The arguments are indexed from 0 through flag.NArg()-1. + + The pflag package also defines some new functions that are not in flag, + that give one-letter shorthands for flags. You can use these by appending + 'P' to the name of any function that defines a flag. + var ip = flag.IntP("flagname", "f", 1234, "help message") + var flagvar bool + func init() { + flag.BoolVarP("boolname", "b", true, "help message") + } + flag.VarP(&flagVar, "varname", "v", 1234, "help message") + Shorthand letters can be used with single dashes on the command line. + Boolean shorthand flags can be combined with other shorthand flags. + + Command line flag syntax: + --flag // boolean flags only + --flag=x + + Unlike the flag package, a single dash before an option means something + different than a double dash. Single dashes signify a series of shorthand + letters for flags. All but the last shorthand letter must be boolean flags. + // boolean flags + -f + -abc + // non-boolean flags + -n 1234 + -Ifile + // mixed + -abcs "hello" + -abcn1234 + + Flag parsing stops after the terminator "--". Unlike the flag package, + flags can be interspersed with arguments anywhere on the command line + before this terminator. + + Integer flags accept 1234, 0664, 0x1234 and may be negative. + Boolean flags (in their long form) accept 1, 0, t, f, true, false, + TRUE, FALSE, True, False. + Duration flags accept any input valid for time.ParseDuration. + + The default set of command-line flags is controlled by + top-level functions. The FlagSet type allows one to define + independent sets of flags, such as to implement subcommands + in a command-line interface. The methods of FlagSet are + analogous to the top-level functions for the command-line + flag set. +*/ +package pflag + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "sort" + "strings" +) + +// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined. +var ErrHelp = errors.New("pflag: help requested") + +// ErrorHandling defines how to handle flag parsing errors. +type ErrorHandling int + +const ( + ContinueOnError ErrorHandling = iota + ExitOnError + PanicOnError +) + +// NormalizedName is a flag name that has been normalized according to rules +// for the FlagSet (e.g. making '-' and '_' equivalent). +type NormalizedName string + +// A FlagSet represents a set of defined flags. +type FlagSet struct { + // Usage is the function called when an error occurs while parsing flags. + // The field is a function (not a method) that may be changed to point to + // a custom error handler. + Usage func() + + name string + parsed bool + actual map[NormalizedName]*Flag + formal map[NormalizedName]*Flag + shorthands map[byte]*Flag + args []string // arguments after flags + exitOnError bool // does the program exit if there's an error? + errorHandling ErrorHandling + output io.Writer // nil means stderr; use out() accessor + interspersed bool // allow interspersed option/non-option args + normalizeNameFunc func(f *FlagSet, name string) NormalizedName +} + +// A Flag represents the state of a flag. +type Flag struct { + Name string // name as it appears on command line + Shorthand string // one-letter abbreviated flag + Usage string // help message + Value Value // value as set + DefValue string // default value (as text); for usage message + Changed bool // If the user set the value (or if left to default) + NoOptDefVal string //default value (as text); if the flag is on the command line without any options + Deprecated string // If this flag is deprecated, this string is the new or now thing to use + Annotations map[string][]string // used by cobra.Command bash autocomple code +} + +// Value is the interface to the dynamic value stored in a flag. +// (The default value is represented as a string.) +type Value interface { + String() string + Set(string) error + Type() string +} + +// sortFlags returns the flags as a slice in lexicographical sorted order. +func sortFlags(flags map[NormalizedName]*Flag) []*Flag { + list := make(sort.StringSlice, len(flags)) + i := 0 + for k := range flags { + list[i] = string(k) + i++ + } + list.Sort() + result := make([]*Flag, len(list)) + for i, name := range list { + result[i] = flags[NormalizedName(name)] + } + return result +} + +func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) { + f.normalizeNameFunc = n + for k, v := range f.formal { + delete(f.formal, k) + nname := f.normalizeFlagName(string(k)) + f.formal[nname] = v + v.Name = string(nname) + } +} + +func (f *FlagSet) GetNormalizeFunc() func(f *FlagSet, name string) NormalizedName { + if f.normalizeNameFunc != nil { + return f.normalizeNameFunc + } + return func(f *FlagSet, name string) NormalizedName { return NormalizedName(name) } +} + +func (f *FlagSet) normalizeFlagName(name string) NormalizedName { + n := f.GetNormalizeFunc() + return n(f, name) +} + +func (f *FlagSet) out() io.Writer { + if f.output == nil { + return os.Stderr + } + return f.output +} + +// SetOutput sets the destination for usage and error messages. +// If output is nil, os.Stderr is used. +func (f *FlagSet) SetOutput(output io.Writer) { + f.output = output +} + +// VisitAll visits the flags in lexicographical order, calling fn for each. +// It visits all flags, even those not set. +func (f *FlagSet) VisitAll(fn func(*Flag)) { + for _, flag := range sortFlags(f.formal) { + fn(flag) + } +} + +func (f *FlagSet) HasFlags() bool { + return len(f.formal) > 0 +} + +// VisitAll visits the command-line flags in lexicographical order, calling +// fn for each. It visits all flags, even those not set. +func VisitAll(fn func(*Flag)) { + CommandLine.VisitAll(fn) +} + +// Visit visits the flags in lexicographical order, calling fn for each. +// It visits only those flags that have been set. +func (f *FlagSet) Visit(fn func(*Flag)) { + for _, flag := range sortFlags(f.actual) { + fn(flag) + } +} + +// Visit visits the command-line flags in lexicographical order, calling fn +// for each. It visits only those flags that have been set. +func Visit(fn func(*Flag)) { + CommandLine.Visit(fn) +} + +// Lookup returns the Flag structure of the named flag, returning nil if none exists. +func (f *FlagSet) Lookup(name string) *Flag { + return f.lookup(f.normalizeFlagName(name)) +} + +// lookup returns the Flag structure of the named flag, returning nil if none exists. +func (f *FlagSet) lookup(name NormalizedName) *Flag { + return f.formal[name] +} + +// func to return a given type for a given flag name +func (f *FlagSet) getFlagType(name string, ftype string, convFunc func(sval string) (interface{}, error)) (interface{}, error) { + flag := f.Lookup(name) + if flag == nil { + err := fmt.Errorf("flag accessed but not defined: %s\n", name) + return nil, err + } + + if flag.Value.Type() != ftype { + err := fmt.Errorf("trying to get %s value of flag of type %s\n", ftype, flag.Value.Type()) + return nil, err + } + + sval := flag.Value.String() + result, err := convFunc(sval) + if err != nil { + return nil, err + } + return result, nil +} + +// Mark a flag deprecated in your program +func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error { + flag := f.Lookup(name) + if flag == nil { + return fmt.Errorf("flag %q does not exist", name) + } + flag.Deprecated = usageMessage + return nil +} + +// Lookup returns the Flag structure of the named command-line flag, +// returning nil if none exists. +func Lookup(name string) *Flag { + return CommandLine.Lookup(name) +} + +// Set sets the value of the named flag. +func (f *FlagSet) Set(name, value string) error { + normalName := f.normalizeFlagName(name) + flag, ok := f.formal[normalName] + if !ok { + return fmt.Errorf("no such flag -%v", name) + } + err := flag.Value.Set(value) + if err != nil { + return err + } + if f.actual == nil { + f.actual = make(map[NormalizedName]*Flag) + } + f.actual[normalName] = flag + flag.Changed = true + if len(flag.Deprecated) > 0 { + fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated) + } + return nil +} + +func (f *FlagSet) SetAnnotation(name, key string, values []string) error { + normalName := f.normalizeFlagName(name) + flag, ok := f.formal[normalName] + if !ok { + return fmt.Errorf("no such flag -%v", name) + } + if flag.Annotations == nil { + flag.Annotations = map[string][]string{} + } + flag.Annotations[key] = values + return nil +} + +// Set sets the value of the named command-line flag. +func Set(name, value string) error { + return CommandLine.Set(name, value) +} + +// PrintDefaults prints, to standard error unless configured +// otherwise, the default values of all defined flags in the set. +func (f *FlagSet) PrintDefaults() { + f.VisitAll(func(flag *Flag) { + if len(flag.Deprecated) > 0 { + return + } + format := "" + // ex: w/ option string argument '-%s, --%s[=%q]: %s\n' + if len(flag.Shorthand) > 0 { + format = " -%s, --%s" + } else { + format = " %s --%s" + } + if len(flag.NoOptDefVal) > 0 { + format = format + "[" + } + if _, ok := flag.Value.(*stringValue); ok { + format = format + "=%q" + } else { + format = format + "=%s" + } + if len(flag.NoOptDefVal) > 0 { + format = format + "]" + } + format = format + ": %s\n" + fmt.Fprintf(f.out(), format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage) + }) +} + +func (f *FlagSet) FlagUsages() string { + x := new(bytes.Buffer) + + f.VisitAll(func(flag *Flag) { + if len(flag.Deprecated) > 0 { + return + } + format := "--%s=%s: %s\n" + if _, ok := flag.Value.(*stringValue); ok { + // put quotes on the value + format = "--%s=%q: %s\n" + } + if len(flag.Shorthand) > 0 { + format = " -%s, " + format + } else { + format = " %s " + format + } + fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage) + }) + + return x.String() +} + +// PrintDefaults prints to standard error the default values of all defined command-line flags. +func PrintDefaults() { + CommandLine.PrintDefaults() +} + +// defaultUsage is the default function to print a usage message. +func defaultUsage(f *FlagSet) { + fmt.Fprintf(f.out(), "Usage of %s:\n", f.name) + f.PrintDefaults() +} + +// NOTE: Usage is not just defaultUsage(CommandLine) +// because it serves (via godoc flag Usage) as the example +// for how to write your own usage function. + +// Usage prints to standard error a usage message documenting all defined command-line flags. +// The function is a variable that may be changed to point to a custom function. +var Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + PrintDefaults() +} + +// NFlag returns the number of flags that have been set. +func (f *FlagSet) NFlag() int { return len(f.actual) } + +// NFlag returns the number of command-line flags that have been set. +func NFlag() int { return len(CommandLine.actual) } + +// Arg returns the i'th argument. Arg(0) is the first remaining argument +// after flags have been processed. +func (f *FlagSet) Arg(i int) string { + if i < 0 || i >= len(f.args) { + return "" + } + return f.args[i] +} + +// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument +// after flags have been processed. +func Arg(i int) string { + return CommandLine.Arg(i) +} + +// NArg is the number of arguments remaining after flags have been processed. +func (f *FlagSet) NArg() int { return len(f.args) } + +// NArg is the number of arguments remaining after flags have been processed. +func NArg() int { return len(CommandLine.args) } + +// Args returns the non-flag arguments. +func (f *FlagSet) Args() []string { return f.args } + +// Args returns the non-flag command-line arguments. +func Args() []string { return CommandLine.args } + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func (f *FlagSet) Var(value Value, name string, usage string) { + f.VarP(value, name, "", usage) +} + +// Like VarP, but returns the flag created +func (f *FlagSet) VarPF(value Value, name, shorthand, usage string) *Flag { + // Remember the default value as a string; it won't change. + flag := &Flag{ + Name: name, + Shorthand: shorthand, + Usage: usage, + Value: value, + DefValue: value.String(), + } + f.AddFlag(flag) + return flag +} + +// Like Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) VarP(value Value, name, shorthand, usage string) { + _ = f.VarPF(value, name, shorthand, usage) +} + +func (f *FlagSet) AddFlag(flag *Flag) { + // Call normalizeFlagName function only once + var normalizedFlagName NormalizedName = f.normalizeFlagName(flag.Name) + + _, alreadythere := f.formal[normalizedFlagName] + if alreadythere { + msg := fmt.Sprintf("%s flag redefined: %s", f.name, flag.Name) + fmt.Fprintln(f.out(), msg) + panic(msg) // Happens only if flags are declared with identical names + } + if f.formal == nil { + f.formal = make(map[NormalizedName]*Flag) + } + + flag.Name = string(normalizedFlagName) + f.formal[normalizedFlagName] = flag + + if len(flag.Shorthand) == 0 { + return + } + if len(flag.Shorthand) > 1 { + fmt.Fprintf(f.out(), "%s shorthand more than ASCII character: %s\n", f.name, flag.Shorthand) + panic("shorthand is more than one character") + } + if f.shorthands == nil { + f.shorthands = make(map[byte]*Flag) + } + c := flag.Shorthand[0] + old, alreadythere := f.shorthands[c] + if alreadythere { + fmt.Fprintf(f.out(), "%s shorthand reused: %q for %s already used for %s\n", f.name, c, flag.Name, old.Name) + panic("shorthand redefinition") + } + f.shorthands[c] = flag +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func Var(value Value, name string, usage string) { + CommandLine.VarP(value, name, "", usage) +} + +// Like Var, but accepts a shorthand letter that can be used after a single dash. +func VarP(value Value, name, shorthand, usage string) { + CommandLine.VarP(value, name, shorthand, usage) +} + +// failf prints to standard error a formatted error and usage message and +// returns the error. +func (f *FlagSet) failf(format string, a ...interface{}) error { + err := fmt.Errorf(format, a...) + fmt.Fprintln(f.out(), err) + f.usage() + return err +} + +// usage calls the Usage method for the flag set, or the usage function if +// the flag set is CommandLine. +func (f *FlagSet) usage() { + if f == CommandLine { + Usage() + } else if f.Usage == nil { + defaultUsage(f) + } else { + f.Usage() + } +} + +func (f *FlagSet) setFlag(flag *Flag, value string, origArg string) error { + if err := flag.Value.Set(value); err != nil { + return f.failf("invalid argument %q for %s: %v", value, origArg, err) + } + // mark as visited for Visit() + if f.actual == nil { + f.actual = make(map[NormalizedName]*Flag) + } + f.actual[f.normalizeFlagName(flag.Name)] = flag + flag.Changed = true + if len(flag.Deprecated) > 0 { + fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated) + } + return nil +} + +func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error) { + a = args + name := s[2:] + if len(name) == 0 || name[0] == '-' || name[0] == '=' { + err = f.failf("bad flag syntax: %s", s) + return + } + split := strings.SplitN(name, "=", 2) + name = split[0] + flag, alreadythere := f.formal[f.normalizeFlagName(name)] + if !alreadythere { + if name == "help" { // special case for nice help message. + f.usage() + return a, ErrHelp + } + err = f.failf("unknown flag: --%s", name) + return + } + var value string + if len(split) == 2 { + // '--flag=arg' + value = split[1] + } else if len(flag.NoOptDefVal) > 0 { + // '--flag' (arg was optional) + value = flag.NoOptDefVal + } else if len(a) > 0 { + // '--flag arg' + value = a[0] + a = a[1:] + } else { + // '--flag' (arg was required) + err = f.failf("flag needs an argument: %s", s) + return + } + err = f.setFlag(flag, value, s) + return +} + +func (f *FlagSet) parseSingleShortArg(shorthands string, args []string) (outShorts string, outArgs []string, err error) { + outArgs = args + outShorts = shorthands[1:] + c := shorthands[0] + + flag, alreadythere := f.shorthands[c] + if !alreadythere { + if c == 'h' { // special case for nice help message. + f.usage() + err = ErrHelp + return + } + //TODO continue on error + err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) + return + } + var value string + if len(shorthands) > 2 && shorthands[1] == '=' { + value = shorthands[2:] + outShorts = "" + } else if len(flag.NoOptDefVal) > 0 { + value = flag.NoOptDefVal + } else if len(shorthands) > 1 { + value = shorthands[1:] + outShorts = "" + } else if len(args) > 0 { + value = args[0] + outArgs = args[1:] + } else { + err = f.failf("flag needs an argument: %q in -%s", c, shorthands) + return + } + err = f.setFlag(flag, value, shorthands) + return +} + +func (f *FlagSet) parseShortArg(s string, args []string) (a []string, err error) { + a = args + shorthands := s[1:] + + for len(shorthands) > 0 { + shorthands, a, err = f.parseSingleShortArg(shorthands, args) + if err != nil { + return + } + } + + return +} + +func (f *FlagSet) parseArgs(args []string) (err error) { + for len(args) > 0 { + s := args[0] + args = args[1:] + if len(s) == 0 || s[0] != '-' || len(s) == 1 { + if !f.interspersed { + f.args = append(f.args, s) + f.args = append(f.args, args...) + return nil + } + f.args = append(f.args, s) + continue + } + + if s[1] == '-' { + if len(s) == 2 { // "--" terminates the flags + f.args = append(f.args, args...) + break + } + args, err = f.parseLongArg(s, args) + } else { + args, err = f.parseShortArg(s, args) + } + if err != nil { + return + } + } + return +} + +// Parse parses flag definitions from the argument list, which should not +// include the command name. Must be called after all flags in the FlagSet +// are defined and before flags are accessed by the program. +// The return value will be ErrHelp if -help was set but not defined. +func (f *FlagSet) Parse(arguments []string) error { + f.parsed = true + f.args = make([]string, 0, len(arguments)) + err := f.parseArgs(arguments) + if err != nil { + switch f.errorHandling { + case ContinueOnError: + return err + case ExitOnError: + os.Exit(2) + case PanicOnError: + panic(err) + } + } + return nil +} + +// Parsed reports whether f.Parse has been called. +func (f *FlagSet) Parsed() bool { + return f.parsed +} + +// Parse parses the command-line flags from os.Args[1:]. Must be called +// after all flags are defined and before flags are accessed by the program. +func Parse() { + // Ignore errors; CommandLine is set for ExitOnError. + CommandLine.Parse(os.Args[1:]) +} + +// Whether to support interspersed option/non-option arguments. +func SetInterspersed(interspersed bool) { + CommandLine.SetInterspersed(interspersed) +} + +// Parsed returns true if the command-line flags have been parsed. +func Parsed() bool { + return CommandLine.Parsed() +} + +// The default set of command-line flags, parsed from os.Args. +var CommandLine = NewFlagSet(os.Args[0], ExitOnError) + +// NewFlagSet returns a new, empty flag set with the specified name and +// error handling property. +func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { + f := &FlagSet{ + name: name, + errorHandling: errorHandling, + interspersed: true, + } + return f +} + +// Whether to support interspersed option/non-option arguments. +func (f *FlagSet) SetInterspersed(interspersed bool) { + f.interspersed = interspersed +} + +// Init sets the name and error handling property for a flag set. +// By default, the zero FlagSet uses an empty name and the +// ContinueOnError error handling policy. +func (f *FlagSet) Init(name string, errorHandling ErrorHandling) { + f.name = name + f.errorHandling = errorHandling +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/flag_test.go b/Godeps/_workspace/src/github.com/spf13/pflag/flag_test.go new file mode 100644 index 0000000..a5928e5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/flag_test.go @@ -0,0 +1,755 @@ +// Copyright 2009 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 pflag + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "reflect" + "sort" + "strings" + "testing" + "time" +) + +var ( + test_bool = Bool("test_bool", false, "bool value") + test_int = Int("test_int", 0, "int value") + test_int64 = Int64("test_int64", 0, "int64 value") + test_uint = Uint("test_uint", 0, "uint value") + test_uint64 = Uint64("test_uint64", 0, "uint64 value") + test_string = String("test_string", "0", "string value") + test_float64 = Float64("test_float64", 0, "float64 value") + test_duration = Duration("test_duration", 0, "time.Duration value") + test_optional_int = Int("test_optional_int", 0, "optional int value") + normalizeFlagNameInvocations = 0 +) + +func boolString(s string) string { + if s == "0" { + return "false" + } + return "true" +} + +func TestEverything(t *testing.T) { + m := make(map[string]*Flag) + desired := "0" + visitor := func(f *Flag) { + if len(f.Name) > 5 && f.Name[0:5] == "test_" { + m[f.Name] = f + ok := false + switch { + case f.Value.String() == desired: + ok = true + case f.Name == "test_bool" && f.Value.String() == boolString(desired): + ok = true + case f.Name == "test_duration" && f.Value.String() == desired+"s": + ok = true + } + if !ok { + t.Error("Visit: bad value", f.Value.String(), "for", f.Name) + } + } + } + VisitAll(visitor) + if len(m) != 9 { + t.Error("VisitAll misses some flags") + for k, v := range m { + t.Log(k, *v) + } + } + m = make(map[string]*Flag) + Visit(visitor) + if len(m) != 0 { + t.Errorf("Visit sees unset flags") + for k, v := range m { + t.Log(k, *v) + } + } + // Now set all flags + Set("test_bool", "true") + Set("test_int", "1") + Set("test_int64", "1") + Set("test_uint", "1") + Set("test_uint64", "1") + Set("test_string", "1") + Set("test_float64", "1") + Set("test_duration", "1s") + Set("test_optional_int", "1") + desired = "1" + Visit(visitor) + if len(m) != 9 { + t.Error("Visit fails after set") + for k, v := range m { + t.Log(k, *v) + } + } + // Now test they're visited in sort order. + var flagNames []string + Visit(func(f *Flag) { flagNames = append(flagNames, f.Name) }) + if !sort.StringsAreSorted(flagNames) { + t.Errorf("flag names not sorted: %v", flagNames) + } +} + +func TestUsage(t *testing.T) { + called := false + ResetForTesting(func() { called = true }) + if GetCommandLine().Parse([]string{"--x"}) == nil { + t.Error("parse did not fail for unknown flag") + } + if !called { + t.Error("did not call Usage for unknown flag") + } +} + +func TestAnnotation(t *testing.T) { + f := NewFlagSet("shorthand", ContinueOnError) + + if err := f.SetAnnotation("missing-flag", "key", nil); err == nil { + t.Errorf("Expected error setting annotation on non-existent flag") + } + + f.StringP("stringa", "a", "", "string value") + if err := f.SetAnnotation("stringa", "key", nil); err != nil { + t.Errorf("Unexpected error setting new nil annotation: %v", err) + } + if annotation := f.Lookup("stringa").Annotations["key"]; annotation != nil { + t.Errorf("Unexpected annotation: %v", annotation) + } + + f.StringP("stringb", "b", "", "string2 value") + if err := f.SetAnnotation("stringb", "key", []string{"value1"}); err != nil { + t.Errorf("Unexpected error setting new annotation: %v", err) + } + if annotation := f.Lookup("stringb").Annotations["key"]; !reflect.DeepEqual(annotation, []string{"value1"}) { + t.Errorf("Unexpected annotation: %v", annotation) + } + + if err := f.SetAnnotation("stringb", "key", []string{"value2"}); err != nil { + t.Errorf("Unexpected error updating annotation: %v", err) + } + if annotation := f.Lookup("stringb").Annotations["key"]; !reflect.DeepEqual(annotation, []string{"value2"}) { + t.Errorf("Unexpected annotation: %v", annotation) + } +} + +func testParse(f *FlagSet, t *testing.T) { + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + boolFlag := f.Bool("bool", false, "bool value") + bool2Flag := f.Bool("bool2", false, "bool2 value") + bool3Flag := f.Bool("bool3", false, "bool3 value") + intFlag := f.Int("int", 0, "int value") + int8Flag := f.Int8("int8", 0, "int value") + int32Flag := f.Int32("int32", 0, "int value") + int64Flag := f.Int64("int64", 0, "int64 value") + uintFlag := f.Uint("uint", 0, "uint value") + uint8Flag := f.Uint8("uint8", 0, "uint value") + uint16Flag := f.Uint16("uint16", 0, "uint value") + uint32Flag := f.Uint32("uint32", 0, "uint value") + uint64Flag := f.Uint64("uint64", 0, "uint64 value") + stringFlag := f.String("string", "0", "string value") + float32Flag := f.Float32("float32", 0, "float32 value") + float64Flag := f.Float64("float64", 0, "float64 value") + ipFlag := f.IP("ip", net.ParseIP("127.0.0.1"), "ip value") + maskFlag := f.IPMask("mask", ParseIPv4Mask("0.0.0.0"), "mask value") + durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value") + optionalIntNoValueFlag := f.Int("optional-int-no-value", 0, "int value") + f.Lookup("optional-int-no-value").NoOptDefVal = "9" + optionalIntWithValueFlag := f.Int("optional-int-with-value", 0, "int value") + f.Lookup("optional-int-no-value").NoOptDefVal = "9" + extra := "one-extra-argument" + args := []string{ + "--bool", + "--bool2=true", + "--bool3=false", + "--int=22", + "--int8=-8", + "--int32=-32", + "--int64=0x23", + "--uint", "24", + "--uint8=8", + "--uint16=16", + "--uint32=32", + "--uint64=25", + "--string=hello", + "--float32=-172e12", + "--float64=2718e28", + "--ip=10.11.12.13", + "--mask=255.255.255.0", + "--duration=2m", + "--optional-int-no-value", + "--optional-int-with-value=42", + extra, + } + if err := f.Parse(args); err != nil { + t.Fatal(err) + } + if !f.Parsed() { + t.Error("f.Parse() = false after Parse") + } + if *boolFlag != true { + t.Error("bool flag should be true, is ", *boolFlag) + } + if v, err := f.GetBool("bool"); err != nil || v != *boolFlag { + t.Error("GetBool does not work.") + } + if *bool2Flag != true { + t.Error("bool2 flag should be true, is ", *bool2Flag) + } + if *bool3Flag != false { + t.Error("bool3 flag should be false, is ", *bool2Flag) + } + if *intFlag != 22 { + t.Error("int flag should be 22, is ", *intFlag) + } + if v, err := f.GetInt("int"); err != nil || v != *intFlag { + t.Error("GetInt does not work.") + } + if *int8Flag != -8 { + t.Error("int8 flag should be 0x23, is ", *int8Flag) + } + if v, err := f.GetInt8("int8"); err != nil || v != *int8Flag { + t.Error("GetInt8 does not work.") + } + if *int32Flag != -32 { + t.Error("int32 flag should be 0x23, is ", *int32Flag) + } + if v, err := f.GetInt32("int32"); err != nil || v != *int32Flag { + t.Error("GetInt32 does not work.") + } + if *int64Flag != 0x23 { + t.Error("int64 flag should be 0x23, is ", *int64Flag) + } + if v, err := f.GetInt64("int64"); err != nil || v != *int64Flag { + t.Error("GetInt64 does not work.") + } + if *uintFlag != 24 { + t.Error("uint flag should be 24, is ", *uintFlag) + } + if v, err := f.GetUint("uint"); err != nil || v != *uintFlag { + t.Error("GetUint does not work.") + } + if *uint8Flag != 8 { + t.Error("uint8 flag should be 8, is ", *uint8Flag) + } + if v, err := f.GetUint8("uint8"); err != nil || v != *uint8Flag { + t.Error("GetUint8 does not work.") + } + if *uint16Flag != 16 { + t.Error("uint16 flag should be 16, is ", *uint16Flag) + } + if v, err := f.GetUint16("uint16"); err != nil || v != *uint16Flag { + t.Error("GetUint16 does not work.") + } + if *uint32Flag != 32 { + t.Error("uint32 flag should be 32, is ", *uint32Flag) + } + if v, err := f.GetUint32("uint32"); err != nil || v != *uint32Flag { + t.Error("GetUint32 does not work.") + } + if *uint64Flag != 25 { + t.Error("uint64 flag should be 25, is ", *uint64Flag) + } + if v, err := f.GetUint64("uint64"); err != nil || v != *uint64Flag { + t.Error("GetUint64 does not work.") + } + if *stringFlag != "hello" { + t.Error("string flag should be `hello`, is ", *stringFlag) + } + if v, err := f.GetString("string"); err != nil || v != *stringFlag { + t.Error("GetString does not work.") + } + if *float32Flag != -172e12 { + t.Error("float32 flag should be -172e12, is ", *float32Flag) + } + if v, err := f.GetFloat32("float32"); err != nil || v != *float32Flag { + t.Errorf("GetFloat32 returned %v but float32Flag was %v", v, *float32Flag) + } + if *float64Flag != 2718e28 { + t.Error("float64 flag should be 2718e28, is ", *float64Flag) + } + if v, err := f.GetFloat64("float64"); err != nil || v != *float64Flag { + t.Errorf("GetFloat64 returned %v but float64Flag was %v", v, *float64Flag) + } + if !(*ipFlag).Equal(net.ParseIP("10.11.12.13")) { + t.Error("ip flag should be 10.11.12.13, is ", *ipFlag) + } + if v, err := f.GetIP("ip"); err != nil || !v.Equal(*ipFlag) { + t.Errorf("GetIP returned %v but ipFlag was %v", v, *ipFlag) + } + if (*maskFlag).String() != ParseIPv4Mask("255.255.255.0").String() { + t.Error("mask flag should be 255.255.255.0, is ", (*maskFlag).String()) + } + if v, err := f.GetIPv4Mask("mask"); err != nil || v.String() != (*maskFlag).String() { + t.Errorf("GetIP returned %v but maskFlag was %v", v, *maskFlag, err) + } + if *durationFlag != 2*time.Minute { + t.Error("duration flag should be 2m, is ", *durationFlag) + } + if v, err := f.GetDuration("duration"); err != nil || v != *durationFlag { + t.Error("GetDuration does not work.") + } + if _, err := f.GetInt("duration"); err == nil { + t.Error("GetInt parsed a time.Duration?!?!") + } + if *optionalIntNoValueFlag != 9 { + t.Error("optional int flag should be the default value, is ", *optionalIntNoValueFlag) + } + if *optionalIntWithValueFlag != 42 { + t.Error("optional int flag should be 42, is ", *optionalIntWithValueFlag) + } + if len(f.Args()) != 1 { + t.Error("expected one argument, got", len(f.Args())) + } else if f.Args()[0] != extra { + t.Errorf("expected argument %q got %q", extra, f.Args()[0]) + } +} + +func TestShorthand(t *testing.T) { + f := NewFlagSet("shorthand", ContinueOnError) + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + boolaFlag := f.BoolP("boola", "a", false, "bool value") + boolbFlag := f.BoolP("boolb", "b", false, "bool2 value") + boolcFlag := f.BoolP("boolc", "c", false, "bool3 value") + booldFlag := f.BoolP("boold", "d", false, "bool4 value") + stringaFlag := f.StringP("stringa", "s", "0", "string value") + stringzFlag := f.StringP("stringz", "z", "0", "string value") + extra := "interspersed-argument" + notaflag := "--i-look-like-a-flag" + args := []string{ + "-ab", + extra, + "-cs", + "hello", + "-z=something", + "-d=true", + "--", + notaflag, + } + f.SetOutput(ioutil.Discard) + if err := f.Parse(args); err != nil { + t.Error("expected no error, got ", err) + } + if !f.Parsed() { + t.Error("f.Parse() = false after Parse") + } + if *boolaFlag != true { + t.Error("boola flag should be true, is ", *boolaFlag) + } + if *boolbFlag != true { + t.Error("boolb flag should be true, is ", *boolbFlag) + } + if *boolcFlag != true { + t.Error("boolc flag should be true, is ", *boolcFlag) + } + if *booldFlag != true { + t.Error("boold flag should be true, is ", *booldFlag) + } + if *stringaFlag != "hello" { + t.Error("stringa flag should be `hello`, is ", *stringaFlag) + } + if *stringzFlag != "something" { + t.Error("stringz flag should be `something`, is ", *stringzFlag) + } + if len(f.Args()) != 2 { + t.Error("expected one argument, got", len(f.Args())) + } else if f.Args()[0] != extra { + t.Errorf("expected argument %q got %q", extra, f.Args()[0]) + } else if f.Args()[1] != notaflag { + t.Errorf("expected argument %q got %q", notaflag, f.Args()[1]) + } +} + +func TestParse(t *testing.T) { + ResetForTesting(func() { t.Error("bad parse") }) + testParse(GetCommandLine(), t) +} + +func TestFlagSetParse(t *testing.T) { + testParse(NewFlagSet("test", ContinueOnError), t) +} + +func replaceSeparators(name string, from []string, to string) string { + result := name + for _, sep := range from { + result = strings.Replace(result, sep, to, -1) + } + // Type convert to indicate normalization has been done. + return result +} + +func wordSepNormalizeFunc(f *FlagSet, name string) NormalizedName { + seps := []string{"-", "_"} + name = replaceSeparators(name, seps, ".") + normalizeFlagNameInvocations++ + + return NormalizedName(name) +} + +func testWordSepNormalizedNames(args []string, t *testing.T) { + f := NewFlagSet("normalized", ContinueOnError) + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + withDashFlag := f.Bool("with-dash-flag", false, "bool value") + // Set this after some flags have been added and before others. + f.SetNormalizeFunc(wordSepNormalizeFunc) + withUnderFlag := f.Bool("with_under_flag", false, "bool value") + withBothFlag := f.Bool("with-both_flag", false, "bool value") + if err := f.Parse(args); err != nil { + t.Fatal(err) + } + if !f.Parsed() { + t.Error("f.Parse() = false after Parse") + } + if *withDashFlag != true { + t.Error("withDashFlag flag should be true, is ", *withDashFlag) + } + if *withUnderFlag != true { + t.Error("withUnderFlag flag should be true, is ", *withUnderFlag) + } + if *withBothFlag != true { + t.Error("withBothFlag flag should be true, is ", *withBothFlag) + } +} + +func TestWordSepNormalizedNames(t *testing.T) { + args := []string{ + "--with-dash-flag", + "--with-under-flag", + "--with-both-flag", + } + testWordSepNormalizedNames(args, t) + + args = []string{ + "--with_dash_flag", + "--with_under_flag", + "--with_both_flag", + } + testWordSepNormalizedNames(args, t) + + args = []string{ + "--with-dash_flag", + "--with-under_flag", + "--with-both_flag", + } + testWordSepNormalizedNames(args, t) +} + +func aliasAndWordSepFlagNames(f *FlagSet, name string) NormalizedName { + seps := []string{"-", "_"} + + oldName := replaceSeparators("old-valid_flag", seps, ".") + newName := replaceSeparators("valid-flag", seps, ".") + + name = replaceSeparators(name, seps, ".") + switch name { + case oldName: + name = newName + break + } + + return NormalizedName(name) +} + +func TestCustomNormalizedNames(t *testing.T) { + f := NewFlagSet("normalized", ContinueOnError) + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + + validFlag := f.Bool("valid-flag", false, "bool value") + f.SetNormalizeFunc(aliasAndWordSepFlagNames) + someOtherFlag := f.Bool("some-other-flag", false, "bool value") + + args := []string{"--old_valid_flag", "--some-other_flag"} + if err := f.Parse(args); err != nil { + t.Fatal(err) + } + + if *validFlag != true { + t.Errorf("validFlag is %v even though we set the alias --old_valid_falg", *validFlag) + } + if *someOtherFlag != true { + t.Error("someOtherFlag should be true, is ", *someOtherFlag) + } +} + +// Every flag we add, the name (displayed also in usage) should normalized +func TestNormalizationFuncShouldChangeFlagName(t *testing.T) { + // Test normalization after addition + f := NewFlagSet("normalized", ContinueOnError) + + f.Bool("valid_flag", false, "bool value") + if f.Lookup("valid_flag").Name != "valid_flag" { + t.Error("The new flag should have the name 'valid_flag' instead of ", f.Lookup("valid_flag").Name) + } + + f.SetNormalizeFunc(wordSepNormalizeFunc) + if f.Lookup("valid_flag").Name != "valid.flag" { + t.Error("The new flag should have the name 'valid.flag' instead of ", f.Lookup("valid_flag").Name) + } + + // Test normalization before addition + f = NewFlagSet("normalized", ContinueOnError) + f.SetNormalizeFunc(wordSepNormalizeFunc) + + f.Bool("valid_flag", false, "bool value") + if f.Lookup("valid_flag").Name != "valid.flag" { + t.Error("The new flag should have the name 'valid.flag' instead of ", f.Lookup("valid_flag").Name) + } +} + +// Declare a user-defined flag type. +type flagVar []string + +func (f *flagVar) String() string { + return fmt.Sprint([]string(*f)) +} + +func (f *flagVar) Set(value string) error { + *f = append(*f, value) + return nil +} + +func (f *flagVar) Type() string { + return "flagVar" +} + +func TestUserDefined(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + var v flagVar + flags.VarP(&v, "v", "v", "usage") + if err := flags.Parse([]string{"--v=1", "-v2", "-v", "3"}); err != nil { + t.Error(err) + } + if len(v) != 3 { + t.Fatal("expected 3 args; got ", len(v)) + } + expect := "[1 2 3]" + if v.String() != expect { + t.Errorf("expected value %q got %q", expect, v.String()) + } +} + +func TestSetOutput(t *testing.T) { + var flags FlagSet + var buf bytes.Buffer + flags.SetOutput(&buf) + flags.Init("test", ContinueOnError) + flags.Parse([]string{"--unknown"}) + if out := buf.String(); !strings.Contains(out, "--unknown") { + t.Logf("expected output mentioning unknown; got %q", out) + } +} + +// This tests that one can reset the flags. This still works but not well, and is +// superseded by FlagSet. +func TestChangingArgs(t *testing.T) { + ResetForTesting(func() { t.Fatal("bad parse") }) + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + os.Args = []string{"cmd", "--before", "subcmd"} + before := Bool("before", false, "") + if err := GetCommandLine().Parse(os.Args[1:]); err != nil { + t.Fatal(err) + } + cmd := Arg(0) + os.Args = []string{"subcmd", "--after", "args"} + after := Bool("after", false, "") + Parse() + args := Args() + + if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" { + t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args) + } +} + +// Test that -help invokes the usage message and returns ErrHelp. +func TestHelp(t *testing.T) { + var helpCalled = false + fs := NewFlagSet("help test", ContinueOnError) + fs.Usage = func() { helpCalled = true } + var flag bool + fs.BoolVar(&flag, "flag", false, "regular flag") + // Regular flag invocation should work + err := fs.Parse([]string{"--flag=true"}) + if err != nil { + t.Fatal("expected no error; got ", err) + } + if !flag { + t.Error("flag was not set by --flag") + } + if helpCalled { + t.Error("help called for regular flag") + helpCalled = false // reset for next test + } + // Help flag should work as expected. + err = fs.Parse([]string{"--help"}) + if err == nil { + t.Fatal("error expected") + } + if err != ErrHelp { + t.Fatal("expected ErrHelp; got ", err) + } + if !helpCalled { + t.Fatal("help was not called") + } + // If we define a help flag, that should override. + var help bool + fs.BoolVar(&help, "help", false, "help flag") + helpCalled = false + err = fs.Parse([]string{"--help"}) + if err != nil { + t.Fatal("expected no error for defined --help; got ", err) + } + if helpCalled { + t.Fatal("help was called; should not have been for defined help flag") + } +} + +func TestNoInterspersed(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + f.SetInterspersed(false) + f.Bool("true", true, "always true") + f.Bool("false", false, "always false") + err := f.Parse([]string{"--true", "break", "--false"}) + if err != nil { + t.Fatal("expected no error; got ", err) + } + args := f.Args() + if len(args) != 2 || args[0] != "break" || args[1] != "--false" { + t.Fatal("expected interspersed options/non-options to fail") + } +} + +func TestTermination(t *testing.T) { + f := NewFlagSet("termination", ContinueOnError) + boolFlag := f.BoolP("bool", "l", false, "bool value") + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + arg1 := "ls" + arg2 := "-l" + args := []string{ + "--", + arg1, + arg2, + } + f.SetOutput(ioutil.Discard) + if err := f.Parse(args); err != nil { + t.Fatal("expected no error; got ", err) + } + if !f.Parsed() { + t.Error("f.Parse() = false after Parse") + } + if *boolFlag { + t.Error("expected boolFlag=false, got true") + } + if len(f.Args()) != 2 { + t.Errorf("expected 2 arguments, got %d: %v", len(f.Args()), f.Args()) + } + if f.Args()[0] != arg1 { + t.Errorf("expected argument %q got %q", arg1, f.Args()[0]) + } + if f.Args()[1] != arg2 { + t.Errorf("expected argument %q got %q", arg2, f.Args()[1]) + } +} + +func TestDeprecatedFlagInDocs(t *testing.T) { + f := NewFlagSet("bob", ContinueOnError) + f.Bool("badflag", true, "always true") + f.MarkDeprecated("badflag", "use --good-flag instead") + + out := new(bytes.Buffer) + f.SetOutput(out) + f.PrintDefaults() + + if strings.Contains(out.String(), "badflag") { + t.Errorf("found deprecated flag in usage!") + } +} + +func parseReturnStderr(t *testing.T, f *FlagSet, args []string) (string, error) { + oldStderr := os.Stderr + r, w, _ := os.Pipe() + os.Stderr = w + + err := f.Parse(args) + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + w.Close() + os.Stderr = oldStderr + out := <-outC + + return out, err +} + +func TestDeprecatedFlagUsage(t *testing.T) { + f := NewFlagSet("bob", ContinueOnError) + f.Bool("badflag", true, "always true") + usageMsg := "use --good-flag instead" + f.MarkDeprecated("badflag", usageMsg) + + args := []string{"--badflag"} + out, err := parseReturnStderr(t, f, args) + if err != nil { + t.Fatal("expected no error; got ", err) + } + + if !strings.Contains(out, usageMsg) { + t.Errorf("usageMsg not printed when using a deprecated flag!") + } +} + +func TestDeprecatedFlagUsageNormalized(t *testing.T) { + f := NewFlagSet("bob", ContinueOnError) + f.Bool("bad-double_flag", true, "always true") + f.SetNormalizeFunc(wordSepNormalizeFunc) + usageMsg := "use --good-flag instead" + f.MarkDeprecated("bad_double-flag", usageMsg) + + args := []string{"--bad_double_flag"} + out, err := parseReturnStderr(t, f, args) + if err != nil { + t.Fatal("expected no error; got ", err) + } + + if !strings.Contains(out, usageMsg) { + t.Errorf("usageMsg not printed when using a deprecated flag!") + } +} + +// Name normalization function should be called only once on flag addition +func TestMultipleNormalizeFlagNameInvocations(t *testing.T) { + normalizeFlagNameInvocations = 0 + + f := NewFlagSet("normalized", ContinueOnError) + f.SetNormalizeFunc(wordSepNormalizeFunc) + f.Bool("with_under_flag", false, "bool value") + + if normalizeFlagNameInvocations != 1 { + t.Fatal("Expected normalizeFlagNameInvocations to be 1; got ", normalizeFlagNameInvocations) + } +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/float32.go b/Godeps/_workspace/src/github.com/spf13/pflag/float32.go new file mode 100644 index 0000000..30174cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/float32.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- float32 Value +type float32Value float32 + +func newFloat32Value(val float32, p *float32) *float32Value { + *p = val + return (*float32Value)(p) +} + +func (f *float32Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 32) + *f = float32Value(v) + return err +} + +func (f *float32Value) Type() string { + return "float32" +} + +func (f *float32Value) String() string { return fmt.Sprintf("%v", *f) } + +func float32Conv(sval string) (interface{}, error) { + v, err := strconv.ParseFloat(sval, 32) + if err != nil { + return 0, err + } + return float32(v), nil +} + +// GetFloat32 return the float32 value of a flag with the given name +func (f *FlagSet) GetFloat32(name string) (float32, error) { + val, err := f.getFlagType(name, "float32", float32Conv) + if err != nil { + return 0, err + } + return val.(float32), nil +} + +// Float32Var defines a float32 flag with specified name, default value, and usage string. +// The argument p points to a float32 variable in which to store the value of the flag. +func (f *FlagSet) Float32Var(p *float32, name string, value float32, usage string) { + f.VarP(newFloat32Value(value, p), name, "", usage) +} + +// Like Float32Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float32VarP(p *float32, name, shorthand string, value float32, usage string) { + f.VarP(newFloat32Value(value, p), name, shorthand, usage) +} + +// Float32Var defines a float32 flag with specified name, default value, and usage string. +// The argument p points to a float32 variable in which to store the value of the flag. +func Float32Var(p *float32, name string, value float32, usage string) { + CommandLine.VarP(newFloat32Value(value, p), name, "", usage) +} + +// Like Float32Var, but accepts a shorthand letter that can be used after a single dash. +func Float32VarP(p *float32, name, shorthand string, value float32, usage string) { + CommandLine.VarP(newFloat32Value(value, p), name, shorthand, usage) +} + +// Float32 defines a float32 flag with specified name, default value, and usage string. +// The return value is the address of a float32 variable that stores the value of the flag. +func (f *FlagSet) Float32(name string, value float32, usage string) *float32 { + p := new(float32) + f.Float32VarP(p, name, "", value, usage) + return p +} + +// Like Float32, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float32P(name, shorthand string, value float32, usage string) *float32 { + p := new(float32) + f.Float32VarP(p, name, shorthand, value, usage) + return p +} + +// Float32 defines a float32 flag with specified name, default value, and usage string. +// The return value is the address of a float32 variable that stores the value of the flag. +func Float32(name string, value float32, usage string) *float32 { + return CommandLine.Float32P(name, "", value, usage) +} + +// Like Float32, but accepts a shorthand letter that can be used after a single dash. +func Float32P(name, shorthand string, value float32, usage string) *float32 { + return CommandLine.Float32P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/float64.go b/Godeps/_workspace/src/github.com/spf13/pflag/float64.go new file mode 100644 index 0000000..10e17e4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/float64.go @@ -0,0 +1,87 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- float64 Value +type float64Value float64 + +func newFloat64Value(val float64, p *float64) *float64Value { + *p = val + return (*float64Value)(p) +} + +func (f *float64Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + *f = float64Value(v) + return err +} + +func (f *float64Value) Type() string { + return "float64" +} + +func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) } + +func float64Conv(sval string) (interface{}, error) { + return strconv.ParseFloat(sval, 64) +} + +// GetFloat64 return the float64 value of a flag with the given name +func (f *FlagSet) GetFloat64(name string) (float64, error) { + val, err := f.getFlagType(name, "float64", float64Conv) + if err != nil { + return 0, err + } + return val.(float64), nil +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string) { + f.VarP(newFloat64Value(value, p), name, "", usage) +} + +// Like Float64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float64VarP(p *float64, name, shorthand string, value float64, usage string) { + f.VarP(newFloat64Value(value, p), name, shorthand, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func Float64Var(p *float64, name string, value float64, usage string) { + CommandLine.VarP(newFloat64Value(value, p), name, "", usage) +} + +// Like Float64Var, but accepts a shorthand letter that can be used after a single dash. +func Float64VarP(p *float64, name, shorthand string, value float64, usage string) { + CommandLine.VarP(newFloat64Value(value, p), name, shorthand, usage) +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func (f *FlagSet) Float64(name string, value float64, usage string) *float64 { + p := new(float64) + f.Float64VarP(p, name, "", value, usage) + return p +} + +// Like Float64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float64P(name, shorthand string, value float64, usage string) *float64 { + p := new(float64) + f.Float64VarP(p, name, shorthand, value, usage) + return p +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func Float64(name string, value float64, usage string) *float64 { + return CommandLine.Float64P(name, "", value, usage) +} + +// Like Float64, but accepts a shorthand letter that can be used after a single dash. +func Float64P(name, shorthand string, value float64, usage string) *float64 { + return CommandLine.Float64P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/int.go b/Godeps/_workspace/src/github.com/spf13/pflag/int.go new file mode 100644 index 0000000..23f70dd --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/int.go @@ -0,0 +1,87 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int Value +type intValue int + +func newIntValue(val int, p *int) *intValue { + *p = val + return (*intValue)(p) +} + +func (i *intValue) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = intValue(v) + return err +} + +func (i *intValue) Type() string { + return "int" +} + +func (i *intValue) String() string { return fmt.Sprintf("%v", *i) } + +func intConv(sval string) (interface{}, error) { + return strconv.Atoi(sval) +} + +// GetInt return the int value of a flag with the given name +func (f *FlagSet) GetInt(name string) (int, error) { + val, err := f.getFlagType(name, "int", intConv) + if err != nil { + return 0, err + } + return val.(int), nil +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func (f *FlagSet) IntVar(p *int, name string, value int, usage string) { + f.VarP(newIntValue(value, p), name, "", usage) +} + +// Like IntVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntVarP(p *int, name, shorthand string, value int, usage string) { + f.VarP(newIntValue(value, p), name, shorthand, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func IntVar(p *int, name string, value int, usage string) { + CommandLine.VarP(newIntValue(value, p), name, "", usage) +} + +// Like IntVar, but accepts a shorthand letter that can be used after a single dash. +func IntVarP(p *int, name, shorthand string, value int, usage string) { + CommandLine.VarP(newIntValue(value, p), name, shorthand, usage) +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func (f *FlagSet) Int(name string, value int, usage string) *int { + p := new(int) + f.IntVarP(p, name, "", value, usage) + return p +} + +// Like Int, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntP(name, shorthand string, value int, usage string) *int { + p := new(int) + f.IntVarP(p, name, shorthand, value, usage) + return p +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func Int(name string, value int, usage string) *int { + return CommandLine.IntP(name, "", value, usage) +} + +// Like Int, but accepts a shorthand letter that can be used after a single dash. +func IntP(name, shorthand string, value int, usage string) *int { + return CommandLine.IntP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/int32.go b/Godeps/_workspace/src/github.com/spf13/pflag/int32.go new file mode 100644 index 0000000..515f90b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/int32.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int32 Value +type int32Value int32 + +func newInt32Value(val int32, p *int32) *int32Value { + *p = val + return (*int32Value)(p) +} + +func (i *int32Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 32) + *i = int32Value(v) + return err +} + +func (i *int32Value) Type() string { + return "int32" +} + +func (i *int32Value) String() string { return fmt.Sprintf("%v", *i) } + +func int32Conv(sval string) (interface{}, error) { + v, err := strconv.ParseInt(sval, 0, 32) + if err != nil { + return 0, err + } + return int32(v), nil +} + +// GetInt32 return the int32 value of a flag with the given name +func (f *FlagSet) GetInt32(name string) (int32, error) { + val, err := f.getFlagType(name, "int32", int32Conv) + if err != nil { + return 0, err + } + return val.(int32), nil +} + +// Int32Var defines an int32 flag with specified name, default value, and usage string. +// The argument p points to an int32 variable in which to store the value of the flag. +func (f *FlagSet) Int32Var(p *int32, name string, value int32, usage string) { + f.VarP(newInt32Value(value, p), name, "", usage) +} + +// Like Int32Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int32VarP(p *int32, name, shorthand string, value int32, usage string) { + f.VarP(newInt32Value(value, p), name, shorthand, usage) +} + +// Int32Var defines an int32 flag with specified name, default value, and usage string. +// The argument p points to an int32 variable in which to store the value of the flag. +func Int32Var(p *int32, name string, value int32, usage string) { + CommandLine.VarP(newInt32Value(value, p), name, "", usage) +} + +// Like Int32Var, but accepts a shorthand letter that can be used after a single dash. +func Int32VarP(p *int32, name, shorthand string, value int32, usage string) { + CommandLine.VarP(newInt32Value(value, p), name, shorthand, usage) +} + +// Int32 defines an int32 flag with specified name, default value, and usage string. +// The return value is the address of an int32 variable that stores the value of the flag. +func (f *FlagSet) Int32(name string, value int32, usage string) *int32 { + p := new(int32) + f.Int32VarP(p, name, "", value, usage) + return p +} + +// Like Int32, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int32P(name, shorthand string, value int32, usage string) *int32 { + p := new(int32) + f.Int32VarP(p, name, shorthand, value, usage) + return p +} + +// Int32 defines an int32 flag with specified name, default value, and usage string. +// The return value is the address of an int32 variable that stores the value of the flag. +func Int32(name string, value int32, usage string) *int32 { + return CommandLine.Int32P(name, "", value, usage) +} + +// Like Int32, but accepts a shorthand letter that can be used after a single dash. +func Int32P(name, shorthand string, value int32, usage string) *int32 { + return CommandLine.Int32P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/int64.go b/Godeps/_workspace/src/github.com/spf13/pflag/int64.go new file mode 100644 index 0000000..b77ade4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/int64.go @@ -0,0 +1,87 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int64 Value +type int64Value int64 + +func newInt64Value(val int64, p *int64) *int64Value { + *p = val + return (*int64Value)(p) +} + +func (i *int64Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = int64Value(v) + return err +} + +func (i *int64Value) Type() string { + return "int64" +} + +func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) } + +func int64Conv(sval string) (interface{}, error) { + return strconv.ParseInt(sval, 0, 64) +} + +// GetInt64 return the int64 value of a flag with the given name +func (f *FlagSet) GetInt64(name string) (int64, error) { + val, err := f.getFlagType(name, "int64", int64Conv) + if err != nil { + return 0, err + } + return val.(int64), nil +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string) { + f.VarP(newInt64Value(value, p), name, "", usage) +} + +// Like Int64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int64VarP(p *int64, name, shorthand string, value int64, usage string) { + f.VarP(newInt64Value(value, p), name, shorthand, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func Int64Var(p *int64, name string, value int64, usage string) { + CommandLine.VarP(newInt64Value(value, p), name, "", usage) +} + +// Like Int64Var, but accepts a shorthand letter that can be used after a single dash. +func Int64VarP(p *int64, name, shorthand string, value int64, usage string) { + CommandLine.VarP(newInt64Value(value, p), name, shorthand, usage) +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func (f *FlagSet) Int64(name string, value int64, usage string) *int64 { + p := new(int64) + f.Int64VarP(p, name, "", value, usage) + return p +} + +// Like Int64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int64P(name, shorthand string, value int64, usage string) *int64 { + p := new(int64) + f.Int64VarP(p, name, shorthand, value, usage) + return p +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func Int64(name string, value int64, usage string) *int64 { + return CommandLine.Int64P(name, "", value, usage) +} + +// Like Int64, but accepts a shorthand letter that can be used after a single dash. +func Int64P(name, shorthand string, value int64, usage string) *int64 { + return CommandLine.Int64P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/int8.go b/Godeps/_workspace/src/github.com/spf13/pflag/int8.go new file mode 100644 index 0000000..c51cb4f --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/int8.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int8 Value +type int8Value int8 + +func newInt8Value(val int8, p *int8) *int8Value { + *p = val + return (*int8Value)(p) +} + +func (i *int8Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 8) + *i = int8Value(v) + return err +} + +func (i *int8Value) Type() string { + return "int8" +} + +func (i *int8Value) String() string { return fmt.Sprintf("%v", *i) } + +func int8Conv(sval string) (interface{}, error) { + v, err := strconv.ParseInt(sval, 0, 8) + if err != nil { + return 0, err + } + return int8(v), nil +} + +// GetInt8 return the int8 value of a flag with the given name +func (f *FlagSet) GetInt8(name string) (int8, error) { + val, err := f.getFlagType(name, "int8", int8Conv) + if err != nil { + return 0, err + } + return val.(int8), nil +} + +// Int8Var defines an int8 flag with specified name, default value, and usage string. +// The argument p points to an int8 variable in which to store the value of the flag. +func (f *FlagSet) Int8Var(p *int8, name string, value int8, usage string) { + f.VarP(newInt8Value(value, p), name, "", usage) +} + +// Like Int8Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int8VarP(p *int8, name, shorthand string, value int8, usage string) { + f.VarP(newInt8Value(value, p), name, shorthand, usage) +} + +// Int8Var defines an int8 flag with specified name, default value, and usage string. +// The argument p points to an int8 variable in which to store the value of the flag. +func Int8Var(p *int8, name string, value int8, usage string) { + CommandLine.VarP(newInt8Value(value, p), name, "", usage) +} + +// Like Int8Var, but accepts a shorthand letter that can be used after a single dash. +func Int8VarP(p *int8, name, shorthand string, value int8, usage string) { + CommandLine.VarP(newInt8Value(value, p), name, shorthand, usage) +} + +// Int8 defines an int8 flag with specified name, default value, and usage string. +// The return value is the address of an int8 variable that stores the value of the flag. +func (f *FlagSet) Int8(name string, value int8, usage string) *int8 { + p := new(int8) + f.Int8VarP(p, name, "", value, usage) + return p +} + +// Like Int8, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int8P(name, shorthand string, value int8, usage string) *int8 { + p := new(int8) + f.Int8VarP(p, name, shorthand, value, usage) + return p +} + +// Int8 defines an int8 flag with specified name, default value, and usage string. +// The return value is the address of an int8 variable that stores the value of the flag. +func Int8(name string, value int8, usage string) *int8 { + return CommandLine.Int8P(name, "", value, usage) +} + +// Like Int8, but accepts a shorthand letter that can be used after a single dash. +func Int8P(name, shorthand string, value int8, usage string) *int8 { + return CommandLine.Int8P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/int_slice.go b/Godeps/_workspace/src/github.com/spf13/pflag/int_slice.go new file mode 100644 index 0000000..fb3e67b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/int_slice.go @@ -0,0 +1,113 @@ +package pflag + +import ( + "fmt" + "strconv" + "strings" +) + +// -- intSlice Value +type intSliceValue []int + +func newIntSliceValue(val []int, p *[]int) *intSliceValue { + *p = val + return (*intSliceValue)(p) +} + +func (s *intSliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]int, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.Atoi(d) + if err != nil { + return err + } + + } + *s = intSliceValue(out) + return nil +} + +func (s *intSliceValue) Type() string { + return "intSlice" +} + +func (s *intSliceValue) String() string { + out := make([]string, len(*s)) + for i, d := range *s { + out[i] = fmt.Sprintf("%d", d) + } + return strings.Join(out, ",") +} + +func intSliceConv(val string) (interface{}, error) { + ss := strings.Split(val, ",") + out := make([]int, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.Atoi(d) + if err != nil { + return nil, err + } + + } + return out, nil +} + +// GetIntSlice return the []int value of a flag with the given name +func (f *FlagSet) GetIntSlice(name string) ([]int, error) { + val, err := f.getFlagType(name, "intSlice", intSliceConv) + if err != nil { + return []int{}, err + } + return val.([]int), nil +} + +// IntSliceVar defines a intSlice flag with specified name, default value, and usage string. +// The argument p points to a []int variable in which to store the value of the flag. +func (f *FlagSet) IntSliceVar(p *[]int, name string, value []int, usage string) { + f.VarP(newIntSliceValue(value, p), name, "", usage) +} + +// Like IntSliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntSliceVarP(p *[]int, name, shorthand string, value []int, usage string) { + f.VarP(newIntSliceValue(value, p), name, shorthand, usage) +} + +// IntSliceVar defines a int[] flag with specified name, default value, and usage string. +// The argument p points to a int[] variable in which to store the value of the flag. +func IntSliceVar(p *[]int, name string, value []int, usage string) { + CommandLine.VarP(newIntSliceValue(value, p), name, "", usage) +} + +// Like IntSliceVar, but accepts a shorthand letter that can be used after a single dash. +func IntSliceVarP(p *[]int, name, shorthand string, value []int, usage string) { + CommandLine.VarP(newIntSliceValue(value, p), name, shorthand, usage) +} + +// IntSlice defines a []int flag with specified name, default value, and usage string. +// The return value is the address of a []int variable that stores the value of the flag. +func (f *FlagSet) IntSlice(name string, value []int, usage string) *[]int { + p := make([]int, 0) + f.IntSliceVarP(&p, name, "", value, usage) + return &p +} + +// Like IntSlice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntSliceP(name, shorthand string, value []int, usage string) *[]int { + p := make([]int, 0) + f.IntSliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// IntSlice defines a []int flag with specified name, default value, and usage string. +// The return value is the address of a []int variable that stores the value of the flag. +func IntSlice(name string, value []int, usage string) *[]int { + return CommandLine.IntSliceP(name, "", value, usage) +} + +// Like IntSlice, but accepts a shorthand letter that can be used after a single dash. +func IntSliceP(name, shorthand string, value []int, usage string) *[]int { + return CommandLine.IntSliceP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/int_slice_test.go b/Godeps/_workspace/src/github.com/spf13/pflag/int_slice_test.go new file mode 100644 index 0000000..d162e86 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/int_slice_test.go @@ -0,0 +1,49 @@ +// Copyright 2009 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 pflag + +import ( + "fmt" + "strconv" + "strings" + "testing" +) + +func setUpISFlagSet(isp *[]int) *FlagSet { + f := NewFlagSet("test", ContinueOnError) + f.IntSliceVar(isp, "is", []int{}, "Command seperated list!") + return f +} + +func TestIS(t *testing.T) { + var is []int + f := setUpISFlagSet(&is) + + vals := []string{"1", "2", "4", "3"} + arg := fmt.Sprintf("--is=%s", strings.Join(vals, ",")) + err := f.Parse([]string{arg}) + if err != nil { + t.Fatal("expected no error; got", err) + } + for i, v := range is { + d, err := strconv.Atoi(vals[i]) + if err != nil { + t.Fatalf("got error: %v", err) + } + if d != v { + t.Fatalf("expected is[%d] to be %s but got: %d", i, vals[i], v) + } + } + getIS, err := f.GetIntSlice("is") + for i, v := range getIS { + d, err := strconv.Atoi(vals[i]) + if err != nil { + t.Fatalf("got error: %v", err) + } + if d != v { + t.Fatalf("expected is[%d] to be %s but got: %d from GetIntSlice", i, vals[i], v) + } + } +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/ip.go b/Godeps/_workspace/src/github.com/spf13/pflag/ip.go new file mode 100644 index 0000000..746eefd --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/ip.go @@ -0,0 +1,93 @@ +package pflag + +import ( + "fmt" + "net" +) + +// -- net.IP value +type ipValue net.IP + +func newIPValue(val net.IP, p *net.IP) *ipValue { + *p = val + return (*ipValue)(p) +} + +func (i *ipValue) String() string { return net.IP(*i).String() } +func (i *ipValue) Set(s string) error { + ip := net.ParseIP(s) + if ip == nil { + return fmt.Errorf("failed to parse IP: %q", s) + } + *i = ipValue(ip) + return nil +} + +func (i *ipValue) Type() string { + return "ip" +} + +func ipConv(sval string) (interface{}, error) { + ip := net.ParseIP(sval) + if ip != nil { + return ip, nil + } + return nil, fmt.Errorf("invalid string being converted to IP address: %s", sval) +} + +// GetIP return the net.IP value of a flag with the given name +func (f *FlagSet) GetIP(name string) (net.IP, error) { + val, err := f.getFlagType(name, "ip", ipConv) + if err != nil { + return nil, err + } + return val.(net.IP), nil +} + +// IPVar defines an net.IP flag with specified name, default value, and usage string. +// The argument p points to an net.IP variable in which to store the value of the flag. +func (f *FlagSet) IPVar(p *net.IP, name string, value net.IP, usage string) { + f.VarP(newIPValue(value, p), name, "", usage) +} + +// Like IPVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPVarP(p *net.IP, name, shorthand string, value net.IP, usage string) { + f.VarP(newIPValue(value, p), name, shorthand, usage) +} + +// IPVar defines an net.IP flag with specified name, default value, and usage string. +// The argument p points to an net.IP variable in which to store the value of the flag. +func IPVar(p *net.IP, name string, value net.IP, usage string) { + CommandLine.VarP(newIPValue(value, p), name, "", usage) +} + +// Like IPVar, but accepts a shorthand letter that can be used after a single dash. +func IPVarP(p *net.IP, name, shorthand string, value net.IP, usage string) { + CommandLine.VarP(newIPValue(value, p), name, shorthand, usage) +} + +// IP defines an net.IP flag with specified name, default value, and usage string. +// The return value is the address of an net.IP variable that stores the value of the flag. +func (f *FlagSet) IP(name string, value net.IP, usage string) *net.IP { + p := new(net.IP) + f.IPVarP(p, name, "", value, usage) + return p +} + +// Like IP, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPP(name, shorthand string, value net.IP, usage string) *net.IP { + p := new(net.IP) + f.IPVarP(p, name, shorthand, value, usage) + return p +} + +// IP defines an net.IP flag with specified name, default value, and usage string. +// The return value is the address of an net.IP variable that stores the value of the flag. +func IP(name string, value net.IP, usage string) *net.IP { + return CommandLine.IPP(name, "", value, usage) +} + +// Like IP, but accepts a shorthand letter that can be used after a single dash. +func IPP(name, shorthand string, value net.IP, usage string) *net.IP { + return CommandLine.IPP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/ipmask.go b/Godeps/_workspace/src/github.com/spf13/pflag/ipmask.go new file mode 100644 index 0000000..1b10efb --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/ipmask.go @@ -0,0 +1,122 @@ +package pflag + +import ( + "fmt" + "net" + "strconv" +) + +// -- net.IPMask value +type ipMaskValue net.IPMask + +func newIPMaskValue(val net.IPMask, p *net.IPMask) *ipMaskValue { + *p = val + return (*ipMaskValue)(p) +} + +func (i *ipMaskValue) String() string { return net.IPMask(*i).String() } +func (i *ipMaskValue) Set(s string) error { + ip := ParseIPv4Mask(s) + if ip == nil { + return fmt.Errorf("failed to parse IP mask: %q", s) + } + *i = ipMaskValue(ip) + return nil +} + +func (i *ipMaskValue) Type() string { + return "ipMask" +} + +// Parse IPv4 netmask written in IP form (e.g. 255.255.255.0). +// This function should really belong to the net package. +func ParseIPv4Mask(s string) net.IPMask { + mask := net.ParseIP(s) + if mask == nil { + if len(s) != 8 { + return nil + } + // net.IPMask.String() actually outputs things like ffffff00 + // so write a horrible parser for that as well :-( + m := []int{} + for i := 0; i < 4; i++ { + b := "0x" + s[2*i:2*i+2] + d, err := strconv.ParseInt(b, 0, 0) + if err != nil { + return nil + } + m = append(m, int(d)) + } + s := fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3]) + mask = net.ParseIP(s) + if mask == nil { + return nil + } + } + return net.IPv4Mask(mask[12], mask[13], mask[14], mask[15]) +} + +func parseIPv4Mask(sval string) (interface{}, error) { + mask := ParseIPv4Mask(sval) + if mask == nil { + return nil, fmt.Errorf("unable to parse %s as net.IPMask", sval) + } + return mask, nil +} + +// GetIPv4Mask return the net.IPv4Mask value of a flag with the given name +func (f *FlagSet) GetIPv4Mask(name string) (net.IPMask, error) { + val, err := f.getFlagType(name, "ipMask", parseIPv4Mask) + if err != nil { + return nil, err + } + return val.(net.IPMask), nil +} + +// IPMaskVar defines an net.IPMask flag with specified name, default value, and usage string. +// The argument p points to an net.IPMask variable in which to store the value of the flag. +func (f *FlagSet) IPMaskVar(p *net.IPMask, name string, value net.IPMask, usage string) { + f.VarP(newIPMaskValue(value, p), name, "", usage) +} + +// Like IPMaskVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPMaskVarP(p *net.IPMask, name, shorthand string, value net.IPMask, usage string) { + f.VarP(newIPMaskValue(value, p), name, shorthand, usage) +} + +// IPMaskVar defines an net.IPMask flag with specified name, default value, and usage string. +// The argument p points to an net.IPMask variable in which to store the value of the flag. +func IPMaskVar(p *net.IPMask, name string, value net.IPMask, usage string) { + CommandLine.VarP(newIPMaskValue(value, p), name, "", usage) +} + +// Like IPMaskVar, but accepts a shorthand letter that can be used after a single dash. +func IPMaskVarP(p *net.IPMask, name, shorthand string, value net.IPMask, usage string) { + CommandLine.VarP(newIPMaskValue(value, p), name, shorthand, usage) +} + +// IPMask defines an net.IPMask flag with specified name, default value, and usage string. +// The return value is the address of an net.IPMask variable that stores the value of the flag. +func (f *FlagSet) IPMask(name string, value net.IPMask, usage string) *net.IPMask { + p := new(net.IPMask) + f.IPMaskVarP(p, name, "", value, usage) + return p +} + +// Like IPMask, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPMaskP(name, shorthand string, value net.IPMask, usage string) *net.IPMask { + p := new(net.IPMask) + f.IPMaskVarP(p, name, shorthand, value, usage) + return p +} + +// IPMask defines an net.IPMask flag with specified name, default value, and usage string. +// The return value is the address of an net.IPMask variable that stores the value of the flag. +func IPMask(name string, value net.IPMask, usage string) *net.IPMask { + return CommandLine.IPMaskP(name, "", value, usage) +} + +// Like IP, but accepts a shorthand letter that can be used after a single dash. +func IPMaskP(name, shorthand string, value net.IPMask, usage string) *net.IPMask { + return CommandLine.IPMaskP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/string.go b/Godeps/_workspace/src/github.com/spf13/pflag/string.go new file mode 100644 index 0000000..f89ea8b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/string.go @@ -0,0 +1,82 @@ +package pflag + +import "fmt" + +// -- string Value +type stringValue string + +func newStringValue(val string, p *string) *stringValue { + *p = val + return (*stringValue)(p) +} + +func (s *stringValue) Set(val string) error { + *s = stringValue(val) + return nil +} +func (s *stringValue) Type() string { + return "string" +} + +func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) } + +func stringConv(sval string) (interface{}, error) { + return sval, nil +} + +// GetString return the string value of a flag with the given name +func (f *FlagSet) GetString(name string) (string, error) { + val, err := f.getFlagType(name, "string", stringConv) + if err != nil { + return "", err + } + return val.(string), nil +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func (f *FlagSet) StringVar(p *string, name string, value string, usage string) { + f.VarP(newStringValue(value, p), name, "", usage) +} + +// Like StringVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringVarP(p *string, name, shorthand string, value string, usage string) { + f.VarP(newStringValue(value, p), name, shorthand, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func StringVar(p *string, name string, value string, usage string) { + CommandLine.VarP(newStringValue(value, p), name, "", usage) +} + +// Like StringVar, but accepts a shorthand letter that can be used after a single dash. +func StringVarP(p *string, name, shorthand string, value string, usage string) { + CommandLine.VarP(newStringValue(value, p), name, shorthand, usage) +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func (f *FlagSet) String(name string, value string, usage string) *string { + p := new(string) + f.StringVarP(p, name, "", value, usage) + return p +} + +// Like String, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringP(name, shorthand string, value string, usage string) *string { + p := new(string) + f.StringVarP(p, name, shorthand, value, usage) + return p +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func String(name string, value string, usage string) *string { + return CommandLine.StringP(name, "", value, usage) +} + +// Like String, but accepts a shorthand letter that can be used after a single dash. +func StringP(name, shorthand string, value string, usage string) *string { + return CommandLine.StringP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/string_slice.go b/Godeps/_workspace/src/github.com/spf13/pflag/string_slice.go new file mode 100644 index 0000000..bbe6e00 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/string_slice.go @@ -0,0 +1,86 @@ +package pflag + +import ( + "strings" +) + +// -- stringSlice Value +type stringSliceValue []string + +func newStringSliceValue(val []string, p *[]string) *stringSliceValue { + *p = val + return (*stringSliceValue)(p) +} + +func (s *stringSliceValue) Set(val string) error { + v := strings.Split(val, ",") + *s = stringSliceValue(v) + return nil +} +func (s *stringSliceValue) Type() string { + return "stringSlice" +} + +func (s *stringSliceValue) String() string { return strings.Join(*s, ",") } + +func stringSliceConv(sval string) (interface{}, error) { + v := strings.Split(sval, ",") + return v, nil +} + +// GetStringSlice return the []string value of a flag with the given name +func (f *FlagSet) GetStringSlice(name string) ([]string, error) { + val, err := f.getFlagType(name, "stringSlice", stringSliceConv) + if err != nil { + return []string{}, err + } + return val.([]string), nil +} + +// StringSliceVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a []string variable in which to store the value of the flag. +func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) { + f.VarP(newStringSliceValue(value, p), name, "", usage) +} + +// Like StringSliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []string, usage string) { + f.VarP(newStringSliceValue(value, p), name, shorthand, usage) +} + +// StringSliceVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a []string variable in which to store the value of the flag. +func StringSliceVar(p *[]string, name string, value []string, usage string) { + CommandLine.VarP(newStringSliceValue(value, p), name, "", usage) +} + +// Like StringSliceVar, but accepts a shorthand letter that can be used after a single dash. +func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage string) { + CommandLine.VarP(newStringSliceValue(value, p), name, shorthand, usage) +} + +// StringSlice defines a string flag with specified name, default value, and usage string. +// The return value is the address of a []string variable that stores the value of the flag. +func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string { + p := make([]string, 0) + f.StringSliceVarP(&p, name, "", value, usage) + return &p +} + +// Like StringSlice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage string) *[]string { + p := make([]string, 0) + f.StringSliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// StringSlice defines a string flag with specified name, default value, and usage string. +// The return value is the address of a []string variable that stores the value of the flag. +func StringSlice(name string, value []string, usage string) *[]string { + return CommandLine.StringSliceP(name, "", value, usage) +} + +// Like StringSlice, but accepts a shorthand letter that can be used after a single dash. +func StringSliceP(name, shorthand string, value []string, usage string) *[]string { + return CommandLine.StringSliceP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/string_slice_test.go b/Godeps/_workspace/src/github.com/spf13/pflag/string_slice_test.go new file mode 100644 index 0000000..c37a91a --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/string_slice_test.go @@ -0,0 +1,44 @@ +// Copyright 2009 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 pflag + +import ( + "fmt" + "strings" + "testing" +) + +func setUpSSFlagSet(ssp *[]string) *FlagSet { + f := NewFlagSet("test", ContinueOnError) + f.StringSliceVar(ssp, "ss", []string{}, "Command seperated list!") + return f +} + +func TestSS(t *testing.T) { + var ss []string + f := setUpSSFlagSet(&ss) + + vals := []string{"one", "two", "4", "3"} + arg := fmt.Sprintf("--ss=%s", strings.Join(vals, ",")) + err := f.Parse([]string{arg}) + if err != nil { + t.Fatal("expected no error; got", err) + } + for i, v := range ss { + if vals[i] != v { + t.Fatal("expected ss[%d] to be %s but got: %s", i, vals[i], v) + } + } + + getSS, err := f.GetStringSlice("ss") + if err != nil { + t.Fatal("got an error from GetStringSlice(): %v", err) + } + for i, v := range getSS { + if vals[i] != v { + t.Fatal("expected ss[%d] to be %s from GetStringSlice but got: %s", i, vals[i], v) + } + } +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/uint.go b/Godeps/_workspace/src/github.com/spf13/pflag/uint.go new file mode 100644 index 0000000..d6f8e5b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/uint.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint Value +type uintValue uint + +func newUintValue(val uint, p *uint) *uintValue { + *p = val + return (*uintValue)(p) +} + +func (i *uintValue) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uintValue(v) + return err +} + +func (i *uintValue) Type() string { + return "uint" +} + +func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) } + +func uintConv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 0) + if err != nil { + return 0, err + } + return uint(v), nil +} + +// GetUint return the uint value of a flag with the given name +func (f *FlagSet) GetUint(name string) (uint, error) { + val, err := f.getFlagType(name, "uint", uintConv) + if err != nil { + return 0, err + } + return val.(uint), nil +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string) { + f.VarP(newUintValue(value, p), name, "", usage) +} + +// Like UintVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) UintVarP(p *uint, name, shorthand string, value uint, usage string) { + f.VarP(newUintValue(value, p), name, shorthand, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func UintVar(p *uint, name string, value uint, usage string) { + CommandLine.VarP(newUintValue(value, p), name, "", usage) +} + +// Like UintVar, but accepts a shorthand letter that can be used after a single dash. +func UintVarP(p *uint, name, shorthand string, value uint, usage string) { + CommandLine.VarP(newUintValue(value, p), name, shorthand, usage) +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func (f *FlagSet) Uint(name string, value uint, usage string) *uint { + p := new(uint) + f.UintVarP(p, name, "", value, usage) + return p +} + +// Like Uint, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) UintP(name, shorthand string, value uint, usage string) *uint { + p := new(uint) + f.UintVarP(p, name, shorthand, value, usage) + return p +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func Uint(name string, value uint, usage string) *uint { + return CommandLine.UintP(name, "", value, usage) +} + +// Like Uint, but accepts a shorthand letter that can be used after a single dash. +func UintP(name, shorthand string, value uint, usage string) *uint { + return CommandLine.UintP(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/uint16.go b/Godeps/_workspace/src/github.com/spf13/pflag/uint16.go new file mode 100644 index 0000000..1cdc3df --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/uint16.go @@ -0,0 +1,89 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint16 value +type uint16Value uint16 + +func newUint16Value(val uint16, p *uint16) *uint16Value { + *p = val + return (*uint16Value)(p) +} +func (i *uint16Value) String() string { return fmt.Sprintf("%d", *i) } +func (i *uint16Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 16) + *i = uint16Value(v) + return err +} + +func (i *uint16Value) Type() string { + return "uint16" +} + +func uint16Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 16) + if err != nil { + return 0, err + } + return uint16(v), nil +} + +// GetUint16 return the uint16 value of a flag with the given name +func (f *FlagSet) GetUint16(name string) (uint16, error) { + val, err := f.getFlagType(name, "uint16", uint16Conv) + if err != nil { + return 0, err + } + return val.(uint16), nil +} + +// Uint16Var defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func (f *FlagSet) Uint16Var(p *uint16, name string, value uint16, usage string) { + f.VarP(newUint16Value(value, p), name, "", usage) +} + +// Like Uint16Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint16VarP(p *uint16, name, shorthand string, value uint16, usage string) { + f.VarP(newUint16Value(value, p), name, shorthand, usage) +} + +// Uint16Var defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func Uint16Var(p *uint16, name string, value uint16, usage string) { + CommandLine.VarP(newUint16Value(value, p), name, "", usage) +} + +// Like Uint16Var, but accepts a shorthand letter that can be used after a single dash. +func Uint16VarP(p *uint16, name, shorthand string, value uint16, usage string) { + CommandLine.VarP(newUint16Value(value, p), name, shorthand, usage) +} + +// Uint16 defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func (f *FlagSet) Uint16(name string, value uint16, usage string) *uint16 { + p := new(uint16) + f.Uint16VarP(p, name, "", value, usage) + return p +} + +// Like Uint16, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint16P(name, shorthand string, value uint16, usage string) *uint16 { + p := new(uint16) + f.Uint16VarP(p, name, shorthand, value, usage) + return p +} + +// Uint16 defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func Uint16(name string, value uint16, usage string) *uint16 { + return CommandLine.Uint16P(name, "", value, usage) +} + +// Like Uint16, but accepts a shorthand letter that can be used after a single dash. +func Uint16P(name, shorthand string, value uint16, usage string) *uint16 { + return CommandLine.Uint16P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/uint32.go b/Godeps/_workspace/src/github.com/spf13/pflag/uint32.go new file mode 100644 index 0000000..1326e4a --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/uint32.go @@ -0,0 +1,89 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint16 value +type uint32Value uint32 + +func newUint32Value(val uint32, p *uint32) *uint32Value { + *p = val + return (*uint32Value)(p) +} +func (i *uint32Value) String() string { return fmt.Sprintf("%d", *i) } +func (i *uint32Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 32) + *i = uint32Value(v) + return err +} + +func (i *uint32Value) Type() string { + return "uint32" +} + +func uint32Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 32) + if err != nil { + return 0, err + } + return uint32(v), nil +} + +// GetUint32 return the uint32 value of a flag with the given name +func (f *FlagSet) GetUint32(name string) (uint32, error) { + val, err := f.getFlagType(name, "uint32", uint32Conv) + if err != nil { + return 0, err + } + return val.(uint32), nil +} + +// Uint32Var defines a uint32 flag with specified name, default value, and usage string. +// The argument p points to a uint32 variable in which to store the value of the flag. +func (f *FlagSet) Uint32Var(p *uint32, name string, value uint32, usage string) { + f.VarP(newUint32Value(value, p), name, "", usage) +} + +// Like Uint32Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint32VarP(p *uint32, name, shorthand string, value uint32, usage string) { + f.VarP(newUint32Value(value, p), name, shorthand, usage) +} + +// Uint32Var defines a uint32 flag with specified name, default value, and usage string. +// The argument p points to a uint32 variable in which to store the value of the flag. +func Uint32Var(p *uint32, name string, value uint32, usage string) { + CommandLine.VarP(newUint32Value(value, p), name, "", usage) +} + +// Like Uint32Var, but accepts a shorthand letter that can be used after a single dash. +func Uint32VarP(p *uint32, name, shorthand string, value uint32, usage string) { + CommandLine.VarP(newUint32Value(value, p), name, shorthand, usage) +} + +// Uint32 defines a uint32 flag with specified name, default value, and usage string. +// The return value is the address of a uint32 variable that stores the value of the flag. +func (f *FlagSet) Uint32(name string, value uint32, usage string) *uint32 { + p := new(uint32) + f.Uint32VarP(p, name, "", value, usage) + return p +} + +// Like Uint32, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint32P(name, shorthand string, value uint32, usage string) *uint32 { + p := new(uint32) + f.Uint32VarP(p, name, shorthand, value, usage) + return p +} + +// Uint32 defines a uint32 flag with specified name, default value, and usage string. +// The return value is the address of a uint32 variable that stores the value of the flag. +func Uint32(name string, value uint32, usage string) *uint32 { + return CommandLine.Uint32P(name, "", value, usage) +} + +// Like Uint32, but accepts a shorthand letter that can be used after a single dash. +func Uint32P(name, shorthand string, value uint32, usage string) *uint32 { + return CommandLine.Uint32P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/uint64.go b/Godeps/_workspace/src/github.com/spf13/pflag/uint64.go new file mode 100644 index 0000000..6788bbf --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/uint64.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint64 Value +type uint64Value uint64 + +func newUint64Value(val uint64, p *uint64) *uint64Value { + *p = val + return (*uint64Value)(p) +} + +func (i *uint64Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uint64Value(v) + return err +} + +func (i *uint64Value) Type() string { + return "uint64" +} + +func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) } + +func uint64Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 64) + if err != nil { + return 0, err + } + return uint64(v), nil +} + +// GetUint64 return the uint64 value of a flag with the given name +func (f *FlagSet) GetUint64(name string) (uint64, error) { + val, err := f.getFlagType(name, "uint64", uint64Conv) + if err != nil { + return 0, err + } + return val.(uint64), nil +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) { + f.VarP(newUint64Value(value, p), name, "", usage) +} + +// Like Uint64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint64VarP(p *uint64, name, shorthand string, value uint64, usage string) { + f.VarP(newUint64Value(value, p), name, shorthand, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func Uint64Var(p *uint64, name string, value uint64, usage string) { + CommandLine.VarP(newUint64Value(value, p), name, "", usage) +} + +// Like Uint64Var, but accepts a shorthand letter that can be used after a single dash. +func Uint64VarP(p *uint64, name, shorthand string, value uint64, usage string) { + CommandLine.VarP(newUint64Value(value, p), name, shorthand, usage) +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64 { + p := new(uint64) + f.Uint64VarP(p, name, "", value, usage) + return p +} + +// Like Uint64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint64P(name, shorthand string, value uint64, usage string) *uint64 { + p := new(uint64) + f.Uint64VarP(p, name, shorthand, value, usage) + return p +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func Uint64(name string, value uint64, usage string) *uint64 { + return CommandLine.Uint64P(name, "", value, usage) +} + +// Like Uint64, but accepts a shorthand letter that can be used after a single dash. +func Uint64P(name, shorthand string, value uint64, usage string) *uint64 { + return CommandLine.Uint64P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/github.com/spf13/pflag/uint8.go b/Godeps/_workspace/src/github.com/spf13/pflag/uint8.go new file mode 100644 index 0000000..560c569 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/pflag/uint8.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint8 Value +type uint8Value uint8 + +func newUint8Value(val uint8, p *uint8) *uint8Value { + *p = val + return (*uint8Value)(p) +} + +func (i *uint8Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 8) + *i = uint8Value(v) + return err +} + +func (i *uint8Value) Type() string { + return "uint8" +} + +func (i *uint8Value) String() string { return fmt.Sprintf("%v", *i) } + +func uint8Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 8) + if err != nil { + return 0, err + } + return uint8(v), nil +} + +// GetUint8 return the uint8 value of a flag with the given name +func (f *FlagSet) GetUint8(name string) (uint8, error) { + val, err := f.getFlagType(name, "uint8", uint8Conv) + if err != nil { + return 0, err + } + return val.(uint8), nil +} + +// Uint8Var defines a uint8 flag with specified name, default value, and usage string. +// The argument p points to a uint8 variable in which to store the value of the flag. +func (f *FlagSet) Uint8Var(p *uint8, name string, value uint8, usage string) { + f.VarP(newUint8Value(value, p), name, "", usage) +} + +// Like Uint8Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint8VarP(p *uint8, name, shorthand string, value uint8, usage string) { + f.VarP(newUint8Value(value, p), name, shorthand, usage) +} + +// Uint8Var defines a uint8 flag with specified name, default value, and usage string. +// The argument p points to a uint8 variable in which to store the value of the flag. +func Uint8Var(p *uint8, name string, value uint8, usage string) { + CommandLine.VarP(newUint8Value(value, p), name, "", usage) +} + +// Like Uint8Var, but accepts a shorthand letter that can be used after a single dash. +func Uint8VarP(p *uint8, name, shorthand string, value uint8, usage string) { + CommandLine.VarP(newUint8Value(value, p), name, shorthand, usage) +} + +// Uint8 defines a uint8 flag with specified name, default value, and usage string. +// The return value is the address of a uint8 variable that stores the value of the flag. +func (f *FlagSet) Uint8(name string, value uint8, usage string) *uint8 { + p := new(uint8) + f.Uint8VarP(p, name, "", value, usage) + return p +} + +// Like Uint8, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint8P(name, shorthand string, value uint8, usage string) *uint8 { + p := new(uint8) + f.Uint8VarP(p, name, shorthand, value, usage) + return p +} + +// Uint8 defines a uint8 flag with specified name, default value, and usage string. +// The return value is the address of a uint8 variable that stores the value of the flag. +func Uint8(name string, value uint8, usage string) *uint8 { + return CommandLine.Uint8P(name, "", value, usage) +} + +// Like Uint8, but accepts a shorthand letter that can be used after a single dash. +func Uint8P(name, shorthand string, value uint8, usage string) *uint8 { + return CommandLine.Uint8P(name, shorthand, value, usage) +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/terminal.go b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/terminal.go new file mode 100644 index 0000000..741eeb1 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/terminal.go @@ -0,0 +1,892 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import ( + "bytes" + "io" + "sync" + "unicode/utf8" +) + +// EscapeCodes contains escape sequences that can be written to the terminal in +// order to achieve different styles of text. +type EscapeCodes struct { + // Foreground colors + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte + + // Reset all attributes + Reset []byte +} + +var vt100EscapeCodes = EscapeCodes{ + Black: []byte{keyEscape, '[', '3', '0', 'm'}, + Red: []byte{keyEscape, '[', '3', '1', 'm'}, + Green: []byte{keyEscape, '[', '3', '2', 'm'}, + Yellow: []byte{keyEscape, '[', '3', '3', 'm'}, + Blue: []byte{keyEscape, '[', '3', '4', 'm'}, + Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, + Cyan: []byte{keyEscape, '[', '3', '6', 'm'}, + White: []byte{keyEscape, '[', '3', '7', 'm'}, + + Reset: []byte{keyEscape, '[', '0', 'm'}, +} + +// Terminal contains the state for running a VT100 terminal that is capable of +// reading lines of input. +type Terminal struct { + // AutoCompleteCallback, if non-null, is called for each keypress with + // the full input line and the current position of the cursor (in + // bytes, as an index into |line|). If it returns ok=false, the key + // press is processed normally. Otherwise it returns a replacement line + // and the new cursor position. + AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) + + // Escape contains a pointer to the escape codes for this terminal. + // It's always a valid pointer, although the escape codes themselves + // may be empty if the terminal doesn't support them. + Escape *EscapeCodes + + // lock protects the terminal and the state in this object from + // concurrent processing of a key press and a Write() call. + lock sync.Mutex + + c io.ReadWriter + prompt []rune + + // line is the current line being entered. + line []rune + // pos is the logical position of the cursor in line + pos int + // echo is true if local echo is enabled + echo bool + // pasteActive is true iff there is a bracketed paste operation in + // progress. + pasteActive bool + + // cursorX contains the current X value of the cursor where the left + // edge is 0. cursorY contains the row number where the first row of + // the current line is 0. + cursorX, cursorY int + // maxLine is the greatest value of cursorY so far. + maxLine int + + termWidth, termHeight int + + // outBuf contains the terminal data to be sent. + outBuf []byte + // remainder contains the remainder of any partial key sequences after + // a read. It aliases into inBuf. + remainder []byte + inBuf [256]byte + + // history contains previously entered commands so that they can be + // accessed with the up and down keys. + history stRingBuffer + // historyIndex stores the currently accessed history entry, where zero + // means the immediately previous entry. + historyIndex int + // When navigating up and down the history it's possible to return to + // the incomplete, initial line. That value is stored in + // historyPending. + historyPending string +} + +// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is +// a local terminal, that terminal must first have been put into raw mode. +// prompt is a string that is written at the start of each input line (i.e. +// "> "). +func NewTerminal(c io.ReadWriter, prompt string) *Terminal { + return &Terminal{ + Escape: &vt100EscapeCodes, + c: c, + prompt: []rune(prompt), + termWidth: 80, + termHeight: 24, + echo: true, + historyIndex: -1, + } +} + +const ( + keyCtrlD = 4 + keyCtrlU = 21 + keyEnter = '\r' + keyEscape = 27 + keyBackspace = 127 + keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota + keyUp + keyDown + keyLeft + keyRight + keyAltLeft + keyAltRight + keyHome + keyEnd + keyDeleteWord + keyDeleteLine + keyClearScreen + keyPasteStart + keyPasteEnd +) + +var pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} +var pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'} + +// bytesToKey tries to parse a key sequence from b. If successful, it returns +// the key and the remainder of the input. Otherwise it returns utf8.RuneError. +func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { + if len(b) == 0 { + return utf8.RuneError, nil + } + + if !pasteActive { + switch b[0] { + case 1: // ^A + return keyHome, b[1:] + case 5: // ^E + return keyEnd, b[1:] + case 8: // ^H + return keyBackspace, b[1:] + case 11: // ^K + return keyDeleteLine, b[1:] + case 12: // ^L + return keyClearScreen, b[1:] + case 23: // ^W + return keyDeleteWord, b[1:] + } + } + + if b[0] != keyEscape { + if !utf8.FullRune(b) { + return utf8.RuneError, b + } + r, l := utf8.DecodeRune(b) + return r, b[l:] + } + + if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { + switch b[2] { + case 'A': + return keyUp, b[3:] + case 'B': + return keyDown, b[3:] + case 'C': + return keyRight, b[3:] + case 'D': + return keyLeft, b[3:] + case 'H': + return keyHome, b[3:] + case 'F': + return keyEnd, b[3:] + } + } + + if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { + switch b[5] { + case 'C': + return keyAltRight, b[6:] + case 'D': + return keyAltLeft, b[6:] + } + } + + if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { + return keyPasteStart, b[6:] + } + + if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { + return keyPasteEnd, b[6:] + } + + // If we get here then we have a key that we don't recognise, or a + // partial sequence. It's not clear how one should find the end of a + // sequence without knowing them all, but it seems that [a-zA-Z~] only + // appears at the end of a sequence. + for i, c := range b[0:] { + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { + return keyUnknown, b[i+1:] + } + } + + return utf8.RuneError, b +} + +// queue appends data to the end of t.outBuf +func (t *Terminal) queue(data []rune) { + t.outBuf = append(t.outBuf, []byte(string(data))...) +} + +var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'} +var space = []rune{' '} + +func isPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// moveCursorToPos appends data to t.outBuf which will move the cursor to the +// given, logical position in the text. +func (t *Terminal) moveCursorToPos(pos int) { + if !t.echo { + return + } + + x := visualLength(t.prompt) + pos + y := x / t.termWidth + x = x % t.termWidth + + up := 0 + if y < t.cursorY { + up = t.cursorY - y + } + + down := 0 + if y > t.cursorY { + down = y - t.cursorY + } + + left := 0 + if x < t.cursorX { + left = t.cursorX - x + } + + right := 0 + if x > t.cursorX { + right = x - t.cursorX + } + + t.cursorX = x + t.cursorY = y + t.move(up, down, left, right) +} + +func (t *Terminal) move(up, down, left, right int) { + movement := make([]rune, 3*(up+down+left+right)) + m := movement + for i := 0; i < up; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'A' + m = m[3:] + } + for i := 0; i < down; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'B' + m = m[3:] + } + for i := 0; i < left; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'D' + m = m[3:] + } + for i := 0; i < right; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'C' + m = m[3:] + } + + t.queue(movement) +} + +func (t *Terminal) clearLineToRight() { + op := []rune{keyEscape, '[', 'K'} + t.queue(op) +} + +const maxLineLength = 4096 + +func (t *Terminal) setLine(newLine []rune, newPos int) { + if t.echo { + t.moveCursorToPos(0) + t.writeLine(newLine) + for i := len(newLine); i < len(t.line); i++ { + t.writeLine(space) + } + t.moveCursorToPos(newPos) + } + t.line = newLine + t.pos = newPos +} + +func (t *Terminal) advanceCursor(places int) { + t.cursorX += places + t.cursorY += t.cursorX / t.termWidth + if t.cursorY > t.maxLine { + t.maxLine = t.cursorY + } + t.cursorX = t.cursorX % t.termWidth + + if places > 0 && t.cursorX == 0 { + // Normally terminals will advance the current position + // when writing a character. But that doesn't happen + // for the last character in a line. However, when + // writing a character (except a new line) that causes + // a line wrap, the position will be advanced two + // places. + // + // So, if we are stopping at the end of a line, we + // need to write a newline so that our cursor can be + // advanced to the next line. + t.outBuf = append(t.outBuf, '\n') + } +} + +func (t *Terminal) eraseNPreviousChars(n int) { + if n == 0 { + return + } + + if t.pos < n { + n = t.pos + } + t.pos -= n + t.moveCursorToPos(t.pos) + + copy(t.line[t.pos:], t.line[n+t.pos:]) + t.line = t.line[:len(t.line)-n] + if t.echo { + t.writeLine(t.line[t.pos:]) + for i := 0; i < n; i++ { + t.queue(space) + } + t.advanceCursor(n) + t.moveCursorToPos(t.pos) + } +} + +// countToLeftWord returns then number of characters from the cursor to the +// start of the previous word. +func (t *Terminal) countToLeftWord() int { + if t.pos == 0 { + return 0 + } + + pos := t.pos - 1 + for pos > 0 { + if t.line[pos] != ' ' { + break + } + pos-- + } + for pos > 0 { + if t.line[pos] == ' ' { + pos++ + break + } + pos-- + } + + return t.pos - pos +} + +// countToRightWord returns then number of characters from the cursor to the +// start of the next word. +func (t *Terminal) countToRightWord() int { + pos := t.pos + for pos < len(t.line) { + if t.line[pos] == ' ' { + break + } + pos++ + } + for pos < len(t.line) { + if t.line[pos] != ' ' { + break + } + pos++ + } + return pos - t.pos +} + +// visualLength returns the number of visible glyphs in s. +func visualLength(runes []rune) int { + inEscapeSeq := false + length := 0 + + for _, r := range runes { + switch { + case inEscapeSeq: + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { + inEscapeSeq = false + } + case r == '\x1b': + inEscapeSeq = true + default: + length++ + } + } + + return length +} + +// handleKey processes the given key and, optionally, returns a line of text +// that the user has entered. +func (t *Terminal) handleKey(key rune) (line string, ok bool) { + if t.pasteActive && key != keyEnter { + t.addKeyToLine(key) + return + } + + switch key { + case keyBackspace: + if t.pos == 0 { + return + } + t.eraseNPreviousChars(1) + case keyAltLeft: + // move left by a word. + t.pos -= t.countToLeftWord() + t.moveCursorToPos(t.pos) + case keyAltRight: + // move right by a word. + t.pos += t.countToRightWord() + t.moveCursorToPos(t.pos) + case keyLeft: + if t.pos == 0 { + return + } + t.pos-- + t.moveCursorToPos(t.pos) + case keyRight: + if t.pos == len(t.line) { + return + } + t.pos++ + t.moveCursorToPos(t.pos) + case keyHome: + if t.pos == 0 { + return + } + t.pos = 0 + t.moveCursorToPos(t.pos) + case keyEnd: + if t.pos == len(t.line) { + return + } + t.pos = len(t.line) + t.moveCursorToPos(t.pos) + case keyUp: + entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) + if !ok { + return "", false + } + if t.historyIndex == -1 { + t.historyPending = string(t.line) + } + t.historyIndex++ + runes := []rune(entry) + t.setLine(runes, len(runes)) + case keyDown: + switch t.historyIndex { + case -1: + return + case 0: + runes := []rune(t.historyPending) + t.setLine(runes, len(runes)) + t.historyIndex-- + default: + entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) + if ok { + t.historyIndex-- + runes := []rune(entry) + t.setLine(runes, len(runes)) + } + } + case keyEnter: + t.moveCursorToPos(len(t.line)) + t.queue([]rune("\r\n")) + line = string(t.line) + ok = true + t.line = t.line[:0] + t.pos = 0 + t.cursorX = 0 + t.cursorY = 0 + t.maxLine = 0 + case keyDeleteWord: + // Delete zero or more spaces and then one or more characters. + t.eraseNPreviousChars(t.countToLeftWord()) + case keyDeleteLine: + // Delete everything from the current cursor position to the + // end of line. + for i := t.pos; i < len(t.line); i++ { + t.queue(space) + t.advanceCursor(1) + } + t.line = t.line[:t.pos] + t.moveCursorToPos(t.pos) + case keyCtrlD: + // Erase the character under the current position. + // The EOF case when the line is empty is handled in + // readLine(). + if t.pos < len(t.line) { + t.pos++ + t.eraseNPreviousChars(1) + } + case keyCtrlU: + t.eraseNPreviousChars(t.pos) + case keyClearScreen: + // Erases the screen and moves the cursor to the home position. + t.queue([]rune("\x1b[2J\x1b[H")) + t.queue(t.prompt) + t.cursorX, t.cursorY = 0, 0 + t.advanceCursor(visualLength(t.prompt)) + t.setLine(t.line, t.pos) + default: + if t.AutoCompleteCallback != nil { + prefix := string(t.line[:t.pos]) + suffix := string(t.line[t.pos:]) + + t.lock.Unlock() + newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key) + t.lock.Lock() + + if completeOk { + t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos])) + return + } + } + if !isPrintable(key) { + return + } + if len(t.line) == maxLineLength { + return + } + t.addKeyToLine(key) + } + return +} + +// addKeyToLine inserts the given key at the current position in the current +// line. +func (t *Terminal) addKeyToLine(key rune) { + if len(t.line) == cap(t.line) { + newLine := make([]rune, len(t.line), 2*(1+len(t.line))) + copy(newLine, t.line) + t.line = newLine + } + t.line = t.line[:len(t.line)+1] + copy(t.line[t.pos+1:], t.line[t.pos:]) + t.line[t.pos] = key + if t.echo { + t.writeLine(t.line[t.pos:]) + } + t.pos++ + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) writeLine(line []rune) { + for len(line) != 0 { + remainingOnLine := t.termWidth - t.cursorX + todo := len(line) + if todo > remainingOnLine { + todo = remainingOnLine + } + t.queue(line[:todo]) + t.advanceCursor(visualLength(line[:todo])) + line = line[todo:] + } +} + +func (t *Terminal) Write(buf []byte) (n int, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + if t.cursorX == 0 && t.cursorY == 0 { + // This is the easy case: there's nothing on the screen that we + // have to move out of the way. + return t.c.Write(buf) + } + + // We have a prompt and possibly user input on the screen. We + // have to clear it first. + t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */) + t.cursorX = 0 + t.clearLineToRight() + + for t.cursorY > 0 { + t.move(1 /* up */, 0, 0, 0) + t.cursorY-- + t.clearLineToRight() + } + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + + if n, err = t.c.Write(buf); err != nil { + return + } + + t.writeLine(t.prompt) + if t.echo { + t.writeLine(t.line) + } + + t.moveCursorToPos(t.pos) + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + return +} + +// ReadPassword temporarily changes the prompt and reads a password, without +// echo, from the terminal. +func (t *Terminal) ReadPassword(prompt string) (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + oldPrompt := t.prompt + t.prompt = []rune(prompt) + t.echo = false + + line, err = t.readLine() + + t.prompt = oldPrompt + t.echo = true + + return +} + +// ReadLine returns a line of input from the terminal. +func (t *Terminal) ReadLine() (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + return t.readLine() +} + +func (t *Terminal) readLine() (line string, err error) { + // t.lock must be held at this point + + if t.cursorX == 0 && t.cursorY == 0 { + t.writeLine(t.prompt) + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + } + + lineIsPasted := t.pasteActive + + for { + rest := t.remainder + lineOk := false + for !lineOk { + var key rune + key, rest = bytesToKey(rest, t.pasteActive) + if key == utf8.RuneError { + break + } + if !t.pasteActive { + if key == keyCtrlD { + if len(t.line) == 0 { + return "", io.EOF + } + } + if key == keyPasteStart { + t.pasteActive = true + if len(t.line) == 0 { + lineIsPasted = true + } + continue + } + } else if key == keyPasteEnd { + t.pasteActive = false + continue + } + if !t.pasteActive { + lineIsPasted = false + } + line, lineOk = t.handleKey(key) + } + if len(rest) > 0 { + n := copy(t.inBuf[:], rest) + t.remainder = t.inBuf[:n] + } else { + t.remainder = nil + } + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + if lineOk { + if t.echo { + t.historyIndex = -1 + t.history.Add(line) + } + if lineIsPasted { + err = ErrPasteIndicator + } + return + } + + // t.remainder is a slice at the beginning of t.inBuf + // containing a partial key sequence + readBuf := t.inBuf[len(t.remainder):] + var n int + + t.lock.Unlock() + n, err = t.c.Read(readBuf) + t.lock.Lock() + + if err != nil { + return + } + + t.remainder = t.inBuf[:n+len(t.remainder)] + } + + panic("unreachable") // for Go 1.0. +} + +// SetPrompt sets the prompt to be used when reading subsequent lines. +func (t *Terminal) SetPrompt(prompt string) { + t.lock.Lock() + defer t.lock.Unlock() + + t.prompt = []rune(prompt) +} + +func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { + // Move cursor to column zero at the start of the line. + t.move(t.cursorY, 0, t.cursorX, 0) + t.cursorX, t.cursorY = 0, 0 + t.clearLineToRight() + for t.cursorY < numPrevLines { + // Move down a line + t.move(0, 1, 0, 0) + t.cursorY++ + t.clearLineToRight() + } + // Move back to beginning. + t.move(t.cursorY, 0, 0, 0) + t.cursorX, t.cursorY = 0, 0 + + t.queue(t.prompt) + t.advanceCursor(visualLength(t.prompt)) + t.writeLine(t.line) + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) SetSize(width, height int) error { + t.lock.Lock() + defer t.lock.Unlock() + + if width == 0 { + width = 1 + } + + oldWidth := t.termWidth + t.termWidth, t.termHeight = width, height + + switch { + case width == oldWidth: + // If the width didn't change then nothing else needs to be + // done. + return nil + case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: + // If there is nothing on current line and no prompt printed, + // just do nothing + return nil + case width < oldWidth: + // Some terminals (e.g. xterm) will truncate lines that were + // too long when shinking. Others, (e.g. gnome-terminal) will + // attempt to wrap them. For the former, repainting t.maxLine + // works great, but that behaviour goes badly wrong in the case + // of the latter because they have doubled every full line. + + // We assume that we are working on a terminal that wraps lines + // and adjust the cursor position based on every previous line + // wrapping and turning into two. This causes the prompt on + // xterms to move upwards, which isn't great, but it avoids a + // huge mess with gnome-terminal. + if t.cursorX >= t.termWidth { + t.cursorX = t.termWidth - 1 + } + t.cursorY *= 2 + t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) + case width > oldWidth: + // If the terminal expands then our position calculations will + // be wrong in the future because we think the cursor is + // |t.pos| chars into the string, but there will be a gap at + // the end of any wrapped line. + // + // But the position will actually be correct until we move, so + // we can move back to the beginning and repaint everything. + t.clearAndRepaintLinePlusNPrevious(t.maxLine) + } + + _, err := t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + return err +} + +type pasteIndicatorError struct{} + +func (pasteIndicatorError) Error() string { + return "terminal: ErrPasteIndicator not correctly handled" +} + +// ErrPasteIndicator may be returned from ReadLine as the error, in addition +// to valid line data. It indicates that bracketed paste mode is enabled and +// that the returned line consists only of pasted data. Programs may wish to +// interpret pasted data more literally than typed data. +var ErrPasteIndicator = pasteIndicatorError{} + +// SetBracketedPasteMode requests that the terminal bracket paste operations +// with markers. Not all terminals support this but, if it is supported, then +// enabling this mode will stop any autocomplete callback from running due to +// pastes. Additionally, any lines that are completely pasted will be returned +// from ReadLine with the error set to ErrPasteIndicator. +func (t *Terminal) SetBracketedPasteMode(on bool) { + if on { + io.WriteString(t.c, "\x1b[?2004h") + } else { + io.WriteString(t.c, "\x1b[?2004l") + } +} + +// stRingBuffer is a ring buffer of strings. +type stRingBuffer struct { + // entries contains max elements. + entries []string + max int + // head contains the index of the element most recently added to the ring. + head int + // size contains the number of elements in the ring. + size int +} + +func (s *stRingBuffer) Add(a string) { + if s.entries == nil { + const defaultNumEntries = 100 + s.entries = make([]string, defaultNumEntries) + s.max = defaultNumEntries + } + + s.head = (s.head + 1) % s.max + s.entries[s.head] = a + if s.size < s.max { + s.size++ + } +} + +// NthPreviousEntry returns the value passed to the nth previous call to Add. +// If n is zero then the immediately prior value is returned, if one, then the +// next most recent, and so on. If such an element doesn't exist then ok is +// false. +func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { + if n >= s.size { + return "", false + } + index := s.head - n + if index < 0 { + index += s.max + } + return s.entries[index], true +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/terminal_test.go b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/terminal_test.go new file mode 100644 index 0000000..a663fe4 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/terminal_test.go @@ -0,0 +1,269 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import ( + "io" + "testing" +) + +type MockTerminal struct { + toSend []byte + bytesPerRead int + received []byte +} + +func (c *MockTerminal) Read(data []byte) (n int, err error) { + n = len(data) + if n == 0 { + return + } + if n > len(c.toSend) { + n = len(c.toSend) + } + if n == 0 { + return 0, io.EOF + } + if c.bytesPerRead > 0 && n > c.bytesPerRead { + n = c.bytesPerRead + } + copy(data, c.toSend[:n]) + c.toSend = c.toSend[n:] + return +} + +func (c *MockTerminal) Write(data []byte) (n int, err error) { + c.received = append(c.received, data...) + return len(data), nil +} + +func TestClose(t *testing.T) { + c := &MockTerminal{} + ss := NewTerminal(c, "> ") + line, err := ss.ReadLine() + if line != "" { + t.Errorf("Expected empty line but got: %s", line) + } + if err != io.EOF { + t.Errorf("Error should have been EOF but got: %s", err) + } +} + +var keyPressTests = []struct { + in string + line string + err error + throwAwayLines int +}{ + { + err: io.EOF, + }, + { + in: "\r", + line: "", + }, + { + in: "foo\r", + line: "foo", + }, + { + in: "a\x1b[Cb\r", // right + line: "ab", + }, + { + in: "a\x1b[Db\r", // left + line: "ba", + }, + { + in: "a\177b\r", // backspace + line: "b", + }, + { + in: "\x1b[A\r", // up + }, + { + in: "\x1b[B\r", // down + }, + { + in: "line\x1b[A\x1b[B\r", // up then down + line: "line", + }, + { + in: "line1\rline2\x1b[A\r", // recall previous line. + line: "line1", + throwAwayLines: 1, + }, + { + // recall two previous lines and append. + in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r", + line: "line1xxx", + throwAwayLines: 2, + }, + { + // Ctrl-A to move to beginning of line followed by ^K to kill + // line. + in: "a b \001\013\r", + line: "", + }, + { + // Ctrl-A to move to beginning of line, Ctrl-E to move to end, + // finally ^K to kill nothing. + in: "a b \001\005\013\r", + line: "a b ", + }, + { + in: "\027\r", + line: "", + }, + { + in: "a\027\r", + line: "", + }, + { + in: "a \027\r", + line: "", + }, + { + in: "a b\027\r", + line: "a ", + }, + { + in: "a b \027\r", + line: "a ", + }, + { + in: "one two thr\x1b[D\027\r", + line: "one two r", + }, + { + in: "\013\r", + line: "", + }, + { + in: "a\013\r", + line: "a", + }, + { + in: "ab\x1b[D\013\r", + line: "a", + }, + { + in: "Ξεσκεπάζω\r", + line: "Ξεσκεπάζω", + }, + { + in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace. + line: "", + throwAwayLines: 1, + }, + { + in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter. + line: "£", + throwAwayLines: 1, + }, + { + // Ctrl-D at the end of the line should be ignored. + in: "a\004\r", + line: "a", + }, + { + // a, b, left, Ctrl-D should erase the b. + in: "ab\x1b[D\004\r", + line: "a", + }, + { + // a, b, c, d, left, left, ^U should erase to the beginning of + // the line. + in: "abcd\x1b[D\x1b[D\025\r", + line: "cd", + }, + { + // Bracketed paste mode: control sequences should be returned + // verbatim in paste mode. + in: "abc\x1b[200~de\177f\x1b[201~\177\r", + line: "abcde\177", + }, + { + // Enter in bracketed paste mode should still work. + in: "abc\x1b[200~d\refg\x1b[201~h\r", + line: "efgh", + throwAwayLines: 1, + }, + { + // Lines consisting entirely of pasted data should be indicated as such. + in: "\x1b[200~a\r", + line: "a", + err: ErrPasteIndicator, + }, +} + +func TestKeyPresses(t *testing.T) { + for i, test := range keyPressTests { + for j := 1; j < len(test.in); j++ { + c := &MockTerminal{ + toSend: []byte(test.in), + bytesPerRead: j, + } + ss := NewTerminal(c, "> ") + for k := 0; k < test.throwAwayLines; k++ { + _, err := ss.ReadLine() + if err != nil { + t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err) + } + } + line, err := ss.ReadLine() + if line != test.line { + t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line) + break + } + if err != test.err { + t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err) + break + } + } + } +} + +func TestPasswordNotSaved(t *testing.T) { + c := &MockTerminal{ + toSend: []byte("password\r\x1b[A\r"), + bytesPerRead: 1, + } + ss := NewTerminal(c, "> ") + pw, _ := ss.ReadPassword("> ") + if pw != "password" { + t.Fatalf("failed to read password, got %s", pw) + } + line, _ := ss.ReadLine() + if len(line) > 0 { + t.Fatalf("password was saved in history") + } +} + +var setSizeTests = []struct { + width, height int +}{ + {40, 13}, + {80, 24}, + {132, 43}, +} + +func TestTerminalSetSize(t *testing.T) { + for _, setSize := range setSizeTests { + c := &MockTerminal{ + toSend: []byte("password\r\x1b[A\r"), + bytesPerRead: 1, + } + ss := NewTerminal(c, "> ") + ss.SetSize(setSize.width, setSize.height) + pw, _ := ss.ReadPassword("Password: ") + if pw != "password" { + t.Fatalf("failed to read password, got %s", pw) + } + if string(c.received) != "Password: \r\n" { + t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received) + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util.go b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util.go new file mode 100644 index 0000000..0763c9a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util.go @@ -0,0 +1,128 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "io" + "syscall" + "unsafe" +) + +// State contains the state of a terminal. +type State struct { + termios syscall.Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + return nil, err + } + + newState := oldState.termios + newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF + newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + return nil, err + } + + return &oldState, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + return nil, err + } + + return &oldState, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var dimensions [4]uint16 + + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 { + return -1, -1, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var oldState syscall.Termios + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { + return nil, err + } + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + return nil, err + } + + defer func() { + syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_bsd.go b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_bsd.go new file mode 100644 index 0000000..9c1ffd1 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_bsd.go @@ -0,0 +1,12 @@ +// 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. + +// +build darwin dragonfly freebsd netbsd openbsd + +package terminal + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA +const ioctlWriteTermios = syscall.TIOCSETA diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_linux.go b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_linux.go new file mode 100644 index 0000000..5883b22 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_linux.go @@ -0,0 +1,11 @@ +// 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 terminal + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_windows.go b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_windows.go new file mode 100644 index 0000000..2dd6c3d --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ssh/terminal/util_windows.go @@ -0,0 +1,174 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "io" + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + short int16 + word uint16 + + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + st &^= (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(syscall.Handle(fd), buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + if n > 0 && buf[n-1] == '\r' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/atom/atom.go b/Godeps/_workspace/src/golang.org/x/net/html/atom/atom.go new file mode 100644 index 0000000..227404b --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/atom/atom.go @@ -0,0 +1,78 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package atom provides integer codes (also known as atoms) for a fixed set of +// frequently occurring HTML strings: tag names and attribute keys such as "p" +// and "id". +// +// Sharing an atom's name between all elements with the same tag can result in +// fewer string allocations when tokenizing and parsing HTML. Integer +// comparisons are also generally faster than string comparisons. +// +// The value of an atom's particular code is not guaranteed to stay the same +// between versions of this package. Neither is any ordering guaranteed: +// whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to +// be dense. The only guarantees are that e.g. looking up "div" will yield +// atom.Div, calling atom.Div.String will return "div", and atom.Div != 0. +package atom + +// Atom is an integer code for a string. The zero value maps to "". +type Atom uint32 + +// String returns the atom's name. +func (a Atom) String() string { + start := uint32(a >> 8) + n := uint32(a & 0xff) + if start+n > uint32(len(atomText)) { + return "" + } + return atomText[start : start+n] +} + +func (a Atom) string() string { + return atomText[a>>8 : a>>8+a&0xff] +} + +// fnv computes the FNV hash with an arbitrary starting value h. +func fnv(h uint32, s []byte) uint32 { + for i := range s { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +func match(s string, t []byte) bool { + for i, c := range t { + if s[i] != c { + return false + } + } + return true +} + +// Lookup returns the atom whose name is s. It returns zero if there is no +// such atom. The lookup is case sensitive. +func Lookup(s []byte) Atom { + if len(s) == 0 || len(s) > maxAtomLen { + return 0 + } + h := fnv(hash0, s) + if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { + return a + } + if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { + return a + } + return 0 +} + +// String returns a string whose contents are equal to s. In that sense, it is +// equivalent to string(s) but may be more efficient. +func String(s []byte) string { + if a := Lookup(s); a != 0 { + return a.String() + } + return string(s) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/atom/atom_test.go b/Godeps/_workspace/src/golang.org/x/net/html/atom/atom_test.go new file mode 100644 index 0000000..6e33704 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/atom/atom_test.go @@ -0,0 +1,109 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package atom + +import ( + "sort" + "testing" +) + +func TestKnown(t *testing.T) { + for _, s := range testAtomList { + if atom := Lookup([]byte(s)); atom.String() != s { + t.Errorf("Lookup(%q) = %#x (%q)", s, uint32(atom), atom.String()) + } + } +} + +func TestHits(t *testing.T) { + for _, a := range table { + if a == 0 { + continue + } + got := Lookup([]byte(a.String())) + if got != a { + t.Errorf("Lookup(%q) = %#x, want %#x", a.String(), uint32(got), uint32(a)) + } + } +} + +func TestMisses(t *testing.T) { + testCases := []string{ + "", + "\x00", + "\xff", + "A", + "DIV", + "Div", + "dIV", + "aa", + "a\x00", + "ab", + "abb", + "abbr0", + "abbr ", + " abbr", + " a", + "acceptcharset", + "acceptCharset", + "accept_charset", + "h0", + "h1h2", + "h7", + "onClick", + "λ", + // The following string has the same hash (0xa1d7fab7) as "onmouseover". + "\x00\x00\x00\x00\x00\x50\x18\xae\x38\xd0\xb7", + } + for _, tc := range testCases { + got := Lookup([]byte(tc)) + if got != 0 { + t.Errorf("Lookup(%q): got %d, want 0", tc, got) + } + } +} + +func TestForeignObject(t *testing.T) { + const ( + afo = Foreignobject + afO = ForeignObject + sfo = "foreignobject" + sfO = "foreignObject" + ) + if got := Lookup([]byte(sfo)); got != afo { + t.Errorf("Lookup(%q): got %#v, want %#v", sfo, got, afo) + } + if got := Lookup([]byte(sfO)); got != afO { + t.Errorf("Lookup(%q): got %#v, want %#v", sfO, got, afO) + } + if got := afo.String(); got != sfo { + t.Errorf("Atom(%#v).String(): got %q, want %q", afo, got, sfo) + } + if got := afO.String(); got != sfO { + t.Errorf("Atom(%#v).String(): got %q, want %q", afO, got, sfO) + } +} + +func BenchmarkLookup(b *testing.B) { + sortedTable := make([]string, 0, len(table)) + for _, a := range table { + if a != 0 { + sortedTable = append(sortedTable, a.String()) + } + } + sort.Strings(sortedTable) + + x := make([][]byte, 1000) + for i := range x { + x[i] = []byte(sortedTable[i%len(sortedTable)]) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range x { + Lookup(s) + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/atom/gen.go b/Godeps/_workspace/src/golang.org/x/net/html/atom/gen.go new file mode 100644 index 0000000..6bfa866 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/atom/gen.go @@ -0,0 +1,648 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +package main + +// This program generates table.go and table_test.go. +// Invoke as +// +// go run gen.go |gofmt >table.go +// go run gen.go -test |gofmt >table_test.go + +import ( + "flag" + "fmt" + "math/rand" + "os" + "sort" + "strings" +) + +// identifier converts s to a Go exported identifier. +// It converts "div" to "Div" and "accept-charset" to "AcceptCharset". +func identifier(s string) string { + b := make([]byte, 0, len(s)) + cap := true + for _, c := range s { + if c == '-' { + cap = true + continue + } + if cap && 'a' <= c && c <= 'z' { + c -= 'a' - 'A' + } + cap = false + b = append(b, byte(c)) + } + return string(b) +} + +var test = flag.Bool("test", false, "generate table_test.go") + +func main() { + flag.Parse() + + var all []string + all = append(all, elements...) + all = append(all, attributes...) + all = append(all, eventHandlers...) + all = append(all, extra...) + sort.Strings(all) + + if *test { + fmt.Printf("// generated by go run gen.go -test; DO NOT EDIT\n\n") + fmt.Printf("package atom\n\n") + fmt.Printf("var testAtomList = []string{\n") + for _, s := range all { + fmt.Printf("\t%q,\n", s) + } + fmt.Printf("}\n") + return + } + + // uniq - lists have dups + // compute max len too + maxLen := 0 + w := 0 + for _, s := range all { + if w == 0 || all[w-1] != s { + if maxLen < len(s) { + maxLen = len(s) + } + all[w] = s + w++ + } + } + all = all[:w] + + // Find hash that minimizes table size. + var best *table + for i := 0; i < 1000000; i++ { + if best != nil && 1<<(best.k-1) < len(all) { + break + } + h := rand.Uint32() + for k := uint(0); k <= 16; k++ { + if best != nil && k >= best.k { + break + } + var t table + if t.init(h, k, all) { + best = &t + break + } + } + } + if best == nil { + fmt.Fprintf(os.Stderr, "failed to construct string table\n") + os.Exit(1) + } + + // Lay out strings, using overlaps when possible. + layout := append([]string{}, all...) + + // Remove strings that are substrings of other strings + for changed := true; changed; { + changed = false + for i, s := range layout { + if s == "" { + continue + } + for j, t := range layout { + if i != j && t != "" && strings.Contains(s, t) { + changed = true + layout[j] = "" + } + } + } + } + + // Join strings where one suffix matches another prefix. + for { + // Find best i, j, k such that layout[i][len-k:] == layout[j][:k], + // maximizing overlap length k. + besti := -1 + bestj := -1 + bestk := 0 + for i, s := range layout { + if s == "" { + continue + } + for j, t := range layout { + if i == j { + continue + } + for k := bestk + 1; k <= len(s) && k <= len(t); k++ { + if s[len(s)-k:] == t[:k] { + besti = i + bestj = j + bestk = k + } + } + } + } + if bestk > 0 { + layout[besti] += layout[bestj][bestk:] + layout[bestj] = "" + continue + } + break + } + + text := strings.Join(layout, "") + + atom := map[string]uint32{} + for _, s := range all { + off := strings.Index(text, s) + if off < 0 { + panic("lost string " + s) + } + atom[s] = uint32(off<<8 | len(s)) + } + + // Generate the Go code. + fmt.Printf("// generated by go run gen.go; DO NOT EDIT\n\n") + fmt.Printf("package atom\n\nconst (\n") + for _, s := range all { + fmt.Printf("\t%s Atom = %#x\n", identifier(s), atom[s]) + } + fmt.Printf(")\n\n") + + fmt.Printf("const hash0 = %#x\n\n", best.h0) + fmt.Printf("const maxAtomLen = %d\n\n", maxLen) + + fmt.Printf("var table = [1<<%d]Atom{\n", best.k) + for i, s := range best.tab { + if s == "" { + continue + } + fmt.Printf("\t%#x: %#x, // %s\n", i, atom[s], s) + } + fmt.Printf("}\n") + datasize := (1 << best.k) * 4 + + fmt.Printf("const atomText =\n") + textsize := len(text) + for len(text) > 60 { + fmt.Printf("\t%q +\n", text[:60]) + text = text[60:] + } + fmt.Printf("\t%q\n\n", text) + + fmt.Fprintf(os.Stderr, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize) +} + +type byLen []string + +func (x byLen) Less(i, j int) bool { return len(x[i]) > len(x[j]) } +func (x byLen) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byLen) Len() int { return len(x) } + +// fnv computes the FNV hash with an arbitrary starting value h. +func fnv(h uint32, s string) uint32 { + for i := 0; i < len(s); i++ { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +// A table represents an attempt at constructing the lookup table. +// The lookup table uses cuckoo hashing, meaning that each string +// can be found in one of two positions. +type table struct { + h0 uint32 + k uint + mask uint32 + tab []string +} + +// hash returns the two hashes for s. +func (t *table) hash(s string) (h1, h2 uint32) { + h := fnv(t.h0, s) + h1 = h & t.mask + h2 = (h >> 16) & t.mask + return +} + +// init initializes the table with the given parameters. +// h0 is the initial hash value, +// k is the number of bits of hash value to use, and +// x is the list of strings to store in the table. +// init returns false if the table cannot be constructed. +func (t *table) init(h0 uint32, k uint, x []string) bool { + t.h0 = h0 + t.k = k + t.tab = make([]string, 1< len(t.tab) { + return false + } + s := t.tab[i] + h1, h2 := t.hash(s) + j := h1 + h2 - i + if t.tab[j] != "" && !t.push(j, depth+1) { + return false + } + t.tab[j] = s + return true +} + +// The lists of element names and attribute keys were taken from +// https://html.spec.whatwg.org/multipage/indices.html#index +// as of the "HTML Living Standard - Last Updated 21 February 2015" version. + +var elements = []string{ + "a", + "abbr", + "address", + "area", + "article", + "aside", + "audio", + "b", + "base", + "bdi", + "bdo", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "command", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "keygen", + "label", + "legend", + "li", + "link", + "map", + "mark", + "menu", + "menuitem", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "pre", + "progress", + "q", + "rp", + "rt", + "ruby", + "s", + "samp", + "script", + "section", + "select", + "small", + "source", + "span", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "video", + "wbr", +} + +// https://html.spec.whatwg.org/multipage/indices.html#attributes-3 + +var attributes = []string{ + "abbr", + "accept", + "accept-charset", + "accesskey", + "action", + "alt", + "async", + "autocomplete", + "autofocus", + "autoplay", + "challenge", + "charset", + "checked", + "cite", + "class", + "cols", + "colspan", + "command", + "content", + "contenteditable", + "contextmenu", + "controls", + "coords", + "crossorigin", + "data", + "datetime", + "default", + "defer", + "dir", + "dirname", + "disabled", + "download", + "draggable", + "dropzone", + "enctype", + "for", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "headers", + "height", + "hidden", + "high", + "href", + "hreflang", + "http-equiv", + "icon", + "id", + "inputmode", + "ismap", + "itemid", + "itemprop", + "itemref", + "itemscope", + "itemtype", + "keytype", + "kind", + "label", + "lang", + "list", + "loop", + "low", + "manifest", + "max", + "maxlength", + "media", + "mediagroup", + "method", + "min", + "minlength", + "multiple", + "muted", + "name", + "novalidate", + "open", + "optimum", + "pattern", + "ping", + "placeholder", + "poster", + "preload", + "radiogroup", + "readonly", + "rel", + "required", + "reversed", + "rows", + "rowspan", + "sandbox", + "spellcheck", + "scope", + "scoped", + "seamless", + "selected", + "shape", + "size", + "sizes", + "sortable", + "sorted", + "span", + "src", + "srcdoc", + "srclang", + "start", + "step", + "style", + "tabindex", + "target", + "title", + "translate", + "type", + "typemustmatch", + "usemap", + "value", + "width", + "wrap", +} + +var eventHandlers = []string{ + "onabort", + "onautocomplete", + "onautocompleteerror", + "onafterprint", + "onbeforeprint", + "onbeforeunload", + "onblur", + "oncancel", + "oncanplay", + "oncanplaythrough", + "onchange", + "onclick", + "onclose", + "oncontextmenu", + "oncuechange", + "ondblclick", + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "ondurationchange", + "onemptied", + "onended", + "onerror", + "onfocus", + "onhashchange", + "oninput", + "oninvalid", + "onkeydown", + "onkeypress", + "onkeyup", + "onlanguagechange", + "onload", + "onloadeddata", + "onloadedmetadata", + "onloadstart", + "onmessage", + "onmousedown", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "onoffline", + "ononline", + "onpagehide", + "onpageshow", + "onpause", + "onplay", + "onplaying", + "onpopstate", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onseeked", + "onseeking", + "onselect", + "onshow", + "onsort", + "onstalled", + "onstorage", + "onsubmit", + "onsuspend", + "ontimeupdate", + "ontoggle", + "onunload", + "onvolumechange", + "onwaiting", +} + +// extra are ad-hoc values not covered by any of the lists above. +var extra = []string{ + "align", + "annotation", + "annotation-xml", + "applet", + "basefont", + "bgsound", + "big", + "blink", + "center", + "color", + "desc", + "face", + "font", + "foreignObject", // HTML is case-insensitive, but SVG-embedded-in-HTML is case-sensitive. + "foreignobject", + "frame", + "frameset", + "image", + "isindex", + "listing", + "malignmark", + "marquee", + "math", + "mglyph", + "mi", + "mn", + "mo", + "ms", + "mtext", + "nobr", + "noembed", + "noframes", + "plaintext", + "prompt", + "public", + "spacer", + "strike", + "svg", + "system", + "tt", + "xmp", +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/atom/table.go b/Godeps/_workspace/src/golang.org/x/net/html/atom/table.go new file mode 100644 index 0000000..2605ba3 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/atom/table.go @@ -0,0 +1,713 @@ +// generated by go run gen.go; DO NOT EDIT + +package atom + +const ( + A Atom = 0x1 + Abbr Atom = 0x4 + Accept Atom = 0x2106 + AcceptCharset Atom = 0x210e + Accesskey Atom = 0x3309 + Action Atom = 0x1f606 + Address Atom = 0x4f307 + Align Atom = 0x1105 + Alt Atom = 0x4503 + Annotation Atom = 0x1670a + AnnotationXml Atom = 0x1670e + Applet Atom = 0x2b306 + Area Atom = 0x2fa04 + Article Atom = 0x38807 + Aside Atom = 0x8305 + Async Atom = 0x7b05 + Audio Atom = 0xa605 + Autocomplete Atom = 0x1fc0c + Autofocus Atom = 0xb309 + Autoplay Atom = 0xce08 + B Atom = 0x101 + Base Atom = 0xd604 + Basefont Atom = 0xd608 + Bdi Atom = 0x1a03 + Bdo Atom = 0xe703 + Bgsound Atom = 0x11807 + Big Atom = 0x12403 + Blink Atom = 0x12705 + Blockquote Atom = 0x12c0a + Body Atom = 0x2f04 + Br Atom = 0x202 + Button Atom = 0x13606 + Canvas Atom = 0x7f06 + Caption Atom = 0x1bb07 + Center Atom = 0x5b506 + Challenge Atom = 0x21f09 + Charset Atom = 0x2807 + Checked Atom = 0x32807 + Cite Atom = 0x3c804 + Class Atom = 0x4de05 + Code Atom = 0x14904 + Col Atom = 0x15003 + Colgroup Atom = 0x15008 + Color Atom = 0x15d05 + Cols Atom = 0x16204 + Colspan Atom = 0x16207 + Command Atom = 0x17507 + Content Atom = 0x42307 + Contenteditable Atom = 0x4230f + Contextmenu Atom = 0x3310b + Controls Atom = 0x18808 + Coords Atom = 0x19406 + Crossorigin Atom = 0x19f0b + Data Atom = 0x44a04 + Datalist Atom = 0x44a08 + Datetime Atom = 0x23c08 + Dd Atom = 0x26702 + Default Atom = 0x8607 + Defer Atom = 0x14b05 + Del Atom = 0x3ef03 + Desc Atom = 0x4db04 + Details Atom = 0x4807 + Dfn Atom = 0x6103 + Dialog Atom = 0x1b06 + Dir Atom = 0x6903 + Dirname Atom = 0x6907 + Disabled Atom = 0x10c08 + Div Atom = 0x11303 + Dl Atom = 0x11e02 + Download Atom = 0x40008 + Draggable Atom = 0x17b09 + Dropzone Atom = 0x39108 + Dt Atom = 0x50902 + Em Atom = 0x6502 + Embed Atom = 0x6505 + Enctype Atom = 0x21107 + Face Atom = 0x5b304 + Fieldset Atom = 0x1b008 + Figcaption Atom = 0x1b80a + Figure Atom = 0x1cc06 + Font Atom = 0xda04 + Footer Atom = 0x8d06 + For Atom = 0x1d803 + ForeignObject Atom = 0x1d80d + Foreignobject Atom = 0x1e50d + Form Atom = 0x1f204 + Formaction Atom = 0x1f20a + Formenctype Atom = 0x20d0b + Formmethod Atom = 0x2280a + Formnovalidate Atom = 0x2320e + Formtarget Atom = 0x2470a + Frame Atom = 0x9a05 + Frameset Atom = 0x9a08 + H1 Atom = 0x26e02 + H2 Atom = 0x29402 + H3 Atom = 0x2a702 + H4 Atom = 0x2e902 + H5 Atom = 0x2f302 + H6 Atom = 0x50b02 + Head Atom = 0x2d504 + Header Atom = 0x2d506 + Headers Atom = 0x2d507 + Height Atom = 0x25106 + Hgroup Atom = 0x25906 + Hidden Atom = 0x26506 + High Atom = 0x26b04 + Hr Atom = 0x27002 + Href Atom = 0x27004 + Hreflang Atom = 0x27008 + Html Atom = 0x25504 + HttpEquiv Atom = 0x2780a + I Atom = 0x601 + Icon Atom = 0x42204 + Id Atom = 0x8502 + Iframe Atom = 0x29606 + Image Atom = 0x29c05 + Img Atom = 0x2a103 + Input Atom = 0x3e805 + Inputmode Atom = 0x3e809 + Ins Atom = 0x1a803 + Isindex Atom = 0x2a907 + Ismap Atom = 0x2b005 + Itemid Atom = 0x33c06 + Itemprop Atom = 0x3c908 + Itemref Atom = 0x5ad07 + Itemscope Atom = 0x2b909 + Itemtype Atom = 0x2c308 + Kbd Atom = 0x1903 + Keygen Atom = 0x3906 + Keytype Atom = 0x53707 + Kind Atom = 0x10904 + Label Atom = 0xf005 + Lang Atom = 0x27404 + Legend Atom = 0x18206 + Li Atom = 0x1202 + Link Atom = 0x12804 + List Atom = 0x44e04 + Listing Atom = 0x44e07 + Loop Atom = 0xf404 + Low Atom = 0x11f03 + Malignmark Atom = 0x100a + Manifest Atom = 0x5f108 + Map Atom = 0x2b203 + Mark Atom = 0x1604 + Marquee Atom = 0x2cb07 + Math Atom = 0x2d204 + Max Atom = 0x2e103 + Maxlength Atom = 0x2e109 + Media Atom = 0x6e05 + Mediagroup Atom = 0x6e0a + Menu Atom = 0x33804 + Menuitem Atom = 0x33808 + Meta Atom = 0x45d04 + Meter Atom = 0x24205 + Method Atom = 0x22c06 + Mglyph Atom = 0x2a206 + Mi Atom = 0x2eb02 + Min Atom = 0x2eb03 + Minlength Atom = 0x2eb09 + Mn Atom = 0x23502 + Mo Atom = 0x3ed02 + Ms Atom = 0x2bc02 + Mtext Atom = 0x2f505 + Multiple Atom = 0x30308 + Muted Atom = 0x30b05 + Name Atom = 0x6c04 + Nav Atom = 0x3e03 + Nobr Atom = 0x5704 + Noembed Atom = 0x6307 + Noframes Atom = 0x9808 + Noscript Atom = 0x3d208 + Novalidate Atom = 0x2360a + Object Atom = 0x1ec06 + Ol Atom = 0xc902 + Onabort Atom = 0x13a07 + Onafterprint Atom = 0x1c00c + Onautocomplete Atom = 0x1fa0e + Onautocompleteerror Atom = 0x1fa13 + Onbeforeprint Atom = 0x6040d + Onbeforeunload Atom = 0x4e70e + Onblur Atom = 0xaa06 + Oncancel Atom = 0xe908 + Oncanplay Atom = 0x28509 + Oncanplaythrough Atom = 0x28510 + Onchange Atom = 0x3a708 + Onclick Atom = 0x31007 + Onclose Atom = 0x31707 + Oncontextmenu Atom = 0x32f0d + Oncuechange Atom = 0x3420b + Ondblclick Atom = 0x34d0a + Ondrag Atom = 0x35706 + Ondragend Atom = 0x35709 + Ondragenter Atom = 0x3600b + Ondragleave Atom = 0x36b0b + Ondragover Atom = 0x3760a + Ondragstart Atom = 0x3800b + Ondrop Atom = 0x38f06 + Ondurationchange Atom = 0x39f10 + Onemptied Atom = 0x39609 + Onended Atom = 0x3af07 + Onerror Atom = 0x3b607 + Onfocus Atom = 0x3bd07 + Onhashchange Atom = 0x3da0c + Oninput Atom = 0x3e607 + Oninvalid Atom = 0x3f209 + Onkeydown Atom = 0x3fb09 + Onkeypress Atom = 0x4080a + Onkeyup Atom = 0x41807 + Onlanguagechange Atom = 0x43210 + Onload Atom = 0x44206 + Onloadeddata Atom = 0x4420c + Onloadedmetadata Atom = 0x45510 + Onloadstart Atom = 0x46b0b + Onmessage Atom = 0x47609 + Onmousedown Atom = 0x47f0b + Onmousemove Atom = 0x48a0b + Onmouseout Atom = 0x4950a + Onmouseover Atom = 0x4a20b + Onmouseup Atom = 0x4ad09 + Onmousewheel Atom = 0x4b60c + Onoffline Atom = 0x4c209 + Ononline Atom = 0x4cb08 + Onpagehide Atom = 0x4d30a + Onpageshow Atom = 0x4fe0a + Onpause Atom = 0x50d07 + Onplay Atom = 0x51706 + Onplaying Atom = 0x51709 + Onpopstate Atom = 0x5200a + Onprogress Atom = 0x52a0a + Onratechange Atom = 0x53e0c + Onreset Atom = 0x54a07 + Onresize Atom = 0x55108 + Onscroll Atom = 0x55f08 + Onseeked Atom = 0x56708 + Onseeking Atom = 0x56f09 + Onselect Atom = 0x57808 + Onshow Atom = 0x58206 + Onsort Atom = 0x58b06 + Onstalled Atom = 0x59509 + Onstorage Atom = 0x59e09 + Onsubmit Atom = 0x5a708 + Onsuspend Atom = 0x5bb09 + Ontimeupdate Atom = 0xdb0c + Ontoggle Atom = 0x5c408 + Onunload Atom = 0x5cc08 + Onvolumechange Atom = 0x5d40e + Onwaiting Atom = 0x5e209 + Open Atom = 0x3cf04 + Optgroup Atom = 0xf608 + Optimum Atom = 0x5eb07 + Option Atom = 0x60006 + Output Atom = 0x49c06 + P Atom = 0xc01 + Param Atom = 0xc05 + Pattern Atom = 0x5107 + Ping Atom = 0x7704 + Placeholder Atom = 0xc30b + Plaintext Atom = 0xfd09 + Poster Atom = 0x15706 + Pre Atom = 0x25e03 + Preload Atom = 0x25e07 + Progress Atom = 0x52c08 + Prompt Atom = 0x5fa06 + Public Atom = 0x41e06 + Q Atom = 0x13101 + Radiogroup Atom = 0x30a + Readonly Atom = 0x2fb08 + Rel Atom = 0x25f03 + Required Atom = 0x1d008 + Reversed Atom = 0x5a08 + Rows Atom = 0x9204 + Rowspan Atom = 0x9207 + Rp Atom = 0x1c602 + Rt Atom = 0x13f02 + Ruby Atom = 0xaf04 + S Atom = 0x2c01 + Samp Atom = 0x4e04 + Sandbox Atom = 0xbb07 + Scope Atom = 0x2bd05 + Scoped Atom = 0x2bd06 + Script Atom = 0x3d406 + Seamless Atom = 0x31c08 + Section Atom = 0x4e207 + Select Atom = 0x57a06 + Selected Atom = 0x57a08 + Shape Atom = 0x4f905 + Size Atom = 0x55504 + Sizes Atom = 0x55505 + Small Atom = 0x18f05 + Sortable Atom = 0x58d08 + Sorted Atom = 0x19906 + Source Atom = 0x1aa06 + Spacer Atom = 0x2db06 + Span Atom = 0x9504 + Spellcheck Atom = 0x3230a + Src Atom = 0x3c303 + Srcdoc Atom = 0x3c306 + Srclang Atom = 0x41107 + Start Atom = 0x38605 + Step Atom = 0x5f704 + Strike Atom = 0x53306 + Strong Atom = 0x55906 + Style Atom = 0x61105 + Sub Atom = 0x5a903 + Summary Atom = 0x61607 + Sup Atom = 0x61d03 + Svg Atom = 0x62003 + System Atom = 0x62306 + Tabindex Atom = 0x46308 + Table Atom = 0x42d05 + Target Atom = 0x24b06 + Tbody Atom = 0x2e05 + Td Atom = 0x4702 + Template Atom = 0x62608 + Textarea Atom = 0x2f608 + Tfoot Atom = 0x8c05 + Th Atom = 0x22e02 + Thead Atom = 0x2d405 + Time Atom = 0xdd04 + Title Atom = 0xa105 + Tr Atom = 0x10502 + Track Atom = 0x10505 + Translate Atom = 0x14009 + Tt Atom = 0x5302 + Type Atom = 0x21404 + Typemustmatch Atom = 0x2140d + U Atom = 0xb01 + Ul Atom = 0x8a02 + Usemap Atom = 0x51106 + Value Atom = 0x4005 + Var Atom = 0x11503 + Video Atom = 0x28105 + Wbr Atom = 0x12103 + Width Atom = 0x50705 + Wrap Atom = 0x58704 + Xmp Atom = 0xc103 +) + +const hash0 = 0xc17da63e + +const maxAtomLen = 19 + +var table = [1 << 9]Atom{ + 0x1: 0x48a0b, // onmousemove + 0x2: 0x5e209, // onwaiting + 0x3: 0x1fa13, // onautocompleteerror + 0x4: 0x5fa06, // prompt + 0x7: 0x5eb07, // optimum + 0x8: 0x1604, // mark + 0xa: 0x5ad07, // itemref + 0xb: 0x4fe0a, // onpageshow + 0xc: 0x57a06, // select + 0xd: 0x17b09, // draggable + 0xe: 0x3e03, // nav + 0xf: 0x17507, // command + 0x11: 0xb01, // u + 0x14: 0x2d507, // headers + 0x15: 0x44a08, // datalist + 0x17: 0x4e04, // samp + 0x1a: 0x3fb09, // onkeydown + 0x1b: 0x55f08, // onscroll + 0x1c: 0x15003, // col + 0x20: 0x3c908, // itemprop + 0x21: 0x2780a, // http-equiv + 0x22: 0x61d03, // sup + 0x24: 0x1d008, // required + 0x2b: 0x25e07, // preload + 0x2c: 0x6040d, // onbeforeprint + 0x2d: 0x3600b, // ondragenter + 0x2e: 0x50902, // dt + 0x2f: 0x5a708, // onsubmit + 0x30: 0x27002, // hr + 0x31: 0x32f0d, // oncontextmenu + 0x33: 0x29c05, // image + 0x34: 0x50d07, // onpause + 0x35: 0x25906, // hgroup + 0x36: 0x7704, // ping + 0x37: 0x57808, // onselect + 0x3a: 0x11303, // div + 0x3b: 0x1fa0e, // onautocomplete + 0x40: 0x2eb02, // mi + 0x41: 0x31c08, // seamless + 0x42: 0x2807, // charset + 0x43: 0x8502, // id + 0x44: 0x5200a, // onpopstate + 0x45: 0x3ef03, // del + 0x46: 0x2cb07, // marquee + 0x47: 0x3309, // accesskey + 0x49: 0x8d06, // footer + 0x4a: 0x44e04, // list + 0x4b: 0x2b005, // ismap + 0x51: 0x33804, // menu + 0x52: 0x2f04, // body + 0x55: 0x9a08, // frameset + 0x56: 0x54a07, // onreset + 0x57: 0x12705, // blink + 0x58: 0xa105, // title + 0x59: 0x38807, // article + 0x5b: 0x22e02, // th + 0x5d: 0x13101, // q + 0x5e: 0x3cf04, // open + 0x5f: 0x2fa04, // area + 0x61: 0x44206, // onload + 0x62: 0xda04, // font + 0x63: 0xd604, // base + 0x64: 0x16207, // colspan + 0x65: 0x53707, // keytype + 0x66: 0x11e02, // dl + 0x68: 0x1b008, // fieldset + 0x6a: 0x2eb03, // min + 0x6b: 0x11503, // var + 0x6f: 0x2d506, // header + 0x70: 0x13f02, // rt + 0x71: 0x15008, // colgroup + 0x72: 0x23502, // mn + 0x74: 0x13a07, // onabort + 0x75: 0x3906, // keygen + 0x76: 0x4c209, // onoffline + 0x77: 0x21f09, // challenge + 0x78: 0x2b203, // map + 0x7a: 0x2e902, // h4 + 0x7b: 0x3b607, // onerror + 0x7c: 0x2e109, // maxlength + 0x7d: 0x2f505, // mtext + 0x7e: 0xbb07, // sandbox + 0x7f: 0x58b06, // onsort + 0x80: 0x100a, // malignmark + 0x81: 0x45d04, // meta + 0x82: 0x7b05, // async + 0x83: 0x2a702, // h3 + 0x84: 0x26702, // dd + 0x85: 0x27004, // href + 0x86: 0x6e0a, // mediagroup + 0x87: 0x19406, // coords + 0x88: 0x41107, // srclang + 0x89: 0x34d0a, // ondblclick + 0x8a: 0x4005, // value + 0x8c: 0xe908, // oncancel + 0x8e: 0x3230a, // spellcheck + 0x8f: 0x9a05, // frame + 0x91: 0x12403, // big + 0x94: 0x1f606, // action + 0x95: 0x6903, // dir + 0x97: 0x2fb08, // readonly + 0x99: 0x42d05, // table + 0x9a: 0x61607, // summary + 0x9b: 0x12103, // wbr + 0x9c: 0x30a, // radiogroup + 0x9d: 0x6c04, // name + 0x9f: 0x62306, // system + 0xa1: 0x15d05, // color + 0xa2: 0x7f06, // canvas + 0xa3: 0x25504, // html + 0xa5: 0x56f09, // onseeking + 0xac: 0x4f905, // shape + 0xad: 0x25f03, // rel + 0xae: 0x28510, // oncanplaythrough + 0xaf: 0x3760a, // ondragover + 0xb0: 0x62608, // template + 0xb1: 0x1d80d, // foreignObject + 0xb3: 0x9204, // rows + 0xb6: 0x44e07, // listing + 0xb7: 0x49c06, // output + 0xb9: 0x3310b, // contextmenu + 0xbb: 0x11f03, // low + 0xbc: 0x1c602, // rp + 0xbd: 0x5bb09, // onsuspend + 0xbe: 0x13606, // button + 0xbf: 0x4db04, // desc + 0xc1: 0x4e207, // section + 0xc2: 0x52a0a, // onprogress + 0xc3: 0x59e09, // onstorage + 0xc4: 0x2d204, // math + 0xc5: 0x4503, // alt + 0xc7: 0x8a02, // ul + 0xc8: 0x5107, // pattern + 0xc9: 0x4b60c, // onmousewheel + 0xca: 0x35709, // ondragend + 0xcb: 0xaf04, // ruby + 0xcc: 0xc01, // p + 0xcd: 0x31707, // onclose + 0xce: 0x24205, // meter + 0xcf: 0x11807, // bgsound + 0xd2: 0x25106, // height + 0xd4: 0x101, // b + 0xd5: 0x2c308, // itemtype + 0xd8: 0x1bb07, // caption + 0xd9: 0x10c08, // disabled + 0xdb: 0x33808, // menuitem + 0xdc: 0x62003, // svg + 0xdd: 0x18f05, // small + 0xde: 0x44a04, // data + 0xe0: 0x4cb08, // ononline + 0xe1: 0x2a206, // mglyph + 0xe3: 0x6505, // embed + 0xe4: 0x10502, // tr + 0xe5: 0x46b0b, // onloadstart + 0xe7: 0x3c306, // srcdoc + 0xeb: 0x5c408, // ontoggle + 0xed: 0xe703, // bdo + 0xee: 0x4702, // td + 0xef: 0x8305, // aside + 0xf0: 0x29402, // h2 + 0xf1: 0x52c08, // progress + 0xf2: 0x12c0a, // blockquote + 0xf4: 0xf005, // label + 0xf5: 0x601, // i + 0xf7: 0x9207, // rowspan + 0xfb: 0x51709, // onplaying + 0xfd: 0x2a103, // img + 0xfe: 0xf608, // optgroup + 0xff: 0x42307, // content + 0x101: 0x53e0c, // onratechange + 0x103: 0x3da0c, // onhashchange + 0x104: 0x4807, // details + 0x106: 0x40008, // download + 0x109: 0x14009, // translate + 0x10b: 0x4230f, // contenteditable + 0x10d: 0x36b0b, // ondragleave + 0x10e: 0x2106, // accept + 0x10f: 0x57a08, // selected + 0x112: 0x1f20a, // formaction + 0x113: 0x5b506, // center + 0x115: 0x45510, // onloadedmetadata + 0x116: 0x12804, // link + 0x117: 0xdd04, // time + 0x118: 0x19f0b, // crossorigin + 0x119: 0x3bd07, // onfocus + 0x11a: 0x58704, // wrap + 0x11b: 0x42204, // icon + 0x11d: 0x28105, // video + 0x11e: 0x4de05, // class + 0x121: 0x5d40e, // onvolumechange + 0x122: 0xaa06, // onblur + 0x123: 0x2b909, // itemscope + 0x124: 0x61105, // style + 0x127: 0x41e06, // public + 0x129: 0x2320e, // formnovalidate + 0x12a: 0x58206, // onshow + 0x12c: 0x51706, // onplay + 0x12d: 0x3c804, // cite + 0x12e: 0x2bc02, // ms + 0x12f: 0xdb0c, // ontimeupdate + 0x130: 0x10904, // kind + 0x131: 0x2470a, // formtarget + 0x135: 0x3af07, // onended + 0x136: 0x26506, // hidden + 0x137: 0x2c01, // s + 0x139: 0x2280a, // formmethod + 0x13a: 0x3e805, // input + 0x13c: 0x50b02, // h6 + 0x13d: 0xc902, // ol + 0x13e: 0x3420b, // oncuechange + 0x13f: 0x1e50d, // foreignobject + 0x143: 0x4e70e, // onbeforeunload + 0x144: 0x2bd05, // scope + 0x145: 0x39609, // onemptied + 0x146: 0x14b05, // defer + 0x147: 0xc103, // xmp + 0x148: 0x39f10, // ondurationchange + 0x149: 0x1903, // kbd + 0x14c: 0x47609, // onmessage + 0x14d: 0x60006, // option + 0x14e: 0x2eb09, // minlength + 0x14f: 0x32807, // checked + 0x150: 0xce08, // autoplay + 0x152: 0x202, // br + 0x153: 0x2360a, // novalidate + 0x156: 0x6307, // noembed + 0x159: 0x31007, // onclick + 0x15a: 0x47f0b, // onmousedown + 0x15b: 0x3a708, // onchange + 0x15e: 0x3f209, // oninvalid + 0x15f: 0x2bd06, // scoped + 0x160: 0x18808, // controls + 0x161: 0x30b05, // muted + 0x162: 0x58d08, // sortable + 0x163: 0x51106, // usemap + 0x164: 0x1b80a, // figcaption + 0x165: 0x35706, // ondrag + 0x166: 0x26b04, // high + 0x168: 0x3c303, // src + 0x169: 0x15706, // poster + 0x16b: 0x1670e, // annotation-xml + 0x16c: 0x5f704, // step + 0x16d: 0x4, // abbr + 0x16e: 0x1b06, // dialog + 0x170: 0x1202, // li + 0x172: 0x3ed02, // mo + 0x175: 0x1d803, // for + 0x176: 0x1a803, // ins + 0x178: 0x55504, // size + 0x179: 0x43210, // onlanguagechange + 0x17a: 0x8607, // default + 0x17b: 0x1a03, // bdi + 0x17c: 0x4d30a, // onpagehide + 0x17d: 0x6907, // dirname + 0x17e: 0x21404, // type + 0x17f: 0x1f204, // form + 0x181: 0x28509, // oncanplay + 0x182: 0x6103, // dfn + 0x183: 0x46308, // tabindex + 0x186: 0x6502, // em + 0x187: 0x27404, // lang + 0x189: 0x39108, // dropzone + 0x18a: 0x4080a, // onkeypress + 0x18b: 0x23c08, // datetime + 0x18c: 0x16204, // cols + 0x18d: 0x1, // a + 0x18e: 0x4420c, // onloadeddata + 0x190: 0xa605, // audio + 0x192: 0x2e05, // tbody + 0x193: 0x22c06, // method + 0x195: 0xf404, // loop + 0x196: 0x29606, // iframe + 0x198: 0x2d504, // head + 0x19e: 0x5f108, // manifest + 0x19f: 0xb309, // autofocus + 0x1a0: 0x14904, // code + 0x1a1: 0x55906, // strong + 0x1a2: 0x30308, // multiple + 0x1a3: 0xc05, // param + 0x1a6: 0x21107, // enctype + 0x1a7: 0x5b304, // face + 0x1a8: 0xfd09, // plaintext + 0x1a9: 0x26e02, // h1 + 0x1aa: 0x59509, // onstalled + 0x1ad: 0x3d406, // script + 0x1ae: 0x2db06, // spacer + 0x1af: 0x55108, // onresize + 0x1b0: 0x4a20b, // onmouseover + 0x1b1: 0x5cc08, // onunload + 0x1b2: 0x56708, // onseeked + 0x1b4: 0x2140d, // typemustmatch + 0x1b5: 0x1cc06, // figure + 0x1b6: 0x4950a, // onmouseout + 0x1b7: 0x25e03, // pre + 0x1b8: 0x50705, // width + 0x1b9: 0x19906, // sorted + 0x1bb: 0x5704, // nobr + 0x1be: 0x5302, // tt + 0x1bf: 0x1105, // align + 0x1c0: 0x3e607, // oninput + 0x1c3: 0x41807, // onkeyup + 0x1c6: 0x1c00c, // onafterprint + 0x1c7: 0x210e, // accept-charset + 0x1c8: 0x33c06, // itemid + 0x1c9: 0x3e809, // inputmode + 0x1cb: 0x53306, // strike + 0x1cc: 0x5a903, // sub + 0x1cd: 0x10505, // track + 0x1ce: 0x38605, // start + 0x1d0: 0xd608, // basefont + 0x1d6: 0x1aa06, // source + 0x1d7: 0x18206, // legend + 0x1d8: 0x2d405, // thead + 0x1da: 0x8c05, // tfoot + 0x1dd: 0x1ec06, // object + 0x1de: 0x6e05, // media + 0x1df: 0x1670a, // annotation + 0x1e0: 0x20d0b, // formenctype + 0x1e2: 0x3d208, // noscript + 0x1e4: 0x55505, // sizes + 0x1e5: 0x1fc0c, // autocomplete + 0x1e6: 0x9504, // span + 0x1e7: 0x9808, // noframes + 0x1e8: 0x24b06, // target + 0x1e9: 0x38f06, // ondrop + 0x1ea: 0x2b306, // applet + 0x1ec: 0x5a08, // reversed + 0x1f0: 0x2a907, // isindex + 0x1f3: 0x27008, // hreflang + 0x1f5: 0x2f302, // h5 + 0x1f6: 0x4f307, // address + 0x1fa: 0x2e103, // max + 0x1fb: 0xc30b, // placeholder + 0x1fc: 0x2f608, // textarea + 0x1fe: 0x4ad09, // onmouseup + 0x1ff: 0x3800b, // ondragstart +} + +const atomText = "abbradiogrouparamalignmarkbdialogaccept-charsetbodyaccesskey" + + "genavaluealtdetailsampatternobreversedfnoembedirnamediagroup" + + "ingasyncanvasidefaultfooterowspanoframesetitleaudionblurubya" + + "utofocusandboxmplaceholderautoplaybasefontimeupdatebdoncance" + + "labelooptgrouplaintextrackindisabledivarbgsoundlowbrbigblink" + + "blockquotebuttonabortranslatecodefercolgroupostercolorcolspa" + + "nnotation-xmlcommandraggablegendcontrolsmallcoordsortedcross" + + "originsourcefieldsetfigcaptionafterprintfigurequiredforeignO" + + "bjectforeignobjectformactionautocompleteerrorformenctypemust" + + "matchallengeformmethodformnovalidatetimeterformtargetheightm" + + "lhgroupreloadhiddenhigh1hreflanghttp-equivideoncanplaythroug" + + "h2iframeimageimglyph3isindexismappletitemscopeditemtypemarqu" + + "eematheaderspacermaxlength4minlength5mtextareadonlymultiplem" + + "utedonclickoncloseamlesspellcheckedoncontextmenuitemidoncuec" + + "hangeondblclickondragendondragenterondragleaveondragoverondr" + + "agstarticleondropzonemptiedondurationchangeonendedonerroronf" + + "ocusrcdocitempropenoscriptonhashchangeoninputmodeloninvalido" + + "nkeydownloadonkeypressrclangonkeyupublicontenteditableonlang" + + "uagechangeonloadeddatalistingonloadedmetadatabindexonloadsta" + + "rtonmessageonmousedownonmousemoveonmouseoutputonmouseoveronm" + + "ouseuponmousewheelonofflineononlineonpagehidesclassectionbef" + + "oreunloaddresshapeonpageshowidth6onpausemaponplayingonpopsta" + + "teonprogresstrikeytypeonratechangeonresetonresizestrongonscr" + + "ollonseekedonseekingonselectedonshowraponsortableonstalledon" + + "storageonsubmitemrefacenteronsuspendontoggleonunloadonvolume" + + "changeonwaitingoptimumanifestepromptoptionbeforeprintstylesu" + + "mmarysupsvgsystemplate" diff --git a/Godeps/_workspace/src/golang.org/x/net/html/atom/table_test.go b/Godeps/_workspace/src/golang.org/x/net/html/atom/table_test.go new file mode 100644 index 0000000..0f2ecce --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/atom/table_test.go @@ -0,0 +1,351 @@ +// generated by go run gen.go -test; DO NOT EDIT + +package atom + +var testAtomList = []string{ + "a", + "abbr", + "abbr", + "accept", + "accept-charset", + "accesskey", + "action", + "address", + "align", + "alt", + "annotation", + "annotation-xml", + "applet", + "area", + "article", + "aside", + "async", + "audio", + "autocomplete", + "autofocus", + "autoplay", + "b", + "base", + "basefont", + "bdi", + "bdo", + "bgsound", + "big", + "blink", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "center", + "challenge", + "charset", + "checked", + "cite", + "cite", + "class", + "code", + "col", + "colgroup", + "color", + "cols", + "colspan", + "command", + "command", + "content", + "contenteditable", + "contextmenu", + "controls", + "coords", + "crossorigin", + "data", + "data", + "datalist", + "datetime", + "dd", + "default", + "defer", + "del", + "desc", + "details", + "dfn", + "dialog", + "dir", + "dirname", + "disabled", + "div", + "dl", + "download", + "draggable", + "dropzone", + "dt", + "em", + "embed", + "enctype", + "face", + "fieldset", + "figcaption", + "figure", + "font", + "footer", + "for", + "foreignObject", + "foreignobject", + "form", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "headers", + "height", + "hgroup", + "hidden", + "high", + "hr", + "href", + "hreflang", + "html", + "http-equiv", + "i", + "icon", + "id", + "iframe", + "image", + "img", + "input", + "inputmode", + "ins", + "isindex", + "ismap", + "itemid", + "itemprop", + "itemref", + "itemscope", + "itemtype", + "kbd", + "keygen", + "keytype", + "kind", + "label", + "label", + "lang", + "legend", + "li", + "link", + "list", + "listing", + "loop", + "low", + "malignmark", + "manifest", + "map", + "mark", + "marquee", + "math", + "max", + "maxlength", + "media", + "mediagroup", + "menu", + "menuitem", + "meta", + "meter", + "method", + "mglyph", + "mi", + "min", + "minlength", + "mn", + "mo", + "ms", + "mtext", + "multiple", + "muted", + "name", + "nav", + "nobr", + "noembed", + "noframes", + "noscript", + "novalidate", + "object", + "ol", + "onabort", + "onafterprint", + "onautocomplete", + "onautocompleteerror", + "onbeforeprint", + "onbeforeunload", + "onblur", + "oncancel", + "oncanplay", + "oncanplaythrough", + "onchange", + "onclick", + "onclose", + "oncontextmenu", + "oncuechange", + "ondblclick", + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "ondurationchange", + "onemptied", + "onended", + "onerror", + "onfocus", + "onhashchange", + "oninput", + "oninvalid", + "onkeydown", + "onkeypress", + "onkeyup", + "onlanguagechange", + "onload", + "onloadeddata", + "onloadedmetadata", + "onloadstart", + "onmessage", + "onmousedown", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "onoffline", + "ononline", + "onpagehide", + "onpageshow", + "onpause", + "onplay", + "onplaying", + "onpopstate", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onseeked", + "onseeking", + "onselect", + "onshow", + "onsort", + "onstalled", + "onstorage", + "onsubmit", + "onsuspend", + "ontimeupdate", + "ontoggle", + "onunload", + "onvolumechange", + "onwaiting", + "open", + "optgroup", + "optimum", + "option", + "output", + "p", + "param", + "pattern", + "ping", + "placeholder", + "plaintext", + "poster", + "pre", + "preload", + "progress", + "prompt", + "public", + "q", + "radiogroup", + "readonly", + "rel", + "required", + "reversed", + "rows", + "rowspan", + "rp", + "rt", + "ruby", + "s", + "samp", + "sandbox", + "scope", + "scoped", + "script", + "seamless", + "section", + "select", + "selected", + "shape", + "size", + "sizes", + "small", + "sortable", + "sorted", + "source", + "spacer", + "span", + "span", + "spellcheck", + "src", + "srcdoc", + "srclang", + "start", + "step", + "strike", + "strong", + "style", + "style", + "sub", + "summary", + "sup", + "svg", + "system", + "tabindex", + "table", + "target", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "title", + "tr", + "track", + "translate", + "tt", + "type", + "typemustmatch", + "u", + "ul", + "usemap", + "value", + "var", + "video", + "wbr", + "width", + "wrap", + "xmp", +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/charset.go b/Godeps/_workspace/src/golang.org/x/net/html/charset/charset.go new file mode 100644 index 0000000..c5aa0a3 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/charset.go @@ -0,0 +1,244 @@ +// 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 charset provides common text encodings for HTML documents. +// +// The mapping from encoding labels to encodings is defined at +// https://encoding.spec.whatwg.org/. +package charset + +import ( + "bytes" + "fmt" + "io" + "mime" + "strings" + "unicode/utf8" + + "github.com/appc/acpush/Godeps/_workspace/src/golang.org/x/net/html" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/transform" +) + +// Lookup returns the encoding with the specified label, and its canonical +// name. It returns nil and the empty string if label is not one of the +// standard encodings for HTML. Matching is case-insensitive and ignores +// leading and trailing whitespace. +func Lookup(label string) (e encoding.Encoding, name string) { + label = strings.ToLower(strings.Trim(label, "\t\n\r\f ")) + enc := encodings[label] + return enc.e, enc.name +} + +// DetermineEncoding determines the encoding of an HTML document by examining +// up to the first 1024 bytes of content and the declared Content-Type. +// +// See http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#determining-the-character-encoding +func DetermineEncoding(content []byte, contentType string) (e encoding.Encoding, name string, certain bool) { + if len(content) > 1024 { + content = content[:1024] + } + + for _, b := range boms { + if bytes.HasPrefix(content, b.bom) { + e, name = Lookup(b.enc) + return e, name, true + } + } + + if _, params, err := mime.ParseMediaType(contentType); err == nil { + if cs, ok := params["charset"]; ok { + if e, name = Lookup(cs); e != nil { + return e, name, true + } + } + } + + if len(content) > 0 { + e, name = prescan(content) + if e != nil { + return e, name, false + } + } + + // Try to detect UTF-8. + // First eliminate any partial rune at the end. + for i := len(content) - 1; i >= 0 && i > len(content)-4; i-- { + b := content[i] + if b < 0x80 { + break + } + if utf8.RuneStart(b) { + content = content[:i] + break + } + } + hasHighBit := false + for _, c := range content { + if c >= 0x80 { + hasHighBit = true + break + } + } + if hasHighBit && utf8.Valid(content) { + return encoding.Nop, "utf-8", false + } + + // TODO: change default depending on user's locale? + return charmap.Windows1252, "windows-1252", false +} + +// NewReader returns an io.Reader that converts the content of r to UTF-8. +// It calls DetermineEncoding to find out what r's encoding is. +func NewReader(r io.Reader, contentType string) (io.Reader, error) { + preview := make([]byte, 1024) + n, err := io.ReadFull(r, preview) + switch { + case err == io.ErrUnexpectedEOF: + preview = preview[:n] + r = bytes.NewReader(preview) + case err != nil: + return nil, err + default: + r = io.MultiReader(bytes.NewReader(preview), r) + } + + if e, _, _ := DetermineEncoding(preview, contentType); e != encoding.Nop { + r = transform.NewReader(r, e.NewDecoder()) + } + return r, nil +} + +// NewReaderLabel returns a reader that converts from the specified charset to +// UTF-8. It uses Lookup to find the encoding that corresponds to label, and +// returns an error if Lookup returns nil. It is suitable for use as +// encoding/xml.Decoder's CharsetReader function. +func NewReaderLabel(label string, input io.Reader) (io.Reader, error) { + e, _ := Lookup(label) + if e == nil { + return nil, fmt.Errorf("unsupported charset: %q", label) + } + return transform.NewReader(input, e.NewDecoder()), nil +} + +func prescan(content []byte) (e encoding.Encoding, name string) { + z := html.NewTokenizer(bytes.NewReader(content)) + for { + switch z.Next() { + case html.ErrorToken: + return nil, "" + + case html.StartTagToken, html.SelfClosingTagToken: + tagName, hasAttr := z.TagName() + if !bytes.Equal(tagName, []byte("meta")) { + continue + } + attrList := make(map[string]bool) + gotPragma := false + + const ( + dontKnow = iota + doNeedPragma + doNotNeedPragma + ) + needPragma := dontKnow + + name = "" + e = nil + for hasAttr { + var key, val []byte + key, val, hasAttr = z.TagAttr() + ks := string(key) + if attrList[ks] { + continue + } + attrList[ks] = true + for i, c := range val { + if 'A' <= c && c <= 'Z' { + val[i] = c + 0x20 + } + } + + switch ks { + case "http-equiv": + if bytes.Equal(val, []byte("content-type")) { + gotPragma = true + } + + case "content": + if e == nil { + name = fromMetaElement(string(val)) + if name != "" { + e, name = Lookup(name) + if e != nil { + needPragma = doNeedPragma + } + } + } + + case "charset": + e, name = Lookup(string(val)) + needPragma = doNotNeedPragma + } + } + + if needPragma == dontKnow || needPragma == doNeedPragma && !gotPragma { + continue + } + + if strings.HasPrefix(name, "utf-16") { + name = "utf-8" + e = encoding.Nop + } + + if e != nil { + return e, name + } + } + } +} + +func fromMetaElement(s string) string { + for s != "" { + csLoc := strings.Index(s, "charset") + if csLoc == -1 { + return "" + } + s = s[csLoc+len("charset"):] + s = strings.TrimLeft(s, " \t\n\f\r") + if !strings.HasPrefix(s, "=") { + continue + } + s = s[1:] + s = strings.TrimLeft(s, " \t\n\f\r") + if s == "" { + return "" + } + if q := s[0]; q == '"' || q == '\'' { + s = s[1:] + closeQuote := strings.IndexRune(s, rune(q)) + if closeQuote == -1 { + return "" + } + return s[:closeQuote] + } + + end := strings.IndexAny(s, "; \t\n\f\r") + if end == -1 { + end = len(s) + } + return s[:end] + } + return "" +} + +var boms = []struct { + bom []byte + enc string +}{ + {[]byte{0xfe, 0xff}, "utf-16be"}, + {[]byte{0xff, 0xfe}, "utf-16le"}, + {[]byte{0xef, 0xbb, 0xbf}, "utf-8"}, +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/charset_test.go b/Godeps/_workspace/src/golang.org/x/net/html/charset/charset_test.go new file mode 100644 index 0000000..8b10399 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/charset_test.go @@ -0,0 +1,236 @@ +// 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 charset + +import ( + "bytes" + "encoding/xml" + "io/ioutil" + "runtime" + "strings" + "testing" + + "golang.org/x/text/transform" +) + +func transformString(t transform.Transformer, s string) (string, error) { + r := transform.NewReader(strings.NewReader(s), t) + b, err := ioutil.ReadAll(r) + return string(b), err +} + +var testCases = []struct { + utf8, other, otherEncoding string +}{ + {"Résumé", "Résumé", "utf8"}, + {"Résumé", "R\xe9sum\xe9", "latin1"}, + {"これは漢字です。", "S0\x8c0o0\"oW[g0Y0\x020", "UTF-16LE"}, + {"これは漢字です。", "0S0\x8c0oo\"[W0g0Y0\x02", "UTF-16BE"}, + {"Hello, world", "Hello, world", "ASCII"}, + {"Gdańsk", "Gda\xf1sk", "ISO-8859-2"}, + {"Ââ Čč Đđ Ŋŋ Õõ Šš Žž Åå Ää", "\xc2\xe2 \xc8\xe8 \xa9\xb9 \xaf\xbf \xd5\xf5 \xaa\xba \xac\xbc \xc5\xe5 \xc4\xe4", "ISO-8859-10"}, + {"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "ISO-8859-11"}, + {"latviešu", "latvie\xf0u", "ISO-8859-13"}, + {"Seònaid", "Se\xf2naid", "ISO-8859-14"}, + {"€1 is cheap", "\xa41 is cheap", "ISO-8859-15"}, + {"românește", "rom\xe2ne\xbate", "ISO-8859-16"}, + {"nutraĵo", "nutra\xbco", "ISO-8859-3"}, + {"Kalâdlit", "Kal\xe2dlit", "ISO-8859-4"}, + {"русский", "\xe0\xe3\xe1\xe1\xda\xd8\xd9", "ISO-8859-5"}, + {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "ISO-8859-7"}, + {"Kağan", "Ka\xf0an", "ISO-8859-9"}, + {"Résumé", "R\x8esum\x8e", "macintosh"}, + {"Gdańsk", "Gda\xf1sk", "windows-1250"}, + {"русский", "\xf0\xf3\xf1\xf1\xea\xe8\xe9", "windows-1251"}, + {"Résumé", "R\xe9sum\xe9", "windows-1252"}, + {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "windows-1253"}, + {"Kağan", "Ka\xf0an", "windows-1254"}, + {"עִבְרִית", "\xf2\xc4\xe1\xc0\xf8\xc4\xe9\xfa", "windows-1255"}, + {"العربية", "\xc7\xe1\xda\xd1\xc8\xed\xc9", "windows-1256"}, + {"latviešu", "latvie\xf0u", "windows-1257"}, + {"Việt", "Vi\xea\xf2t", "windows-1258"}, + {"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "windows-874"}, + {"русский", "\xd2\xd5\xd3\xd3\xcb\xc9\xca", "KOI8-R"}, + {"українська", "\xd5\xcb\xd2\xc1\xa7\xce\xd3\xd8\xcb\xc1", "KOI8-U"}, + {"Hello 常用國字標準字體表", "Hello \xb1`\xa5\u03b0\xea\xa6r\xbc\u0437\u01e6r\xc5\xe9\xaa\xed", "big5"}, + {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gbk"}, + {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gb18030"}, + {"עִבְרִית", "\x81\x30\xfb\x30\x81\x30\xf6\x34\x81\x30\xf9\x33\x81\x30\xf6\x30\x81\x30\xfb\x36\x81\x30\xf6\x34\x81\x30\xfa\x31\x81\x30\xfb\x38", "gb18030"}, + {"㧯", "\x82\x31\x89\x38", "gb18030"}, + {"これは漢字です。", "\x82\xb1\x82\xea\x82\xcd\x8a\xbf\x8e\x9a\x82\xc5\x82\xb7\x81B", "SJIS"}, + {"Hello, 世界!", "Hello, \x90\xa2\x8aE!", "SJIS"}, + {"イウエオカ", "\xb2\xb3\xb4\xb5\xb6", "SJIS"}, + {"これは漢字です。", "\xa4\xb3\xa4\xec\xa4\u03f4\xc1\xbb\xfa\xa4\u01e4\xb9\xa1\xa3", "EUC-JP"}, + {"Hello, 世界!", "Hello, \x1b$B@$3&\x1b(B!", "ISO-2022-JP"}, + {"네이트 | 즐거움의 시작, 슈파스(Spaβ) NATE", "\xb3\xd7\xc0\xcc\xc6\xae | \xc1\xf1\xb0\xc5\xbf\xf2\xc0\xc7 \xbd\xc3\xc0\xdb, \xbd\xb4\xc6\xc4\xbd\xba(Spa\xa5\xe2) NATE", "EUC-KR"}, +} + +func TestDecode(t *testing.T) { + for _, tc := range testCases { + e, _ := Lookup(tc.otherEncoding) + if e == nil { + t.Errorf("%s: not found", tc.otherEncoding) + continue + } + s, err := transformString(e.NewDecoder(), tc.other) + if err != nil { + t.Errorf("%s: decode %q: %v", tc.otherEncoding, tc.other, err) + continue + } + if s != tc.utf8 { + t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.utf8) + } + } +} + +func TestEncode(t *testing.T) { + for _, tc := range testCases { + e, _ := Lookup(tc.otherEncoding) + if e == nil { + t.Errorf("%s: not found", tc.otherEncoding) + continue + } + s, err := transformString(e.NewEncoder(), tc.utf8) + if err != nil { + t.Errorf("%s: encode %q: %s", tc.otherEncoding, tc.utf8, err) + continue + } + if s != tc.other { + t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.other) + } + } +} + +// TestNames verifies that you can pass an encoding's name to Lookup and get +// the same encoding back (except for "replacement"). +func TestNames(t *testing.T) { + for _, e := range encodings { + if e.name == "replacement" { + continue + } + _, got := Lookup(e.name) + if got != e.name { + t.Errorf("got %q, want %q", got, e.name) + continue + } + } +} + +var sniffTestCases = []struct { + filename, declared, want string +}{ + {"HTTP-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"}, + {"UTF-16LE-BOM.html", "", "utf-16le"}, + {"UTF-16BE-BOM.html", "", "utf-16be"}, + {"meta-content-attribute.html", "text/html", "iso-8859-15"}, + {"meta-charset-attribute.html", "text/html", "iso-8859-15"}, + {"No-encoding-declaration.html", "text/html", "utf-8"}, + {"HTTP-vs-UTF-8-BOM.html", "text/html; charset=iso-8859-15", "utf-8"}, + {"HTTP-vs-meta-content.html", "text/html; charset=iso-8859-15", "iso-8859-15"}, + {"HTTP-vs-meta-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"}, + {"UTF-8-BOM-vs-meta-content.html", "text/html", "utf-8"}, + {"UTF-8-BOM-vs-meta-charset.html", "text/html", "utf-8"}, +} + +func TestSniff(t *testing.T) { + switch runtime.GOOS { + case "nacl": // platforms that don't permit direct file system access + t.Skipf("not supported on %q", runtime.GOOS) + } + + for _, tc := range sniffTestCases { + content, err := ioutil.ReadFile("testdata/" + tc.filename) + if err != nil { + t.Errorf("%s: error reading file: %v", tc.filename, err) + continue + } + + _, name, _ := DetermineEncoding(content, tc.declared) + if name != tc.want { + t.Errorf("%s: got %q, want %q", tc.filename, name, tc.want) + continue + } + } +} + +func TestReader(t *testing.T) { + switch runtime.GOOS { + case "nacl": // platforms that don't permit direct file system access + t.Skipf("not supported on %q", runtime.GOOS) + } + + for _, tc := range sniffTestCases { + content, err := ioutil.ReadFile("testdata/" + tc.filename) + if err != nil { + t.Errorf("%s: error reading file: %v", tc.filename, err) + continue + } + + r, err := NewReader(bytes.NewReader(content), tc.declared) + if err != nil { + t.Errorf("%s: error creating reader: %v", tc.filename, err) + continue + } + + got, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("%s: error reading from charset.NewReader: %v", tc.filename, err) + continue + } + + e, _ := Lookup(tc.want) + want, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(content), e.NewDecoder())) + if err != nil { + t.Errorf("%s: error decoding with hard-coded charset name: %v", tc.filename, err) + continue + } + + if !bytes.Equal(got, want) { + t.Errorf("%s: got %q, want %q", tc.filename, got, want) + continue + } + } +} + +var metaTestCases = []struct { + meta, want string +}{ + {"", ""}, + {"text/html", ""}, + {"text/html; charset utf-8", ""}, + {"text/html; charset=latin-2", "latin-2"}, + {"text/html; charset; charset = utf-8", "utf-8"}, + {`charset="big5"`, "big5"}, + {"charset='shift_jis'", "shift_jis"}, +} + +func TestFromMeta(t *testing.T) { + for _, tc := range metaTestCases { + got := fromMetaElement(tc.meta) + if got != tc.want { + t.Errorf("%q: got %q, want %q", tc.meta, got, tc.want) + } + } +} + +func TestXML(t *testing.T) { + const s = "r\xe9sum\xe9" + + d := xml.NewDecoder(strings.NewReader(s)) + d.CharsetReader = NewReaderLabel + + var a struct { + Word string + } + err := d.Decode(&a) + if err != nil { + t.Fatalf("Decode: %v", err) + } + + want := "résumé" + if a.Word != want { + t.Errorf("got %q, want %q", a.Word, want) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/gen.go b/Godeps/_workspace/src/golang.org/x/net/html/charset/gen.go new file mode 100644 index 0000000..828347f --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/gen.go @@ -0,0 +1,111 @@ +// 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. + +// +build ignore + +package main + +// Download https://encoding.spec.whatwg.org/encodings.json and use it to +// generate table.go. + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strings" +) + +type enc struct { + Name string + Labels []string +} + +type group struct { + Encodings []enc + Heading string +} + +const specURL = "https://encoding.spec.whatwg.org/encodings.json" + +func main() { + resp, err := http.Get(specURL) + if err != nil { + log.Fatalf("error fetching %s: %s", specURL, err) + } + if resp.StatusCode != 200 { + log.Fatalf("error fetching %s: HTTP status %s", specURL, resp.Status) + } + defer resp.Body.Close() + + var groups []group + d := json.NewDecoder(resp.Body) + err = d.Decode(&groups) + if err != nil { + log.Fatalf("error reading encodings.json: %s", err) + } + + fmt.Println("// generated by go run gen.go; DO NOT EDIT") + fmt.Println() + fmt.Println("package charset") + fmt.Println() + + fmt.Println("import (") + fmt.Println(`"golang.org/x/text/encoding"`) + for _, pkg := range []string{"charmap", "japanese", "korean", "simplifiedchinese", "traditionalchinese", "unicode"} { + fmt.Printf("\"golang.org/x/text/encoding/%s\"\n", pkg) + } + fmt.Println(")") + fmt.Println() + + fmt.Println("var encodings = map[string]struct{e encoding.Encoding; name string} {") + for _, g := range groups { + for _, e := range g.Encodings { + goName, ok := miscNames[e.Name] + if !ok { + for k, v := range prefixes { + if strings.HasPrefix(e.Name, k) { + goName = v + e.Name[len(k):] + break + } + } + if goName == "" { + log.Fatalf("unrecognized encoding name: %s", e.Name) + } + } + + for _, label := range e.Labels { + fmt.Printf("%q: {%s, %q},\n", label, goName, e.Name) + } + } + } + fmt.Println("}") +} + +var prefixes = map[string]string{ + "iso-8859-": "charmap.ISO8859_", + "windows-": "charmap.Windows", +} + +var miscNames = map[string]string{ + "utf-8": "encoding.Nop", + "ibm866": "charmap.CodePage866", + "iso-8859-8-i": "charmap.ISO8859_8", + "koi8-r": "charmap.KOI8R", + "koi8-u": "charmap.KOI8U", + "macintosh": "charmap.Macintosh", + "x-mac-cyrillic": "charmap.MacintoshCyrillic", + "gbk": "simplifiedchinese.GBK", + "gb18030": "simplifiedchinese.GB18030", + "hz-gb-2312": "simplifiedchinese.HZGB2312", + "big5": "traditionalchinese.Big5", + "euc-jp": "japanese.EUCJP", + "iso-2022-jp": "japanese.ISO2022JP", + "shift_jis": "japanese.ShiftJIS", + "euc-kr": "korean.EUCKR", + "replacement": "encoding.Replacement", + "utf-16be": "unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM)", + "utf-16le": "unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)", + "x-user-defined": "charmap.XUserDefined", +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/table.go b/Godeps/_workspace/src/golang.org/x/net/html/charset/table.go new file mode 100644 index 0000000..aa0d948 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/table.go @@ -0,0 +1,235 @@ +// generated by go run gen.go; DO NOT EDIT + +package charset + +import ( + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/encoding/japanese" + "golang.org/x/text/encoding/korean" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/encoding/traditionalchinese" + "golang.org/x/text/encoding/unicode" +) + +var encodings = map[string]struct { + e encoding.Encoding + name string +}{ + "unicode-1-1-utf-8": {encoding.Nop, "utf-8"}, + "utf-8": {encoding.Nop, "utf-8"}, + "utf8": {encoding.Nop, "utf-8"}, + "866": {charmap.CodePage866, "ibm866"}, + "cp866": {charmap.CodePage866, "ibm866"}, + "csibm866": {charmap.CodePage866, "ibm866"}, + "ibm866": {charmap.CodePage866, "ibm866"}, + "csisolatin2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso-8859-2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso-ir-101": {charmap.ISO8859_2, "iso-8859-2"}, + "iso8859-2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso88592": {charmap.ISO8859_2, "iso-8859-2"}, + "iso_8859-2": {charmap.ISO8859_2, "iso-8859-2"}, + "iso_8859-2:1987": {charmap.ISO8859_2, "iso-8859-2"}, + "l2": {charmap.ISO8859_2, "iso-8859-2"}, + "latin2": {charmap.ISO8859_2, "iso-8859-2"}, + "csisolatin3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso-8859-3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso-ir-109": {charmap.ISO8859_3, "iso-8859-3"}, + "iso8859-3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso88593": {charmap.ISO8859_3, "iso-8859-3"}, + "iso_8859-3": {charmap.ISO8859_3, "iso-8859-3"}, + "iso_8859-3:1988": {charmap.ISO8859_3, "iso-8859-3"}, + "l3": {charmap.ISO8859_3, "iso-8859-3"}, + "latin3": {charmap.ISO8859_3, "iso-8859-3"}, + "csisolatin4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso-8859-4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso-ir-110": {charmap.ISO8859_4, "iso-8859-4"}, + "iso8859-4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso88594": {charmap.ISO8859_4, "iso-8859-4"}, + "iso_8859-4": {charmap.ISO8859_4, "iso-8859-4"}, + "iso_8859-4:1988": {charmap.ISO8859_4, "iso-8859-4"}, + "l4": {charmap.ISO8859_4, "iso-8859-4"}, + "latin4": {charmap.ISO8859_4, "iso-8859-4"}, + "csisolatincyrillic": {charmap.ISO8859_5, "iso-8859-5"}, + "cyrillic": {charmap.ISO8859_5, "iso-8859-5"}, + "iso-8859-5": {charmap.ISO8859_5, "iso-8859-5"}, + "iso-ir-144": {charmap.ISO8859_5, "iso-8859-5"}, + "iso8859-5": {charmap.ISO8859_5, "iso-8859-5"}, + "iso88595": {charmap.ISO8859_5, "iso-8859-5"}, + "iso_8859-5": {charmap.ISO8859_5, "iso-8859-5"}, + "iso_8859-5:1988": {charmap.ISO8859_5, "iso-8859-5"}, + "arabic": {charmap.ISO8859_6, "iso-8859-6"}, + "asmo-708": {charmap.ISO8859_6, "iso-8859-6"}, + "csiso88596e": {charmap.ISO8859_6, "iso-8859-6"}, + "csiso88596i": {charmap.ISO8859_6, "iso-8859-6"}, + "csisolatinarabic": {charmap.ISO8859_6, "iso-8859-6"}, + "ecma-114": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-8859-6": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-8859-6-e": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-8859-6-i": {charmap.ISO8859_6, "iso-8859-6"}, + "iso-ir-127": {charmap.ISO8859_6, "iso-8859-6"}, + "iso8859-6": {charmap.ISO8859_6, "iso-8859-6"}, + "iso88596": {charmap.ISO8859_6, "iso-8859-6"}, + "iso_8859-6": {charmap.ISO8859_6, "iso-8859-6"}, + "iso_8859-6:1987": {charmap.ISO8859_6, "iso-8859-6"}, + "csisolatingreek": {charmap.ISO8859_7, "iso-8859-7"}, + "ecma-118": {charmap.ISO8859_7, "iso-8859-7"}, + "elot_928": {charmap.ISO8859_7, "iso-8859-7"}, + "greek": {charmap.ISO8859_7, "iso-8859-7"}, + "greek8": {charmap.ISO8859_7, "iso-8859-7"}, + "iso-8859-7": {charmap.ISO8859_7, "iso-8859-7"}, + "iso-ir-126": {charmap.ISO8859_7, "iso-8859-7"}, + "iso8859-7": {charmap.ISO8859_7, "iso-8859-7"}, + "iso88597": {charmap.ISO8859_7, "iso-8859-7"}, + "iso_8859-7": {charmap.ISO8859_7, "iso-8859-7"}, + "iso_8859-7:1987": {charmap.ISO8859_7, "iso-8859-7"}, + "sun_eu_greek": {charmap.ISO8859_7, "iso-8859-7"}, + "csiso88598e": {charmap.ISO8859_8, "iso-8859-8"}, + "csisolatinhebrew": {charmap.ISO8859_8, "iso-8859-8"}, + "hebrew": {charmap.ISO8859_8, "iso-8859-8"}, + "iso-8859-8": {charmap.ISO8859_8, "iso-8859-8"}, + "iso-8859-8-e": {charmap.ISO8859_8, "iso-8859-8"}, + "iso-ir-138": {charmap.ISO8859_8, "iso-8859-8"}, + "iso8859-8": {charmap.ISO8859_8, "iso-8859-8"}, + "iso88598": {charmap.ISO8859_8, "iso-8859-8"}, + "iso_8859-8": {charmap.ISO8859_8, "iso-8859-8"}, + "iso_8859-8:1988": {charmap.ISO8859_8, "iso-8859-8"}, + "visual": {charmap.ISO8859_8, "iso-8859-8"}, + "csiso88598i": {charmap.ISO8859_8, "iso-8859-8-i"}, + "iso-8859-8-i": {charmap.ISO8859_8, "iso-8859-8-i"}, + "logical": {charmap.ISO8859_8, "iso-8859-8-i"}, + "csisolatin6": {charmap.ISO8859_10, "iso-8859-10"}, + "iso-8859-10": {charmap.ISO8859_10, "iso-8859-10"}, + "iso-ir-157": {charmap.ISO8859_10, "iso-8859-10"}, + "iso8859-10": {charmap.ISO8859_10, "iso-8859-10"}, + "iso885910": {charmap.ISO8859_10, "iso-8859-10"}, + "l6": {charmap.ISO8859_10, "iso-8859-10"}, + "latin6": {charmap.ISO8859_10, "iso-8859-10"}, + "iso-8859-13": {charmap.ISO8859_13, "iso-8859-13"}, + "iso8859-13": {charmap.ISO8859_13, "iso-8859-13"}, + "iso885913": {charmap.ISO8859_13, "iso-8859-13"}, + "iso-8859-14": {charmap.ISO8859_14, "iso-8859-14"}, + "iso8859-14": {charmap.ISO8859_14, "iso-8859-14"}, + "iso885914": {charmap.ISO8859_14, "iso-8859-14"}, + "csisolatin9": {charmap.ISO8859_15, "iso-8859-15"}, + "iso-8859-15": {charmap.ISO8859_15, "iso-8859-15"}, + "iso8859-15": {charmap.ISO8859_15, "iso-8859-15"}, + "iso885915": {charmap.ISO8859_15, "iso-8859-15"}, + "iso_8859-15": {charmap.ISO8859_15, "iso-8859-15"}, + "l9": {charmap.ISO8859_15, "iso-8859-15"}, + "iso-8859-16": {charmap.ISO8859_16, "iso-8859-16"}, + "cskoi8r": {charmap.KOI8R, "koi8-r"}, + "koi": {charmap.KOI8R, "koi8-r"}, + "koi8": {charmap.KOI8R, "koi8-r"}, + "koi8-r": {charmap.KOI8R, "koi8-r"}, + "koi8_r": {charmap.KOI8R, "koi8-r"}, + "koi8-u": {charmap.KOI8U, "koi8-u"}, + "csmacintosh": {charmap.Macintosh, "macintosh"}, + "mac": {charmap.Macintosh, "macintosh"}, + "macintosh": {charmap.Macintosh, "macintosh"}, + "x-mac-roman": {charmap.Macintosh, "macintosh"}, + "dos-874": {charmap.Windows874, "windows-874"}, + "iso-8859-11": {charmap.Windows874, "windows-874"}, + "iso8859-11": {charmap.Windows874, "windows-874"}, + "iso885911": {charmap.Windows874, "windows-874"}, + "tis-620": {charmap.Windows874, "windows-874"}, + "windows-874": {charmap.Windows874, "windows-874"}, + "cp1250": {charmap.Windows1250, "windows-1250"}, + "windows-1250": {charmap.Windows1250, "windows-1250"}, + "x-cp1250": {charmap.Windows1250, "windows-1250"}, + "cp1251": {charmap.Windows1251, "windows-1251"}, + "windows-1251": {charmap.Windows1251, "windows-1251"}, + "x-cp1251": {charmap.Windows1251, "windows-1251"}, + "ansi_x3.4-1968": {charmap.Windows1252, "windows-1252"}, + "ascii": {charmap.Windows1252, "windows-1252"}, + "cp1252": {charmap.Windows1252, "windows-1252"}, + "cp819": {charmap.Windows1252, "windows-1252"}, + "csisolatin1": {charmap.Windows1252, "windows-1252"}, + "ibm819": {charmap.Windows1252, "windows-1252"}, + "iso-8859-1": {charmap.Windows1252, "windows-1252"}, + "iso-ir-100": {charmap.Windows1252, "windows-1252"}, + "iso8859-1": {charmap.Windows1252, "windows-1252"}, + "iso88591": {charmap.Windows1252, "windows-1252"}, + "iso_8859-1": {charmap.Windows1252, "windows-1252"}, + "iso_8859-1:1987": {charmap.Windows1252, "windows-1252"}, + "l1": {charmap.Windows1252, "windows-1252"}, + "latin1": {charmap.Windows1252, "windows-1252"}, + "us-ascii": {charmap.Windows1252, "windows-1252"}, + "windows-1252": {charmap.Windows1252, "windows-1252"}, + "x-cp1252": {charmap.Windows1252, "windows-1252"}, + "cp1253": {charmap.Windows1253, "windows-1253"}, + "windows-1253": {charmap.Windows1253, "windows-1253"}, + "x-cp1253": {charmap.Windows1253, "windows-1253"}, + "cp1254": {charmap.Windows1254, "windows-1254"}, + "csisolatin5": {charmap.Windows1254, "windows-1254"}, + "iso-8859-9": {charmap.Windows1254, "windows-1254"}, + "iso-ir-148": {charmap.Windows1254, "windows-1254"}, + "iso8859-9": {charmap.Windows1254, "windows-1254"}, + "iso88599": {charmap.Windows1254, "windows-1254"}, + "iso_8859-9": {charmap.Windows1254, "windows-1254"}, + "iso_8859-9:1989": {charmap.Windows1254, "windows-1254"}, + "l5": {charmap.Windows1254, "windows-1254"}, + "latin5": {charmap.Windows1254, "windows-1254"}, + "windows-1254": {charmap.Windows1254, "windows-1254"}, + "x-cp1254": {charmap.Windows1254, "windows-1254"}, + "cp1255": {charmap.Windows1255, "windows-1255"}, + "windows-1255": {charmap.Windows1255, "windows-1255"}, + "x-cp1255": {charmap.Windows1255, "windows-1255"}, + "cp1256": {charmap.Windows1256, "windows-1256"}, + "windows-1256": {charmap.Windows1256, "windows-1256"}, + "x-cp1256": {charmap.Windows1256, "windows-1256"}, + "cp1257": {charmap.Windows1257, "windows-1257"}, + "windows-1257": {charmap.Windows1257, "windows-1257"}, + "x-cp1257": {charmap.Windows1257, "windows-1257"}, + "cp1258": {charmap.Windows1258, "windows-1258"}, + "windows-1258": {charmap.Windows1258, "windows-1258"}, + "x-cp1258": {charmap.Windows1258, "windows-1258"}, + "x-mac-cyrillic": {charmap.MacintoshCyrillic, "x-mac-cyrillic"}, + "x-mac-ukrainian": {charmap.MacintoshCyrillic, "x-mac-cyrillic"}, + "chinese": {simplifiedchinese.GBK, "gbk"}, + "csgb2312": {simplifiedchinese.GBK, "gbk"}, + "csiso58gb231280": {simplifiedchinese.GBK, "gbk"}, + "gb2312": {simplifiedchinese.GBK, "gbk"}, + "gb_2312": {simplifiedchinese.GBK, "gbk"}, + "gb_2312-80": {simplifiedchinese.GBK, "gbk"}, + "gbk": {simplifiedchinese.GBK, "gbk"}, + "iso-ir-58": {simplifiedchinese.GBK, "gbk"}, + "x-gbk": {simplifiedchinese.GBK, "gbk"}, + "gb18030": {simplifiedchinese.GB18030, "gb18030"}, + "hz-gb-2312": {simplifiedchinese.HZGB2312, "hz-gb-2312"}, + "big5": {traditionalchinese.Big5, "big5"}, + "big5-hkscs": {traditionalchinese.Big5, "big5"}, + "cn-big5": {traditionalchinese.Big5, "big5"}, + "csbig5": {traditionalchinese.Big5, "big5"}, + "x-x-big5": {traditionalchinese.Big5, "big5"}, + "cseucpkdfmtjapanese": {japanese.EUCJP, "euc-jp"}, + "euc-jp": {japanese.EUCJP, "euc-jp"}, + "x-euc-jp": {japanese.EUCJP, "euc-jp"}, + "csiso2022jp": {japanese.ISO2022JP, "iso-2022-jp"}, + "iso-2022-jp": {japanese.ISO2022JP, "iso-2022-jp"}, + "csshiftjis": {japanese.ShiftJIS, "shift_jis"}, + "ms_kanji": {japanese.ShiftJIS, "shift_jis"}, + "shift-jis": {japanese.ShiftJIS, "shift_jis"}, + "shift_jis": {japanese.ShiftJIS, "shift_jis"}, + "sjis": {japanese.ShiftJIS, "shift_jis"}, + "windows-31j": {japanese.ShiftJIS, "shift_jis"}, + "x-sjis": {japanese.ShiftJIS, "shift_jis"}, + "cseuckr": {korean.EUCKR, "euc-kr"}, + "csksc56011987": {korean.EUCKR, "euc-kr"}, + "euc-kr": {korean.EUCKR, "euc-kr"}, + "iso-ir-149": {korean.EUCKR, "euc-kr"}, + "korean": {korean.EUCKR, "euc-kr"}, + "ks_c_5601-1987": {korean.EUCKR, "euc-kr"}, + "ks_c_5601-1989": {korean.EUCKR, "euc-kr"}, + "ksc5601": {korean.EUCKR, "euc-kr"}, + "ksc_5601": {korean.EUCKR, "euc-kr"}, + "windows-949": {korean.EUCKR, "euc-kr"}, + "csiso2022kr": {encoding.Replacement, "replacement"}, + "iso-2022-kr": {encoding.Replacement, "replacement"}, + "iso-2022-cn": {encoding.Replacement, "replacement"}, + "iso-2022-cn-ext": {encoding.Replacement, "replacement"}, + "utf-16be": {unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM), "utf-16be"}, + "utf-16": {unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), "utf-16le"}, + "utf-16le": {unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), "utf-16le"}, + "x-user-defined": {charmap.XUserDefined, "x-user-defined"}, +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-charset.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-charset.html new file mode 100644 index 0000000..9915fa0 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-charset.html @@ -0,0 +1,48 @@ + + + + HTTP charset + + + + + + + + + + + +

HTTP charset

+ + +
+ + +
 
+ + + + + +
+

The character encoding of a page can be set using the HTTP header charset declaration.

+

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

The only character encoding declaration for this HTML file is in the HTTP header, which sets the encoding to ISO 8859-15.

+
+
+
HTML5
+

the-input-byte-stream-001
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-UTF-8-BOM.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-UTF-8-BOM.html new file mode 100644 index 0000000..26e5d8b --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-UTF-8-BOM.html @@ -0,0 +1,48 @@ + + + + HTTP vs UTF-8 BOM + + + + + + + + + + + +

HTTP vs UTF-8 BOM

+ + +
+ + +
 
+ + + + + +
+

A character encoding set in the HTTP header has lower precedence than the UTF-8 signature.

+

The HTTP header attempts to set the character encoding to ISO 8859-15. The page starts with a UTF-8 signature.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

If the test is unsuccessful, the characters  should appear at the top of the page. These represent the bytes that make up the UTF-8 signature when encountered in the ISO 8859-15 encoding.

+
+
+
HTML5
+

the-input-byte-stream-034
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-charset.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-charset.html new file mode 100644 index 0000000..2f07e95 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-charset.html @@ -0,0 +1,49 @@ + + + + HTTP vs meta charset + + + + + + + + + + + +

HTTP vs meta charset

+ + +
+ + +
 
+ + + + + +
+

The HTTP header has a higher precedence than an encoding declaration in a meta charset attribute.

+

The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta charset attribute that attempts to set the character encoding to ISO 8859-1.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-018
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-content.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-content.html new file mode 100644 index 0000000..6853cdd --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/HTTP-vs-meta-content.html @@ -0,0 +1,49 @@ + + + + HTTP vs meta content + + + + + + + + + + + +

HTTP vs meta content

+ + +
+ + +
 
+ + + + + +
+

The HTTP header has a higher precedence than an encoding declaration in a meta content attribute.

+

The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta content attribute that attempts to set the character encoding to ISO 8859-1.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-016
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/No-encoding-declaration.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/No-encoding-declaration.html new file mode 100644 index 0000000..612e26c --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/No-encoding-declaration.html @@ -0,0 +1,47 @@ + + + + No encoding declaration + + + + + + + + + + + +

No encoding declaration

+ + +
+ + +
 
+ + + + + +
+

A page with no encoding information in HTTP, BOM, XML declaration or meta element will be treated as UTF-8.

+

The test on this page contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-015
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/README b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/README new file mode 100644 index 0000000..38ef0f9 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/README @@ -0,0 +1,9 @@ +These test cases come from +http://www.w3.org/International/tests/repository/html5/the-input-byte-stream/results-basics + +Distributed under both the W3C Test Suite License +(http://www.w3.org/Consortium/Legal/2008/04-testsuite-license) +and the W3C 3-clause BSD License +(http://www.w3.org/Consortium/Legal/2008/03-bsd-license). +To contribute to a W3C Test Suite, see the policies and contribution +forms (http://www.w3.org/2004/10/27-testcases). diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html new file mode 100644 index 0000000..3abf7a9 Binary files /dev/null and b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html differ diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html new file mode 100644 index 0000000..76254c9 Binary files /dev/null and b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html differ diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html new file mode 100644 index 0000000..83de433 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html @@ -0,0 +1,49 @@ + + + + UTF-8 BOM vs meta charset + + + + + + + + + + + +

UTF-8 BOM vs meta charset

+ + +
+ + +
 
+ + + + + +
+

A page with a UTF-8 BOM will be recognized as UTF-8 even if the meta charset attribute declares a different encoding.

+

The page contains an encoding declaration in a meta charset attribute that attempts to set the character encoding to ISO 8859-15, but the file starts with a UTF-8 signature.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-038
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-content.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-content.html new file mode 100644 index 0000000..501aac2 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-content.html @@ -0,0 +1,48 @@ + + + + UTF-8 BOM vs meta content + + + + + + + + + + + +

UTF-8 BOM vs meta content

+ + +
+ + +
 
+ + + + + +
+

A page with a UTF-8 BOM will be recognized as UTF-8 even if the meta content attribute declares a different encoding.

+

The page contains an encoding declaration in a meta content attribute that attempts to set the character encoding to ISO 8859-15, but the file starts with a UTF-8 signature.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-037
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/meta-charset-attribute.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/meta-charset-attribute.html new file mode 100644 index 0000000..2d7d25a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/meta-charset-attribute.html @@ -0,0 +1,48 @@ + + + + meta charset attribute + + + + + + + + + + + +

meta charset attribute

+ + +
+ + +
 
+ + + + + +
+

The character encoding of the page can be set by a meta element with charset attribute.

+

The only character encoding declaration for this HTML file is in the charset attribute of the meta element, which declares the encoding to be ISO 8859-15.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-009
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/meta-content-attribute.html b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/meta-content-attribute.html new file mode 100644 index 0000000..1c3f228 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/charset/testdata/meta-content-attribute.html @@ -0,0 +1,48 @@ + + + + meta content attribute + + + + + + + + + + + +

meta content attribute

+ + +
+ + +
 
+ + + + + +
+

The character encoding of the page can be set by a meta element with http-equiv and content attributes.

+

The only character encoding declaration for this HTML file is in the content attribute of the meta element, which declares the encoding to be ISO 8859-15.

The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.

+
+
+
HTML5
+

the-input-byte-stream-007
Result summary & related tests
Detailed results for this test
Link to spec

+
Assumptions:
  • The default encoding for the browser you are testing is not set to ISO 8859-15.
  • +
  • The test is read from a server that supports HTTP.
+
+ + + + + + diff --git a/Godeps/_workspace/src/golang.org/x/net/html/const.go b/Godeps/_workspace/src/golang.org/x/net/html/const.go new file mode 100644 index 0000000..52f651f --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/const.go @@ -0,0 +1,102 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +// Section 12.2.3.2 of the HTML5 specification says "The following elements +// have varying levels of special parsing rules". +// https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements +var isSpecialElementMap = map[string]bool{ + "address": true, + "applet": true, + "area": true, + "article": true, + "aside": true, + "base": true, + "basefont": true, + "bgsound": true, + "blockquote": true, + "body": true, + "br": true, + "button": true, + "caption": true, + "center": true, + "col": true, + "colgroup": true, + "dd": true, + "details": true, + "dir": true, + "div": true, + "dl": true, + "dt": true, + "embed": true, + "fieldset": true, + "figcaption": true, + "figure": true, + "footer": true, + "form": true, + "frame": true, + "frameset": true, + "h1": true, + "h2": true, + "h3": true, + "h4": true, + "h5": true, + "h6": true, + "head": true, + "header": true, + "hgroup": true, + "hr": true, + "html": true, + "iframe": true, + "img": true, + "input": true, + "isindex": true, + "li": true, + "link": true, + "listing": true, + "marquee": true, + "menu": true, + "meta": true, + "nav": true, + "noembed": true, + "noframes": true, + "noscript": true, + "object": true, + "ol": true, + "p": true, + "param": true, + "plaintext": true, + "pre": true, + "script": true, + "section": true, + "select": true, + "source": true, + "style": true, + "summary": true, + "table": true, + "tbody": true, + "td": true, + "template": true, + "textarea": true, + "tfoot": true, + "th": true, + "thead": true, + "title": true, + "tr": true, + "track": true, + "ul": true, + "wbr": true, + "xmp": true, +} + +func isSpecialElement(element *Node) bool { + switch element.Namespace { + case "", "html": + return isSpecialElementMap[element.Data] + case "svg": + return element.Data == "foreignObject" + } + return false +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/doc.go b/Godeps/_workspace/src/golang.org/x/net/html/doc.go new file mode 100644 index 0000000..b453fe1 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/doc.go @@ -0,0 +1,106 @@ +// Copyright 2010 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 html implements an HTML5-compliant tokenizer and parser. + +Tokenization is done by creating a Tokenizer for an io.Reader r. It is the +caller's responsibility to ensure that r provides UTF-8 encoded HTML. + + z := html.NewTokenizer(r) + +Given a Tokenizer z, the HTML is tokenized by repeatedly calling z.Next(), +which parses the next token and returns its type, or an error: + + for { + tt := z.Next() + if tt == html.ErrorToken { + // ... + return ... + } + // Process the current token. + } + +There are two APIs for retrieving the current token. The high-level API is to +call Token; the low-level API is to call Text or TagName / TagAttr. Both APIs +allow optionally calling Raw after Next but before Token, Text, TagName, or +TagAttr. In EBNF notation, the valid call sequence per token is: + + Next {Raw} [ Token | Text | TagName {TagAttr} ] + +Token returns an independent data structure that completely describes a token. +Entities (such as "<") are unescaped, tag names and attribute keys are +lower-cased, and attributes are collected into a []Attribute. For example: + + for { + if z.Next() == html.ErrorToken { + // Returning io.EOF indicates success. + return z.Err() + } + emitToken(z.Token()) + } + +The low-level API performs fewer allocations and copies, but the contents of +the []byte values returned by Text, TagName and TagAttr may change on the next +call to Next. For example, to extract an HTML page's anchor text: + + depth := 0 + for { + tt := z.Next() + switch tt { + case ErrorToken: + return z.Err() + case TextToken: + if depth > 0 { + // emitBytes should copy the []byte it receives, + // if it doesn't process it immediately. + emitBytes(z.Text()) + } + case StartTagToken, EndTagToken: + tn, _ := z.TagName() + if len(tn) == 1 && tn[0] == 'a' { + if tt == StartTagToken { + depth++ + } else { + depth-- + } + } + } + } + +Parsing is done by calling Parse with an io.Reader, which returns the root of +the parse tree (the document element) as a *Node. It is the caller's +responsibility to ensure that the Reader provides UTF-8 encoded HTML. For +example, to process each anchor node in depth-first order: + + doc, err := html.Parse(r) + if err != nil { + // ... + } + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + // Do something with n... + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + +The relevant specifications include: +https://html.spec.whatwg.org/multipage/syntax.html and +https://html.spec.whatwg.org/multipage/syntax.html#tokenization +*/ +package html + +// The tokenization algorithm implemented by this package is not a line-by-line +// transliteration of the relatively verbose state-machine in the WHATWG +// specification. A more direct approach is used instead, where the program +// counter implies the state, such as whether it is tokenizing a tag or a text +// node. Specification compliance is verified by checking expected and actual +// outputs over a test suite rather than aiming for algorithmic fidelity. + +// TODO(nigeltao): Does a DOM API belong in this package or a separate one? +// TODO(nigeltao): How does parsing interact with a JavaScript engine? diff --git a/Godeps/_workspace/src/golang.org/x/net/html/doctype.go b/Godeps/_workspace/src/golang.org/x/net/html/doctype.go new file mode 100644 index 0000000..c484e5a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/doctype.go @@ -0,0 +1,156 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "strings" +) + +// parseDoctype parses the data from a DoctypeToken into a name, +// public identifier, and system identifier. It returns a Node whose Type +// is DoctypeNode, whose Data is the name, and which has attributes +// named "system" and "public" for the two identifiers if they were present. +// quirks is whether the document should be parsed in "quirks mode". +func parseDoctype(s string) (n *Node, quirks bool) { + n = &Node{Type: DoctypeNode} + + // Find the name. + space := strings.IndexAny(s, whitespace) + if space == -1 { + space = len(s) + } + n.Data = s[:space] + // The comparison to "html" is case-sensitive. + if n.Data != "html" { + quirks = true + } + n.Data = strings.ToLower(n.Data) + s = strings.TrimLeft(s[space:], whitespace) + + if len(s) < 6 { + // It can't start with "PUBLIC" or "SYSTEM". + // Ignore the rest of the string. + return n, quirks || s != "" + } + + key := strings.ToLower(s[:6]) + s = s[6:] + for key == "public" || key == "system" { + s = strings.TrimLeft(s, whitespace) + if s == "" { + break + } + quote := s[0] + if quote != '"' && quote != '\'' { + break + } + s = s[1:] + q := strings.IndexRune(s, rune(quote)) + var id string + if q == -1 { + id = s + s = "" + } else { + id = s[:q] + s = s[q+1:] + } + n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) + if key == "public" { + key = "system" + } else { + key = "" + } + } + + if key != "" || s != "" { + quirks = true + } else if len(n.Attr) > 0 { + if n.Attr[0].Key == "public" { + public := strings.ToLower(n.Attr[0].Val) + switch public { + case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html": + quirks = true + default: + for _, q := range quirkyIDs { + if strings.HasPrefix(public, q) { + quirks = true + break + } + } + } + // The following two public IDs only cause quirks mode if there is no system ID. + if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") || + strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) { + quirks = true + } + } + if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && + strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { + quirks = true + } + } + + return n, quirks +} + +// quirkyIDs is a list of public doctype identifiers that cause a document +// to be interpreted in quirks mode. The identifiers should be in lower case. +var quirkyIDs = []string{ + "+//silmaril//dtd html pro v0r11 19970101//", + "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", + "-//as//dtd html 3.0 aswedit + extensions//", + "-//ietf//dtd html 2.0 level 1//", + "-//ietf//dtd html 2.0 level 2//", + "-//ietf//dtd html 2.0 strict level 1//", + "-//ietf//dtd html 2.0 strict level 2//", + "-//ietf//dtd html 2.0 strict//", + "-//ietf//dtd html 2.0//", + "-//ietf//dtd html 2.1e//", + "-//ietf//dtd html 3.0//", + "-//ietf//dtd html 3.2 final//", + "-//ietf//dtd html 3.2//", + "-//ietf//dtd html 3//", + "-//ietf//dtd html level 0//", + "-//ietf//dtd html level 1//", + "-//ietf//dtd html level 2//", + "-//ietf//dtd html level 3//", + "-//ietf//dtd html strict level 0//", + "-//ietf//dtd html strict level 1//", + "-//ietf//dtd html strict level 2//", + "-//ietf//dtd html strict level 3//", + "-//ietf//dtd html strict//", + "-//ietf//dtd html//", + "-//metrius//dtd metrius presentational//", + "-//microsoft//dtd internet explorer 2.0 html strict//", + "-//microsoft//dtd internet explorer 2.0 html//", + "-//microsoft//dtd internet explorer 2.0 tables//", + "-//microsoft//dtd internet explorer 3.0 html strict//", + "-//microsoft//dtd internet explorer 3.0 html//", + "-//microsoft//dtd internet explorer 3.0 tables//", + "-//netscape comm. corp.//dtd html//", + "-//netscape comm. corp.//dtd strict html//", + "-//o'reilly and associates//dtd html 2.0//", + "-//o'reilly and associates//dtd html extended 1.0//", + "-//o'reilly and associates//dtd html extended relaxed 1.0//", + "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", + "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", + "-//spyglass//dtd html 2.0 extended//", + "-//sq//dtd html 2.0 hotmetal + extensions//", + "-//sun microsystems corp.//dtd hotjava html//", + "-//sun microsystems corp.//dtd hotjava strict html//", + "-//w3c//dtd html 3 1995-03-24//", + "-//w3c//dtd html 3.2 draft//", + "-//w3c//dtd html 3.2 final//", + "-//w3c//dtd html 3.2//", + "-//w3c//dtd html 3.2s draft//", + "-//w3c//dtd html 4.0 frameset//", + "-//w3c//dtd html 4.0 transitional//", + "-//w3c//dtd html experimental 19960712//", + "-//w3c//dtd html experimental 970421//", + "-//w3c//dtd w3 html//", + "-//w3o//dtd w3 html 3.0//", + "-//webtechs//dtd mozilla html 2.0//", + "-//webtechs//dtd mozilla html//", +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/entity.go b/Godeps/_workspace/src/golang.org/x/net/html/entity.go new file mode 100644 index 0000000..a50c04c --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/entity.go @@ -0,0 +1,2253 @@ +// Copyright 2010 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 html + +// All entities that do not end with ';' are 6 or fewer bytes long. +const longestEntityWithoutSemicolon = 6 + +// entity is a map from HTML entity names to their values. The semicolon matters: +// https://html.spec.whatwg.org/multipage/syntax.html#named-character-references +// lists both "amp" and "amp;" as two separate entries. +// +// Note that the HTML5 list is larger than the HTML4 list at +// http://www.w3.org/TR/html4/sgml/entities.html +var entity = map[string]rune{ + "AElig;": '\U000000C6', + "AMP;": '\U00000026', + "Aacute;": '\U000000C1', + "Abreve;": '\U00000102', + "Acirc;": '\U000000C2', + "Acy;": '\U00000410', + "Afr;": '\U0001D504', + "Agrave;": '\U000000C0', + "Alpha;": '\U00000391', + "Amacr;": '\U00000100', + "And;": '\U00002A53', + "Aogon;": '\U00000104', + "Aopf;": '\U0001D538', + "ApplyFunction;": '\U00002061', + "Aring;": '\U000000C5', + "Ascr;": '\U0001D49C', + "Assign;": '\U00002254', + "Atilde;": '\U000000C3', + "Auml;": '\U000000C4', + "Backslash;": '\U00002216', + "Barv;": '\U00002AE7', + "Barwed;": '\U00002306', + "Bcy;": '\U00000411', + "Because;": '\U00002235', + "Bernoullis;": '\U0000212C', + "Beta;": '\U00000392', + "Bfr;": '\U0001D505', + "Bopf;": '\U0001D539', + "Breve;": '\U000002D8', + "Bscr;": '\U0000212C', + "Bumpeq;": '\U0000224E', + "CHcy;": '\U00000427', + "COPY;": '\U000000A9', + "Cacute;": '\U00000106', + "Cap;": '\U000022D2', + "CapitalDifferentialD;": '\U00002145', + "Cayleys;": '\U0000212D', + "Ccaron;": '\U0000010C', + "Ccedil;": '\U000000C7', + "Ccirc;": '\U00000108', + "Cconint;": '\U00002230', + "Cdot;": '\U0000010A', + "Cedilla;": '\U000000B8', + "CenterDot;": '\U000000B7', + "Cfr;": '\U0000212D', + "Chi;": '\U000003A7', + "CircleDot;": '\U00002299', + "CircleMinus;": '\U00002296', + "CirclePlus;": '\U00002295', + "CircleTimes;": '\U00002297', + "ClockwiseContourIntegral;": '\U00002232', + "CloseCurlyDoubleQuote;": '\U0000201D', + "CloseCurlyQuote;": '\U00002019', + "Colon;": '\U00002237', + "Colone;": '\U00002A74', + "Congruent;": '\U00002261', + "Conint;": '\U0000222F', + "ContourIntegral;": '\U0000222E', + "Copf;": '\U00002102', + "Coproduct;": '\U00002210', + "CounterClockwiseContourIntegral;": '\U00002233', + "Cross;": '\U00002A2F', + "Cscr;": '\U0001D49E', + "Cup;": '\U000022D3', + "CupCap;": '\U0000224D', + "DD;": '\U00002145', + "DDotrahd;": '\U00002911', + "DJcy;": '\U00000402', + "DScy;": '\U00000405', + "DZcy;": '\U0000040F', + "Dagger;": '\U00002021', + "Darr;": '\U000021A1', + "Dashv;": '\U00002AE4', + "Dcaron;": '\U0000010E', + "Dcy;": '\U00000414', + "Del;": '\U00002207', + "Delta;": '\U00000394', + "Dfr;": '\U0001D507', + "DiacriticalAcute;": '\U000000B4', + "DiacriticalDot;": '\U000002D9', + "DiacriticalDoubleAcute;": '\U000002DD', + "DiacriticalGrave;": '\U00000060', + "DiacriticalTilde;": '\U000002DC', + "Diamond;": '\U000022C4', + "DifferentialD;": '\U00002146', + "Dopf;": '\U0001D53B', + "Dot;": '\U000000A8', + "DotDot;": '\U000020DC', + "DotEqual;": '\U00002250', + "DoubleContourIntegral;": '\U0000222F', + "DoubleDot;": '\U000000A8', + "DoubleDownArrow;": '\U000021D3', + "DoubleLeftArrow;": '\U000021D0', + "DoubleLeftRightArrow;": '\U000021D4', + "DoubleLeftTee;": '\U00002AE4', + "DoubleLongLeftArrow;": '\U000027F8', + "DoubleLongLeftRightArrow;": '\U000027FA', + "DoubleLongRightArrow;": '\U000027F9', + "DoubleRightArrow;": '\U000021D2', + "DoubleRightTee;": '\U000022A8', + "DoubleUpArrow;": '\U000021D1', + "DoubleUpDownArrow;": '\U000021D5', + "DoubleVerticalBar;": '\U00002225', + "DownArrow;": '\U00002193', + "DownArrowBar;": '\U00002913', + "DownArrowUpArrow;": '\U000021F5', + "DownBreve;": '\U00000311', + "DownLeftRightVector;": '\U00002950', + "DownLeftTeeVector;": '\U0000295E', + "DownLeftVector;": '\U000021BD', + "DownLeftVectorBar;": '\U00002956', + "DownRightTeeVector;": '\U0000295F', + "DownRightVector;": '\U000021C1', + "DownRightVectorBar;": '\U00002957', + "DownTee;": '\U000022A4', + "DownTeeArrow;": '\U000021A7', + "Downarrow;": '\U000021D3', + "Dscr;": '\U0001D49F', + "Dstrok;": '\U00000110', + "ENG;": '\U0000014A', + "ETH;": '\U000000D0', + "Eacute;": '\U000000C9', + "Ecaron;": '\U0000011A', + "Ecirc;": '\U000000CA', + "Ecy;": '\U0000042D', + "Edot;": '\U00000116', + "Efr;": '\U0001D508', + "Egrave;": '\U000000C8', + "Element;": '\U00002208', + "Emacr;": '\U00000112', + "EmptySmallSquare;": '\U000025FB', + "EmptyVerySmallSquare;": '\U000025AB', + "Eogon;": '\U00000118', + "Eopf;": '\U0001D53C', + "Epsilon;": '\U00000395', + "Equal;": '\U00002A75', + "EqualTilde;": '\U00002242', + "Equilibrium;": '\U000021CC', + "Escr;": '\U00002130', + "Esim;": '\U00002A73', + "Eta;": '\U00000397', + "Euml;": '\U000000CB', + "Exists;": '\U00002203', + "ExponentialE;": '\U00002147', + "Fcy;": '\U00000424', + "Ffr;": '\U0001D509', + "FilledSmallSquare;": '\U000025FC', + "FilledVerySmallSquare;": '\U000025AA', + "Fopf;": '\U0001D53D', + "ForAll;": '\U00002200', + "Fouriertrf;": '\U00002131', + "Fscr;": '\U00002131', + "GJcy;": '\U00000403', + "GT;": '\U0000003E', + "Gamma;": '\U00000393', + "Gammad;": '\U000003DC', + "Gbreve;": '\U0000011E', + "Gcedil;": '\U00000122', + "Gcirc;": '\U0000011C', + "Gcy;": '\U00000413', + "Gdot;": '\U00000120', + "Gfr;": '\U0001D50A', + "Gg;": '\U000022D9', + "Gopf;": '\U0001D53E', + "GreaterEqual;": '\U00002265', + "GreaterEqualLess;": '\U000022DB', + "GreaterFullEqual;": '\U00002267', + "GreaterGreater;": '\U00002AA2', + "GreaterLess;": '\U00002277', + "GreaterSlantEqual;": '\U00002A7E', + "GreaterTilde;": '\U00002273', + "Gscr;": '\U0001D4A2', + "Gt;": '\U0000226B', + "HARDcy;": '\U0000042A', + "Hacek;": '\U000002C7', + "Hat;": '\U0000005E', + "Hcirc;": '\U00000124', + "Hfr;": '\U0000210C', + "HilbertSpace;": '\U0000210B', + "Hopf;": '\U0000210D', + "HorizontalLine;": '\U00002500', + "Hscr;": '\U0000210B', + "Hstrok;": '\U00000126', + "HumpDownHump;": '\U0000224E', + "HumpEqual;": '\U0000224F', + "IEcy;": '\U00000415', + "IJlig;": '\U00000132', + "IOcy;": '\U00000401', + "Iacute;": '\U000000CD', + "Icirc;": '\U000000CE', + "Icy;": '\U00000418', + "Idot;": '\U00000130', + "Ifr;": '\U00002111', + "Igrave;": '\U000000CC', + "Im;": '\U00002111', + "Imacr;": '\U0000012A', + "ImaginaryI;": '\U00002148', + "Implies;": '\U000021D2', + "Int;": '\U0000222C', + "Integral;": '\U0000222B', + "Intersection;": '\U000022C2', + "InvisibleComma;": '\U00002063', + "InvisibleTimes;": '\U00002062', + "Iogon;": '\U0000012E', + "Iopf;": '\U0001D540', + "Iota;": '\U00000399', + "Iscr;": '\U00002110', + "Itilde;": '\U00000128', + "Iukcy;": '\U00000406', + "Iuml;": '\U000000CF', + "Jcirc;": '\U00000134', + "Jcy;": '\U00000419', + "Jfr;": '\U0001D50D', + "Jopf;": '\U0001D541', + "Jscr;": '\U0001D4A5', + "Jsercy;": '\U00000408', + "Jukcy;": '\U00000404', + "KHcy;": '\U00000425', + "KJcy;": '\U0000040C', + "Kappa;": '\U0000039A', + "Kcedil;": '\U00000136', + "Kcy;": '\U0000041A', + "Kfr;": '\U0001D50E', + "Kopf;": '\U0001D542', + "Kscr;": '\U0001D4A6', + "LJcy;": '\U00000409', + "LT;": '\U0000003C', + "Lacute;": '\U00000139', + "Lambda;": '\U0000039B', + "Lang;": '\U000027EA', + "Laplacetrf;": '\U00002112', + "Larr;": '\U0000219E', + "Lcaron;": '\U0000013D', + "Lcedil;": '\U0000013B', + "Lcy;": '\U0000041B', + "LeftAngleBracket;": '\U000027E8', + "LeftArrow;": '\U00002190', + "LeftArrowBar;": '\U000021E4', + "LeftArrowRightArrow;": '\U000021C6', + "LeftCeiling;": '\U00002308', + "LeftDoubleBracket;": '\U000027E6', + "LeftDownTeeVector;": '\U00002961', + "LeftDownVector;": '\U000021C3', + "LeftDownVectorBar;": '\U00002959', + "LeftFloor;": '\U0000230A', + "LeftRightArrow;": '\U00002194', + "LeftRightVector;": '\U0000294E', + "LeftTee;": '\U000022A3', + "LeftTeeArrow;": '\U000021A4', + "LeftTeeVector;": '\U0000295A', + "LeftTriangle;": '\U000022B2', + "LeftTriangleBar;": '\U000029CF', + "LeftTriangleEqual;": '\U000022B4', + "LeftUpDownVector;": '\U00002951', + "LeftUpTeeVector;": '\U00002960', + "LeftUpVector;": '\U000021BF', + "LeftUpVectorBar;": '\U00002958', + "LeftVector;": '\U000021BC', + "LeftVectorBar;": '\U00002952', + "Leftarrow;": '\U000021D0', + "Leftrightarrow;": '\U000021D4', + "LessEqualGreater;": '\U000022DA', + "LessFullEqual;": '\U00002266', + "LessGreater;": '\U00002276', + "LessLess;": '\U00002AA1', + "LessSlantEqual;": '\U00002A7D', + "LessTilde;": '\U00002272', + "Lfr;": '\U0001D50F', + "Ll;": '\U000022D8', + "Lleftarrow;": '\U000021DA', + "Lmidot;": '\U0000013F', + "LongLeftArrow;": '\U000027F5', + "LongLeftRightArrow;": '\U000027F7', + "LongRightArrow;": '\U000027F6', + "Longleftarrow;": '\U000027F8', + "Longleftrightarrow;": '\U000027FA', + "Longrightarrow;": '\U000027F9', + "Lopf;": '\U0001D543', + "LowerLeftArrow;": '\U00002199', + "LowerRightArrow;": '\U00002198', + "Lscr;": '\U00002112', + "Lsh;": '\U000021B0', + "Lstrok;": '\U00000141', + "Lt;": '\U0000226A', + "Map;": '\U00002905', + "Mcy;": '\U0000041C', + "MediumSpace;": '\U0000205F', + "Mellintrf;": '\U00002133', + "Mfr;": '\U0001D510', + "MinusPlus;": '\U00002213', + "Mopf;": '\U0001D544', + "Mscr;": '\U00002133', + "Mu;": '\U0000039C', + "NJcy;": '\U0000040A', + "Nacute;": '\U00000143', + "Ncaron;": '\U00000147', + "Ncedil;": '\U00000145', + "Ncy;": '\U0000041D', + "NegativeMediumSpace;": '\U0000200B', + "NegativeThickSpace;": '\U0000200B', + "NegativeThinSpace;": '\U0000200B', + "NegativeVeryThinSpace;": '\U0000200B', + "NestedGreaterGreater;": '\U0000226B', + "NestedLessLess;": '\U0000226A', + "NewLine;": '\U0000000A', + "Nfr;": '\U0001D511', + "NoBreak;": '\U00002060', + "NonBreakingSpace;": '\U000000A0', + "Nopf;": '\U00002115', + "Not;": '\U00002AEC', + "NotCongruent;": '\U00002262', + "NotCupCap;": '\U0000226D', + "NotDoubleVerticalBar;": '\U00002226', + "NotElement;": '\U00002209', + "NotEqual;": '\U00002260', + "NotExists;": '\U00002204', + "NotGreater;": '\U0000226F', + "NotGreaterEqual;": '\U00002271', + "NotGreaterLess;": '\U00002279', + "NotGreaterTilde;": '\U00002275', + "NotLeftTriangle;": '\U000022EA', + "NotLeftTriangleEqual;": '\U000022EC', + "NotLess;": '\U0000226E', + "NotLessEqual;": '\U00002270', + "NotLessGreater;": '\U00002278', + "NotLessTilde;": '\U00002274', + "NotPrecedes;": '\U00002280', + "NotPrecedesSlantEqual;": '\U000022E0', + "NotReverseElement;": '\U0000220C', + "NotRightTriangle;": '\U000022EB', + "NotRightTriangleEqual;": '\U000022ED', + "NotSquareSubsetEqual;": '\U000022E2', + "NotSquareSupersetEqual;": '\U000022E3', + "NotSubsetEqual;": '\U00002288', + "NotSucceeds;": '\U00002281', + "NotSucceedsSlantEqual;": '\U000022E1', + "NotSupersetEqual;": '\U00002289', + "NotTilde;": '\U00002241', + "NotTildeEqual;": '\U00002244', + "NotTildeFullEqual;": '\U00002247', + "NotTildeTilde;": '\U00002249', + "NotVerticalBar;": '\U00002224', + "Nscr;": '\U0001D4A9', + "Ntilde;": '\U000000D1', + "Nu;": '\U0000039D', + "OElig;": '\U00000152', + "Oacute;": '\U000000D3', + "Ocirc;": '\U000000D4', + "Ocy;": '\U0000041E', + "Odblac;": '\U00000150', + "Ofr;": '\U0001D512', + "Ograve;": '\U000000D2', + "Omacr;": '\U0000014C', + "Omega;": '\U000003A9', + "Omicron;": '\U0000039F', + "Oopf;": '\U0001D546', + "OpenCurlyDoubleQuote;": '\U0000201C', + "OpenCurlyQuote;": '\U00002018', + "Or;": '\U00002A54', + "Oscr;": '\U0001D4AA', + "Oslash;": '\U000000D8', + "Otilde;": '\U000000D5', + "Otimes;": '\U00002A37', + "Ouml;": '\U000000D6', + "OverBar;": '\U0000203E', + "OverBrace;": '\U000023DE', + "OverBracket;": '\U000023B4', + "OverParenthesis;": '\U000023DC', + "PartialD;": '\U00002202', + "Pcy;": '\U0000041F', + "Pfr;": '\U0001D513', + "Phi;": '\U000003A6', + "Pi;": '\U000003A0', + "PlusMinus;": '\U000000B1', + "Poincareplane;": '\U0000210C', + "Popf;": '\U00002119', + "Pr;": '\U00002ABB', + "Precedes;": '\U0000227A', + "PrecedesEqual;": '\U00002AAF', + "PrecedesSlantEqual;": '\U0000227C', + "PrecedesTilde;": '\U0000227E', + "Prime;": '\U00002033', + "Product;": '\U0000220F', + "Proportion;": '\U00002237', + "Proportional;": '\U0000221D', + "Pscr;": '\U0001D4AB', + "Psi;": '\U000003A8', + "QUOT;": '\U00000022', + "Qfr;": '\U0001D514', + "Qopf;": '\U0000211A', + "Qscr;": '\U0001D4AC', + "RBarr;": '\U00002910', + "REG;": '\U000000AE', + "Racute;": '\U00000154', + "Rang;": '\U000027EB', + "Rarr;": '\U000021A0', + "Rarrtl;": '\U00002916', + "Rcaron;": '\U00000158', + "Rcedil;": '\U00000156', + "Rcy;": '\U00000420', + "Re;": '\U0000211C', + "ReverseElement;": '\U0000220B', + "ReverseEquilibrium;": '\U000021CB', + "ReverseUpEquilibrium;": '\U0000296F', + "Rfr;": '\U0000211C', + "Rho;": '\U000003A1', + "RightAngleBracket;": '\U000027E9', + "RightArrow;": '\U00002192', + "RightArrowBar;": '\U000021E5', + "RightArrowLeftArrow;": '\U000021C4', + "RightCeiling;": '\U00002309', + "RightDoubleBracket;": '\U000027E7', + "RightDownTeeVector;": '\U0000295D', + "RightDownVector;": '\U000021C2', + "RightDownVectorBar;": '\U00002955', + "RightFloor;": '\U0000230B', + "RightTee;": '\U000022A2', + "RightTeeArrow;": '\U000021A6', + "RightTeeVector;": '\U0000295B', + "RightTriangle;": '\U000022B3', + "RightTriangleBar;": '\U000029D0', + "RightTriangleEqual;": '\U000022B5', + "RightUpDownVector;": '\U0000294F', + "RightUpTeeVector;": '\U0000295C', + "RightUpVector;": '\U000021BE', + "RightUpVectorBar;": '\U00002954', + "RightVector;": '\U000021C0', + "RightVectorBar;": '\U00002953', + "Rightarrow;": '\U000021D2', + "Ropf;": '\U0000211D', + "RoundImplies;": '\U00002970', + "Rrightarrow;": '\U000021DB', + "Rscr;": '\U0000211B', + "Rsh;": '\U000021B1', + "RuleDelayed;": '\U000029F4', + "SHCHcy;": '\U00000429', + "SHcy;": '\U00000428', + "SOFTcy;": '\U0000042C', + "Sacute;": '\U0000015A', + "Sc;": '\U00002ABC', + "Scaron;": '\U00000160', + "Scedil;": '\U0000015E', + "Scirc;": '\U0000015C', + "Scy;": '\U00000421', + "Sfr;": '\U0001D516', + "ShortDownArrow;": '\U00002193', + "ShortLeftArrow;": '\U00002190', + "ShortRightArrow;": '\U00002192', + "ShortUpArrow;": '\U00002191', + "Sigma;": '\U000003A3', + "SmallCircle;": '\U00002218', + "Sopf;": '\U0001D54A', + "Sqrt;": '\U0000221A', + "Square;": '\U000025A1', + "SquareIntersection;": '\U00002293', + "SquareSubset;": '\U0000228F', + "SquareSubsetEqual;": '\U00002291', + "SquareSuperset;": '\U00002290', + "SquareSupersetEqual;": '\U00002292', + "SquareUnion;": '\U00002294', + "Sscr;": '\U0001D4AE', + "Star;": '\U000022C6', + "Sub;": '\U000022D0', + "Subset;": '\U000022D0', + "SubsetEqual;": '\U00002286', + "Succeeds;": '\U0000227B', + "SucceedsEqual;": '\U00002AB0', + "SucceedsSlantEqual;": '\U0000227D', + "SucceedsTilde;": '\U0000227F', + "SuchThat;": '\U0000220B', + "Sum;": '\U00002211', + "Sup;": '\U000022D1', + "Superset;": '\U00002283', + "SupersetEqual;": '\U00002287', + "Supset;": '\U000022D1', + "THORN;": '\U000000DE', + "TRADE;": '\U00002122', + "TSHcy;": '\U0000040B', + "TScy;": '\U00000426', + "Tab;": '\U00000009', + "Tau;": '\U000003A4', + "Tcaron;": '\U00000164', + "Tcedil;": '\U00000162', + "Tcy;": '\U00000422', + "Tfr;": '\U0001D517', + "Therefore;": '\U00002234', + "Theta;": '\U00000398', + "ThinSpace;": '\U00002009', + "Tilde;": '\U0000223C', + "TildeEqual;": '\U00002243', + "TildeFullEqual;": '\U00002245', + "TildeTilde;": '\U00002248', + "Topf;": '\U0001D54B', + "TripleDot;": '\U000020DB', + "Tscr;": '\U0001D4AF', + "Tstrok;": '\U00000166', + "Uacute;": '\U000000DA', + "Uarr;": '\U0000219F', + "Uarrocir;": '\U00002949', + "Ubrcy;": '\U0000040E', + "Ubreve;": '\U0000016C', + "Ucirc;": '\U000000DB', + "Ucy;": '\U00000423', + "Udblac;": '\U00000170', + "Ufr;": '\U0001D518', + "Ugrave;": '\U000000D9', + "Umacr;": '\U0000016A', + "UnderBar;": '\U0000005F', + "UnderBrace;": '\U000023DF', + "UnderBracket;": '\U000023B5', + "UnderParenthesis;": '\U000023DD', + "Union;": '\U000022C3', + "UnionPlus;": '\U0000228E', + "Uogon;": '\U00000172', + "Uopf;": '\U0001D54C', + "UpArrow;": '\U00002191', + "UpArrowBar;": '\U00002912', + "UpArrowDownArrow;": '\U000021C5', + "UpDownArrow;": '\U00002195', + "UpEquilibrium;": '\U0000296E', + "UpTee;": '\U000022A5', + "UpTeeArrow;": '\U000021A5', + "Uparrow;": '\U000021D1', + "Updownarrow;": '\U000021D5', + "UpperLeftArrow;": '\U00002196', + "UpperRightArrow;": '\U00002197', + "Upsi;": '\U000003D2', + "Upsilon;": '\U000003A5', + "Uring;": '\U0000016E', + "Uscr;": '\U0001D4B0', + "Utilde;": '\U00000168', + "Uuml;": '\U000000DC', + "VDash;": '\U000022AB', + "Vbar;": '\U00002AEB', + "Vcy;": '\U00000412', + "Vdash;": '\U000022A9', + "Vdashl;": '\U00002AE6', + "Vee;": '\U000022C1', + "Verbar;": '\U00002016', + "Vert;": '\U00002016', + "VerticalBar;": '\U00002223', + "VerticalLine;": '\U0000007C', + "VerticalSeparator;": '\U00002758', + "VerticalTilde;": '\U00002240', + "VeryThinSpace;": '\U0000200A', + "Vfr;": '\U0001D519', + "Vopf;": '\U0001D54D', + "Vscr;": '\U0001D4B1', + "Vvdash;": '\U000022AA', + "Wcirc;": '\U00000174', + "Wedge;": '\U000022C0', + "Wfr;": '\U0001D51A', + "Wopf;": '\U0001D54E', + "Wscr;": '\U0001D4B2', + "Xfr;": '\U0001D51B', + "Xi;": '\U0000039E', + "Xopf;": '\U0001D54F', + "Xscr;": '\U0001D4B3', + "YAcy;": '\U0000042F', + "YIcy;": '\U00000407', + "YUcy;": '\U0000042E', + "Yacute;": '\U000000DD', + "Ycirc;": '\U00000176', + "Ycy;": '\U0000042B', + "Yfr;": '\U0001D51C', + "Yopf;": '\U0001D550', + "Yscr;": '\U0001D4B4', + "Yuml;": '\U00000178', + "ZHcy;": '\U00000416', + "Zacute;": '\U00000179', + "Zcaron;": '\U0000017D', + "Zcy;": '\U00000417', + "Zdot;": '\U0000017B', + "ZeroWidthSpace;": '\U0000200B', + "Zeta;": '\U00000396', + "Zfr;": '\U00002128', + "Zopf;": '\U00002124', + "Zscr;": '\U0001D4B5', + "aacute;": '\U000000E1', + "abreve;": '\U00000103', + "ac;": '\U0000223E', + "acd;": '\U0000223F', + "acirc;": '\U000000E2', + "acute;": '\U000000B4', + "acy;": '\U00000430', + "aelig;": '\U000000E6', + "af;": '\U00002061', + "afr;": '\U0001D51E', + "agrave;": '\U000000E0', + "alefsym;": '\U00002135', + "aleph;": '\U00002135', + "alpha;": '\U000003B1', + "amacr;": '\U00000101', + "amalg;": '\U00002A3F', + "amp;": '\U00000026', + "and;": '\U00002227', + "andand;": '\U00002A55', + "andd;": '\U00002A5C', + "andslope;": '\U00002A58', + "andv;": '\U00002A5A', + "ang;": '\U00002220', + "ange;": '\U000029A4', + "angle;": '\U00002220', + "angmsd;": '\U00002221', + "angmsdaa;": '\U000029A8', + "angmsdab;": '\U000029A9', + "angmsdac;": '\U000029AA', + "angmsdad;": '\U000029AB', + "angmsdae;": '\U000029AC', + "angmsdaf;": '\U000029AD', + "angmsdag;": '\U000029AE', + "angmsdah;": '\U000029AF', + "angrt;": '\U0000221F', + "angrtvb;": '\U000022BE', + "angrtvbd;": '\U0000299D', + "angsph;": '\U00002222', + "angst;": '\U000000C5', + "angzarr;": '\U0000237C', + "aogon;": '\U00000105', + "aopf;": '\U0001D552', + "ap;": '\U00002248', + "apE;": '\U00002A70', + "apacir;": '\U00002A6F', + "ape;": '\U0000224A', + "apid;": '\U0000224B', + "apos;": '\U00000027', + "approx;": '\U00002248', + "approxeq;": '\U0000224A', + "aring;": '\U000000E5', + "ascr;": '\U0001D4B6', + "ast;": '\U0000002A', + "asymp;": '\U00002248', + "asympeq;": '\U0000224D', + "atilde;": '\U000000E3', + "auml;": '\U000000E4', + "awconint;": '\U00002233', + "awint;": '\U00002A11', + "bNot;": '\U00002AED', + "backcong;": '\U0000224C', + "backepsilon;": '\U000003F6', + "backprime;": '\U00002035', + "backsim;": '\U0000223D', + "backsimeq;": '\U000022CD', + "barvee;": '\U000022BD', + "barwed;": '\U00002305', + "barwedge;": '\U00002305', + "bbrk;": '\U000023B5', + "bbrktbrk;": '\U000023B6', + "bcong;": '\U0000224C', + "bcy;": '\U00000431', + "bdquo;": '\U0000201E', + "becaus;": '\U00002235', + "because;": '\U00002235', + "bemptyv;": '\U000029B0', + "bepsi;": '\U000003F6', + "bernou;": '\U0000212C', + "beta;": '\U000003B2', + "beth;": '\U00002136', + "between;": '\U0000226C', + "bfr;": '\U0001D51F', + "bigcap;": '\U000022C2', + "bigcirc;": '\U000025EF', + "bigcup;": '\U000022C3', + "bigodot;": '\U00002A00', + "bigoplus;": '\U00002A01', + "bigotimes;": '\U00002A02', + "bigsqcup;": '\U00002A06', + "bigstar;": '\U00002605', + "bigtriangledown;": '\U000025BD', + "bigtriangleup;": '\U000025B3', + "biguplus;": '\U00002A04', + "bigvee;": '\U000022C1', + "bigwedge;": '\U000022C0', + "bkarow;": '\U0000290D', + "blacklozenge;": '\U000029EB', + "blacksquare;": '\U000025AA', + "blacktriangle;": '\U000025B4', + "blacktriangledown;": '\U000025BE', + "blacktriangleleft;": '\U000025C2', + "blacktriangleright;": '\U000025B8', + "blank;": '\U00002423', + "blk12;": '\U00002592', + "blk14;": '\U00002591', + "blk34;": '\U00002593', + "block;": '\U00002588', + "bnot;": '\U00002310', + "bopf;": '\U0001D553', + "bot;": '\U000022A5', + "bottom;": '\U000022A5', + "bowtie;": '\U000022C8', + "boxDL;": '\U00002557', + "boxDR;": '\U00002554', + "boxDl;": '\U00002556', + "boxDr;": '\U00002553', + "boxH;": '\U00002550', + "boxHD;": '\U00002566', + "boxHU;": '\U00002569', + "boxHd;": '\U00002564', + "boxHu;": '\U00002567', + "boxUL;": '\U0000255D', + "boxUR;": '\U0000255A', + "boxUl;": '\U0000255C', + "boxUr;": '\U00002559', + "boxV;": '\U00002551', + "boxVH;": '\U0000256C', + "boxVL;": '\U00002563', + "boxVR;": '\U00002560', + "boxVh;": '\U0000256B', + "boxVl;": '\U00002562', + "boxVr;": '\U0000255F', + "boxbox;": '\U000029C9', + "boxdL;": '\U00002555', + "boxdR;": '\U00002552', + "boxdl;": '\U00002510', + "boxdr;": '\U0000250C', + "boxh;": '\U00002500', + "boxhD;": '\U00002565', + "boxhU;": '\U00002568', + "boxhd;": '\U0000252C', + "boxhu;": '\U00002534', + "boxminus;": '\U0000229F', + "boxplus;": '\U0000229E', + "boxtimes;": '\U000022A0', + "boxuL;": '\U0000255B', + "boxuR;": '\U00002558', + "boxul;": '\U00002518', + "boxur;": '\U00002514', + "boxv;": '\U00002502', + "boxvH;": '\U0000256A', + "boxvL;": '\U00002561', + "boxvR;": '\U0000255E', + "boxvh;": '\U0000253C', + "boxvl;": '\U00002524', + "boxvr;": '\U0000251C', + "bprime;": '\U00002035', + "breve;": '\U000002D8', + "brvbar;": '\U000000A6', + "bscr;": '\U0001D4B7', + "bsemi;": '\U0000204F', + "bsim;": '\U0000223D', + "bsime;": '\U000022CD', + "bsol;": '\U0000005C', + "bsolb;": '\U000029C5', + "bsolhsub;": '\U000027C8', + "bull;": '\U00002022', + "bullet;": '\U00002022', + "bump;": '\U0000224E', + "bumpE;": '\U00002AAE', + "bumpe;": '\U0000224F', + "bumpeq;": '\U0000224F', + "cacute;": '\U00000107', + "cap;": '\U00002229', + "capand;": '\U00002A44', + "capbrcup;": '\U00002A49', + "capcap;": '\U00002A4B', + "capcup;": '\U00002A47', + "capdot;": '\U00002A40', + "caret;": '\U00002041', + "caron;": '\U000002C7', + "ccaps;": '\U00002A4D', + "ccaron;": '\U0000010D', + "ccedil;": '\U000000E7', + "ccirc;": '\U00000109', + "ccups;": '\U00002A4C', + "ccupssm;": '\U00002A50', + "cdot;": '\U0000010B', + "cedil;": '\U000000B8', + "cemptyv;": '\U000029B2', + "cent;": '\U000000A2', + "centerdot;": '\U000000B7', + "cfr;": '\U0001D520', + "chcy;": '\U00000447', + "check;": '\U00002713', + "checkmark;": '\U00002713', + "chi;": '\U000003C7', + "cir;": '\U000025CB', + "cirE;": '\U000029C3', + "circ;": '\U000002C6', + "circeq;": '\U00002257', + "circlearrowleft;": '\U000021BA', + "circlearrowright;": '\U000021BB', + "circledR;": '\U000000AE', + "circledS;": '\U000024C8', + "circledast;": '\U0000229B', + "circledcirc;": '\U0000229A', + "circleddash;": '\U0000229D', + "cire;": '\U00002257', + "cirfnint;": '\U00002A10', + "cirmid;": '\U00002AEF', + "cirscir;": '\U000029C2', + "clubs;": '\U00002663', + "clubsuit;": '\U00002663', + "colon;": '\U0000003A', + "colone;": '\U00002254', + "coloneq;": '\U00002254', + "comma;": '\U0000002C', + "commat;": '\U00000040', + "comp;": '\U00002201', + "compfn;": '\U00002218', + "complement;": '\U00002201', + "complexes;": '\U00002102', + "cong;": '\U00002245', + "congdot;": '\U00002A6D', + "conint;": '\U0000222E', + "copf;": '\U0001D554', + "coprod;": '\U00002210', + "copy;": '\U000000A9', + "copysr;": '\U00002117', + "crarr;": '\U000021B5', + "cross;": '\U00002717', + "cscr;": '\U0001D4B8', + "csub;": '\U00002ACF', + "csube;": '\U00002AD1', + "csup;": '\U00002AD0', + "csupe;": '\U00002AD2', + "ctdot;": '\U000022EF', + "cudarrl;": '\U00002938', + "cudarrr;": '\U00002935', + "cuepr;": '\U000022DE', + "cuesc;": '\U000022DF', + "cularr;": '\U000021B6', + "cularrp;": '\U0000293D', + "cup;": '\U0000222A', + "cupbrcap;": '\U00002A48', + "cupcap;": '\U00002A46', + "cupcup;": '\U00002A4A', + "cupdot;": '\U0000228D', + "cupor;": '\U00002A45', + "curarr;": '\U000021B7', + "curarrm;": '\U0000293C', + "curlyeqprec;": '\U000022DE', + "curlyeqsucc;": '\U000022DF', + "curlyvee;": '\U000022CE', + "curlywedge;": '\U000022CF', + "curren;": '\U000000A4', + "curvearrowleft;": '\U000021B6', + "curvearrowright;": '\U000021B7', + "cuvee;": '\U000022CE', + "cuwed;": '\U000022CF', + "cwconint;": '\U00002232', + "cwint;": '\U00002231', + "cylcty;": '\U0000232D', + "dArr;": '\U000021D3', + "dHar;": '\U00002965', + "dagger;": '\U00002020', + "daleth;": '\U00002138', + "darr;": '\U00002193', + "dash;": '\U00002010', + "dashv;": '\U000022A3', + "dbkarow;": '\U0000290F', + "dblac;": '\U000002DD', + "dcaron;": '\U0000010F', + "dcy;": '\U00000434', + "dd;": '\U00002146', + "ddagger;": '\U00002021', + "ddarr;": '\U000021CA', + "ddotseq;": '\U00002A77', + "deg;": '\U000000B0', + "delta;": '\U000003B4', + "demptyv;": '\U000029B1', + "dfisht;": '\U0000297F', + "dfr;": '\U0001D521', + "dharl;": '\U000021C3', + "dharr;": '\U000021C2', + "diam;": '\U000022C4', + "diamond;": '\U000022C4', + "diamondsuit;": '\U00002666', + "diams;": '\U00002666', + "die;": '\U000000A8', + "digamma;": '\U000003DD', + "disin;": '\U000022F2', + "div;": '\U000000F7', + "divide;": '\U000000F7', + "divideontimes;": '\U000022C7', + "divonx;": '\U000022C7', + "djcy;": '\U00000452', + "dlcorn;": '\U0000231E', + "dlcrop;": '\U0000230D', + "dollar;": '\U00000024', + "dopf;": '\U0001D555', + "dot;": '\U000002D9', + "doteq;": '\U00002250', + "doteqdot;": '\U00002251', + "dotminus;": '\U00002238', + "dotplus;": '\U00002214', + "dotsquare;": '\U000022A1', + "doublebarwedge;": '\U00002306', + "downarrow;": '\U00002193', + "downdownarrows;": '\U000021CA', + "downharpoonleft;": '\U000021C3', + "downharpoonright;": '\U000021C2', + "drbkarow;": '\U00002910', + "drcorn;": '\U0000231F', + "drcrop;": '\U0000230C', + "dscr;": '\U0001D4B9', + "dscy;": '\U00000455', + "dsol;": '\U000029F6', + "dstrok;": '\U00000111', + "dtdot;": '\U000022F1', + "dtri;": '\U000025BF', + "dtrif;": '\U000025BE', + "duarr;": '\U000021F5', + "duhar;": '\U0000296F', + "dwangle;": '\U000029A6', + "dzcy;": '\U0000045F', + "dzigrarr;": '\U000027FF', + "eDDot;": '\U00002A77', + "eDot;": '\U00002251', + "eacute;": '\U000000E9', + "easter;": '\U00002A6E', + "ecaron;": '\U0000011B', + "ecir;": '\U00002256', + "ecirc;": '\U000000EA', + "ecolon;": '\U00002255', + "ecy;": '\U0000044D', + "edot;": '\U00000117', + "ee;": '\U00002147', + "efDot;": '\U00002252', + "efr;": '\U0001D522', + "eg;": '\U00002A9A', + "egrave;": '\U000000E8', + "egs;": '\U00002A96', + "egsdot;": '\U00002A98', + "el;": '\U00002A99', + "elinters;": '\U000023E7', + "ell;": '\U00002113', + "els;": '\U00002A95', + "elsdot;": '\U00002A97', + "emacr;": '\U00000113', + "empty;": '\U00002205', + "emptyset;": '\U00002205', + "emptyv;": '\U00002205', + "emsp;": '\U00002003', + "emsp13;": '\U00002004', + "emsp14;": '\U00002005', + "eng;": '\U0000014B', + "ensp;": '\U00002002', + "eogon;": '\U00000119', + "eopf;": '\U0001D556', + "epar;": '\U000022D5', + "eparsl;": '\U000029E3', + "eplus;": '\U00002A71', + "epsi;": '\U000003B5', + "epsilon;": '\U000003B5', + "epsiv;": '\U000003F5', + "eqcirc;": '\U00002256', + "eqcolon;": '\U00002255', + "eqsim;": '\U00002242', + "eqslantgtr;": '\U00002A96', + "eqslantless;": '\U00002A95', + "equals;": '\U0000003D', + "equest;": '\U0000225F', + "equiv;": '\U00002261', + "equivDD;": '\U00002A78', + "eqvparsl;": '\U000029E5', + "erDot;": '\U00002253', + "erarr;": '\U00002971', + "escr;": '\U0000212F', + "esdot;": '\U00002250', + "esim;": '\U00002242', + "eta;": '\U000003B7', + "eth;": '\U000000F0', + "euml;": '\U000000EB', + "euro;": '\U000020AC', + "excl;": '\U00000021', + "exist;": '\U00002203', + "expectation;": '\U00002130', + "exponentiale;": '\U00002147', + "fallingdotseq;": '\U00002252', + "fcy;": '\U00000444', + "female;": '\U00002640', + "ffilig;": '\U0000FB03', + "fflig;": '\U0000FB00', + "ffllig;": '\U0000FB04', + "ffr;": '\U0001D523', + "filig;": '\U0000FB01', + "flat;": '\U0000266D', + "fllig;": '\U0000FB02', + "fltns;": '\U000025B1', + "fnof;": '\U00000192', + "fopf;": '\U0001D557', + "forall;": '\U00002200', + "fork;": '\U000022D4', + "forkv;": '\U00002AD9', + "fpartint;": '\U00002A0D', + "frac12;": '\U000000BD', + "frac13;": '\U00002153', + "frac14;": '\U000000BC', + "frac15;": '\U00002155', + "frac16;": '\U00002159', + "frac18;": '\U0000215B', + "frac23;": '\U00002154', + "frac25;": '\U00002156', + "frac34;": '\U000000BE', + "frac35;": '\U00002157', + "frac38;": '\U0000215C', + "frac45;": '\U00002158', + "frac56;": '\U0000215A', + "frac58;": '\U0000215D', + "frac78;": '\U0000215E', + "frasl;": '\U00002044', + "frown;": '\U00002322', + "fscr;": '\U0001D4BB', + "gE;": '\U00002267', + "gEl;": '\U00002A8C', + "gacute;": '\U000001F5', + "gamma;": '\U000003B3', + "gammad;": '\U000003DD', + "gap;": '\U00002A86', + "gbreve;": '\U0000011F', + "gcirc;": '\U0000011D', + "gcy;": '\U00000433', + "gdot;": '\U00000121', + "ge;": '\U00002265', + "gel;": '\U000022DB', + "geq;": '\U00002265', + "geqq;": '\U00002267', + "geqslant;": '\U00002A7E', + "ges;": '\U00002A7E', + "gescc;": '\U00002AA9', + "gesdot;": '\U00002A80', + "gesdoto;": '\U00002A82', + "gesdotol;": '\U00002A84', + "gesles;": '\U00002A94', + "gfr;": '\U0001D524', + "gg;": '\U0000226B', + "ggg;": '\U000022D9', + "gimel;": '\U00002137', + "gjcy;": '\U00000453', + "gl;": '\U00002277', + "glE;": '\U00002A92', + "gla;": '\U00002AA5', + "glj;": '\U00002AA4', + "gnE;": '\U00002269', + "gnap;": '\U00002A8A', + "gnapprox;": '\U00002A8A', + "gne;": '\U00002A88', + "gneq;": '\U00002A88', + "gneqq;": '\U00002269', + "gnsim;": '\U000022E7', + "gopf;": '\U0001D558', + "grave;": '\U00000060', + "gscr;": '\U0000210A', + "gsim;": '\U00002273', + "gsime;": '\U00002A8E', + "gsiml;": '\U00002A90', + "gt;": '\U0000003E', + "gtcc;": '\U00002AA7', + "gtcir;": '\U00002A7A', + "gtdot;": '\U000022D7', + "gtlPar;": '\U00002995', + "gtquest;": '\U00002A7C', + "gtrapprox;": '\U00002A86', + "gtrarr;": '\U00002978', + "gtrdot;": '\U000022D7', + "gtreqless;": '\U000022DB', + "gtreqqless;": '\U00002A8C', + "gtrless;": '\U00002277', + "gtrsim;": '\U00002273', + "hArr;": '\U000021D4', + "hairsp;": '\U0000200A', + "half;": '\U000000BD', + "hamilt;": '\U0000210B', + "hardcy;": '\U0000044A', + "harr;": '\U00002194', + "harrcir;": '\U00002948', + "harrw;": '\U000021AD', + "hbar;": '\U0000210F', + "hcirc;": '\U00000125', + "hearts;": '\U00002665', + "heartsuit;": '\U00002665', + "hellip;": '\U00002026', + "hercon;": '\U000022B9', + "hfr;": '\U0001D525', + "hksearow;": '\U00002925', + "hkswarow;": '\U00002926', + "hoarr;": '\U000021FF', + "homtht;": '\U0000223B', + "hookleftarrow;": '\U000021A9', + "hookrightarrow;": '\U000021AA', + "hopf;": '\U0001D559', + "horbar;": '\U00002015', + "hscr;": '\U0001D4BD', + "hslash;": '\U0000210F', + "hstrok;": '\U00000127', + "hybull;": '\U00002043', + "hyphen;": '\U00002010', + "iacute;": '\U000000ED', + "ic;": '\U00002063', + "icirc;": '\U000000EE', + "icy;": '\U00000438', + "iecy;": '\U00000435', + "iexcl;": '\U000000A1', + "iff;": '\U000021D4', + "ifr;": '\U0001D526', + "igrave;": '\U000000EC', + "ii;": '\U00002148', + "iiiint;": '\U00002A0C', + "iiint;": '\U0000222D', + "iinfin;": '\U000029DC', + "iiota;": '\U00002129', + "ijlig;": '\U00000133', + "imacr;": '\U0000012B', + "image;": '\U00002111', + "imagline;": '\U00002110', + "imagpart;": '\U00002111', + "imath;": '\U00000131', + "imof;": '\U000022B7', + "imped;": '\U000001B5', + "in;": '\U00002208', + "incare;": '\U00002105', + "infin;": '\U0000221E', + "infintie;": '\U000029DD', + "inodot;": '\U00000131', + "int;": '\U0000222B', + "intcal;": '\U000022BA', + "integers;": '\U00002124', + "intercal;": '\U000022BA', + "intlarhk;": '\U00002A17', + "intprod;": '\U00002A3C', + "iocy;": '\U00000451', + "iogon;": '\U0000012F', + "iopf;": '\U0001D55A', + "iota;": '\U000003B9', + "iprod;": '\U00002A3C', + "iquest;": '\U000000BF', + "iscr;": '\U0001D4BE', + "isin;": '\U00002208', + "isinE;": '\U000022F9', + "isindot;": '\U000022F5', + "isins;": '\U000022F4', + "isinsv;": '\U000022F3', + "isinv;": '\U00002208', + "it;": '\U00002062', + "itilde;": '\U00000129', + "iukcy;": '\U00000456', + "iuml;": '\U000000EF', + "jcirc;": '\U00000135', + "jcy;": '\U00000439', + "jfr;": '\U0001D527', + "jmath;": '\U00000237', + "jopf;": '\U0001D55B', + "jscr;": '\U0001D4BF', + "jsercy;": '\U00000458', + "jukcy;": '\U00000454', + "kappa;": '\U000003BA', + "kappav;": '\U000003F0', + "kcedil;": '\U00000137', + "kcy;": '\U0000043A', + "kfr;": '\U0001D528', + "kgreen;": '\U00000138', + "khcy;": '\U00000445', + "kjcy;": '\U0000045C', + "kopf;": '\U0001D55C', + "kscr;": '\U0001D4C0', + "lAarr;": '\U000021DA', + "lArr;": '\U000021D0', + "lAtail;": '\U0000291B', + "lBarr;": '\U0000290E', + "lE;": '\U00002266', + "lEg;": '\U00002A8B', + "lHar;": '\U00002962', + "lacute;": '\U0000013A', + "laemptyv;": '\U000029B4', + "lagran;": '\U00002112', + "lambda;": '\U000003BB', + "lang;": '\U000027E8', + "langd;": '\U00002991', + "langle;": '\U000027E8', + "lap;": '\U00002A85', + "laquo;": '\U000000AB', + "larr;": '\U00002190', + "larrb;": '\U000021E4', + "larrbfs;": '\U0000291F', + "larrfs;": '\U0000291D', + "larrhk;": '\U000021A9', + "larrlp;": '\U000021AB', + "larrpl;": '\U00002939', + "larrsim;": '\U00002973', + "larrtl;": '\U000021A2', + "lat;": '\U00002AAB', + "latail;": '\U00002919', + "late;": '\U00002AAD', + "lbarr;": '\U0000290C', + "lbbrk;": '\U00002772', + "lbrace;": '\U0000007B', + "lbrack;": '\U0000005B', + "lbrke;": '\U0000298B', + "lbrksld;": '\U0000298F', + "lbrkslu;": '\U0000298D', + "lcaron;": '\U0000013E', + "lcedil;": '\U0000013C', + "lceil;": '\U00002308', + "lcub;": '\U0000007B', + "lcy;": '\U0000043B', + "ldca;": '\U00002936', + "ldquo;": '\U0000201C', + "ldquor;": '\U0000201E', + "ldrdhar;": '\U00002967', + "ldrushar;": '\U0000294B', + "ldsh;": '\U000021B2', + "le;": '\U00002264', + "leftarrow;": '\U00002190', + "leftarrowtail;": '\U000021A2', + "leftharpoondown;": '\U000021BD', + "leftharpoonup;": '\U000021BC', + "leftleftarrows;": '\U000021C7', + "leftrightarrow;": '\U00002194', + "leftrightarrows;": '\U000021C6', + "leftrightharpoons;": '\U000021CB', + "leftrightsquigarrow;": '\U000021AD', + "leftthreetimes;": '\U000022CB', + "leg;": '\U000022DA', + "leq;": '\U00002264', + "leqq;": '\U00002266', + "leqslant;": '\U00002A7D', + "les;": '\U00002A7D', + "lescc;": '\U00002AA8', + "lesdot;": '\U00002A7F', + "lesdoto;": '\U00002A81', + "lesdotor;": '\U00002A83', + "lesges;": '\U00002A93', + "lessapprox;": '\U00002A85', + "lessdot;": '\U000022D6', + "lesseqgtr;": '\U000022DA', + "lesseqqgtr;": '\U00002A8B', + "lessgtr;": '\U00002276', + "lesssim;": '\U00002272', + "lfisht;": '\U0000297C', + "lfloor;": '\U0000230A', + "lfr;": '\U0001D529', + "lg;": '\U00002276', + "lgE;": '\U00002A91', + "lhard;": '\U000021BD', + "lharu;": '\U000021BC', + "lharul;": '\U0000296A', + "lhblk;": '\U00002584', + "ljcy;": '\U00000459', + "ll;": '\U0000226A', + "llarr;": '\U000021C7', + "llcorner;": '\U0000231E', + "llhard;": '\U0000296B', + "lltri;": '\U000025FA', + "lmidot;": '\U00000140', + "lmoust;": '\U000023B0', + "lmoustache;": '\U000023B0', + "lnE;": '\U00002268', + "lnap;": '\U00002A89', + "lnapprox;": '\U00002A89', + "lne;": '\U00002A87', + "lneq;": '\U00002A87', + "lneqq;": '\U00002268', + "lnsim;": '\U000022E6', + "loang;": '\U000027EC', + "loarr;": '\U000021FD', + "lobrk;": '\U000027E6', + "longleftarrow;": '\U000027F5', + "longleftrightarrow;": '\U000027F7', + "longmapsto;": '\U000027FC', + "longrightarrow;": '\U000027F6', + "looparrowleft;": '\U000021AB', + "looparrowright;": '\U000021AC', + "lopar;": '\U00002985', + "lopf;": '\U0001D55D', + "loplus;": '\U00002A2D', + "lotimes;": '\U00002A34', + "lowast;": '\U00002217', + "lowbar;": '\U0000005F', + "loz;": '\U000025CA', + "lozenge;": '\U000025CA', + "lozf;": '\U000029EB', + "lpar;": '\U00000028', + "lparlt;": '\U00002993', + "lrarr;": '\U000021C6', + "lrcorner;": '\U0000231F', + "lrhar;": '\U000021CB', + "lrhard;": '\U0000296D', + "lrm;": '\U0000200E', + "lrtri;": '\U000022BF', + "lsaquo;": '\U00002039', + "lscr;": '\U0001D4C1', + "lsh;": '\U000021B0', + "lsim;": '\U00002272', + "lsime;": '\U00002A8D', + "lsimg;": '\U00002A8F', + "lsqb;": '\U0000005B', + "lsquo;": '\U00002018', + "lsquor;": '\U0000201A', + "lstrok;": '\U00000142', + "lt;": '\U0000003C', + "ltcc;": '\U00002AA6', + "ltcir;": '\U00002A79', + "ltdot;": '\U000022D6', + "lthree;": '\U000022CB', + "ltimes;": '\U000022C9', + "ltlarr;": '\U00002976', + "ltquest;": '\U00002A7B', + "ltrPar;": '\U00002996', + "ltri;": '\U000025C3', + "ltrie;": '\U000022B4', + "ltrif;": '\U000025C2', + "lurdshar;": '\U0000294A', + "luruhar;": '\U00002966', + "mDDot;": '\U0000223A', + "macr;": '\U000000AF', + "male;": '\U00002642', + "malt;": '\U00002720', + "maltese;": '\U00002720', + "map;": '\U000021A6', + "mapsto;": '\U000021A6', + "mapstodown;": '\U000021A7', + "mapstoleft;": '\U000021A4', + "mapstoup;": '\U000021A5', + "marker;": '\U000025AE', + "mcomma;": '\U00002A29', + "mcy;": '\U0000043C', + "mdash;": '\U00002014', + "measuredangle;": '\U00002221', + "mfr;": '\U0001D52A', + "mho;": '\U00002127', + "micro;": '\U000000B5', + "mid;": '\U00002223', + "midast;": '\U0000002A', + "midcir;": '\U00002AF0', + "middot;": '\U000000B7', + "minus;": '\U00002212', + "minusb;": '\U0000229F', + "minusd;": '\U00002238', + "minusdu;": '\U00002A2A', + "mlcp;": '\U00002ADB', + "mldr;": '\U00002026', + "mnplus;": '\U00002213', + "models;": '\U000022A7', + "mopf;": '\U0001D55E', + "mp;": '\U00002213', + "mscr;": '\U0001D4C2', + "mstpos;": '\U0000223E', + "mu;": '\U000003BC', + "multimap;": '\U000022B8', + "mumap;": '\U000022B8', + "nLeftarrow;": '\U000021CD', + "nLeftrightarrow;": '\U000021CE', + "nRightarrow;": '\U000021CF', + "nVDash;": '\U000022AF', + "nVdash;": '\U000022AE', + "nabla;": '\U00002207', + "nacute;": '\U00000144', + "nap;": '\U00002249', + "napos;": '\U00000149', + "napprox;": '\U00002249', + "natur;": '\U0000266E', + "natural;": '\U0000266E', + "naturals;": '\U00002115', + "nbsp;": '\U000000A0', + "ncap;": '\U00002A43', + "ncaron;": '\U00000148', + "ncedil;": '\U00000146', + "ncong;": '\U00002247', + "ncup;": '\U00002A42', + "ncy;": '\U0000043D', + "ndash;": '\U00002013', + "ne;": '\U00002260', + "neArr;": '\U000021D7', + "nearhk;": '\U00002924', + "nearr;": '\U00002197', + "nearrow;": '\U00002197', + "nequiv;": '\U00002262', + "nesear;": '\U00002928', + "nexist;": '\U00002204', + "nexists;": '\U00002204', + "nfr;": '\U0001D52B', + "nge;": '\U00002271', + "ngeq;": '\U00002271', + "ngsim;": '\U00002275', + "ngt;": '\U0000226F', + "ngtr;": '\U0000226F', + "nhArr;": '\U000021CE', + "nharr;": '\U000021AE', + "nhpar;": '\U00002AF2', + "ni;": '\U0000220B', + "nis;": '\U000022FC', + "nisd;": '\U000022FA', + "niv;": '\U0000220B', + "njcy;": '\U0000045A', + "nlArr;": '\U000021CD', + "nlarr;": '\U0000219A', + "nldr;": '\U00002025', + "nle;": '\U00002270', + "nleftarrow;": '\U0000219A', + "nleftrightarrow;": '\U000021AE', + "nleq;": '\U00002270', + "nless;": '\U0000226E', + "nlsim;": '\U00002274', + "nlt;": '\U0000226E', + "nltri;": '\U000022EA', + "nltrie;": '\U000022EC', + "nmid;": '\U00002224', + "nopf;": '\U0001D55F', + "not;": '\U000000AC', + "notin;": '\U00002209', + "notinva;": '\U00002209', + "notinvb;": '\U000022F7', + "notinvc;": '\U000022F6', + "notni;": '\U0000220C', + "notniva;": '\U0000220C', + "notnivb;": '\U000022FE', + "notnivc;": '\U000022FD', + "npar;": '\U00002226', + "nparallel;": '\U00002226', + "npolint;": '\U00002A14', + "npr;": '\U00002280', + "nprcue;": '\U000022E0', + "nprec;": '\U00002280', + "nrArr;": '\U000021CF', + "nrarr;": '\U0000219B', + "nrightarrow;": '\U0000219B', + "nrtri;": '\U000022EB', + "nrtrie;": '\U000022ED', + "nsc;": '\U00002281', + "nsccue;": '\U000022E1', + "nscr;": '\U0001D4C3', + "nshortmid;": '\U00002224', + "nshortparallel;": '\U00002226', + "nsim;": '\U00002241', + "nsime;": '\U00002244', + "nsimeq;": '\U00002244', + "nsmid;": '\U00002224', + "nspar;": '\U00002226', + "nsqsube;": '\U000022E2', + "nsqsupe;": '\U000022E3', + "nsub;": '\U00002284', + "nsube;": '\U00002288', + "nsubseteq;": '\U00002288', + "nsucc;": '\U00002281', + "nsup;": '\U00002285', + "nsupe;": '\U00002289', + "nsupseteq;": '\U00002289', + "ntgl;": '\U00002279', + "ntilde;": '\U000000F1', + "ntlg;": '\U00002278', + "ntriangleleft;": '\U000022EA', + "ntrianglelefteq;": '\U000022EC', + "ntriangleright;": '\U000022EB', + "ntrianglerighteq;": '\U000022ED', + "nu;": '\U000003BD', + "num;": '\U00000023', + "numero;": '\U00002116', + "numsp;": '\U00002007', + "nvDash;": '\U000022AD', + "nvHarr;": '\U00002904', + "nvdash;": '\U000022AC', + "nvinfin;": '\U000029DE', + "nvlArr;": '\U00002902', + "nvrArr;": '\U00002903', + "nwArr;": '\U000021D6', + "nwarhk;": '\U00002923', + "nwarr;": '\U00002196', + "nwarrow;": '\U00002196', + "nwnear;": '\U00002927', + "oS;": '\U000024C8', + "oacute;": '\U000000F3', + "oast;": '\U0000229B', + "ocir;": '\U0000229A', + "ocirc;": '\U000000F4', + "ocy;": '\U0000043E', + "odash;": '\U0000229D', + "odblac;": '\U00000151', + "odiv;": '\U00002A38', + "odot;": '\U00002299', + "odsold;": '\U000029BC', + "oelig;": '\U00000153', + "ofcir;": '\U000029BF', + "ofr;": '\U0001D52C', + "ogon;": '\U000002DB', + "ograve;": '\U000000F2', + "ogt;": '\U000029C1', + "ohbar;": '\U000029B5', + "ohm;": '\U000003A9', + "oint;": '\U0000222E', + "olarr;": '\U000021BA', + "olcir;": '\U000029BE', + "olcross;": '\U000029BB', + "oline;": '\U0000203E', + "olt;": '\U000029C0', + "omacr;": '\U0000014D', + "omega;": '\U000003C9', + "omicron;": '\U000003BF', + "omid;": '\U000029B6', + "ominus;": '\U00002296', + "oopf;": '\U0001D560', + "opar;": '\U000029B7', + "operp;": '\U000029B9', + "oplus;": '\U00002295', + "or;": '\U00002228', + "orarr;": '\U000021BB', + "ord;": '\U00002A5D', + "order;": '\U00002134', + "orderof;": '\U00002134', + "ordf;": '\U000000AA', + "ordm;": '\U000000BA', + "origof;": '\U000022B6', + "oror;": '\U00002A56', + "orslope;": '\U00002A57', + "orv;": '\U00002A5B', + "oscr;": '\U00002134', + "oslash;": '\U000000F8', + "osol;": '\U00002298', + "otilde;": '\U000000F5', + "otimes;": '\U00002297', + "otimesas;": '\U00002A36', + "ouml;": '\U000000F6', + "ovbar;": '\U0000233D', + "par;": '\U00002225', + "para;": '\U000000B6', + "parallel;": '\U00002225', + "parsim;": '\U00002AF3', + "parsl;": '\U00002AFD', + "part;": '\U00002202', + "pcy;": '\U0000043F', + "percnt;": '\U00000025', + "period;": '\U0000002E', + "permil;": '\U00002030', + "perp;": '\U000022A5', + "pertenk;": '\U00002031', + "pfr;": '\U0001D52D', + "phi;": '\U000003C6', + "phiv;": '\U000003D5', + "phmmat;": '\U00002133', + "phone;": '\U0000260E', + "pi;": '\U000003C0', + "pitchfork;": '\U000022D4', + "piv;": '\U000003D6', + "planck;": '\U0000210F', + "planckh;": '\U0000210E', + "plankv;": '\U0000210F', + "plus;": '\U0000002B', + "plusacir;": '\U00002A23', + "plusb;": '\U0000229E', + "pluscir;": '\U00002A22', + "plusdo;": '\U00002214', + "plusdu;": '\U00002A25', + "pluse;": '\U00002A72', + "plusmn;": '\U000000B1', + "plussim;": '\U00002A26', + "plustwo;": '\U00002A27', + "pm;": '\U000000B1', + "pointint;": '\U00002A15', + "popf;": '\U0001D561', + "pound;": '\U000000A3', + "pr;": '\U0000227A', + "prE;": '\U00002AB3', + "prap;": '\U00002AB7', + "prcue;": '\U0000227C', + "pre;": '\U00002AAF', + "prec;": '\U0000227A', + "precapprox;": '\U00002AB7', + "preccurlyeq;": '\U0000227C', + "preceq;": '\U00002AAF', + "precnapprox;": '\U00002AB9', + "precneqq;": '\U00002AB5', + "precnsim;": '\U000022E8', + "precsim;": '\U0000227E', + "prime;": '\U00002032', + "primes;": '\U00002119', + "prnE;": '\U00002AB5', + "prnap;": '\U00002AB9', + "prnsim;": '\U000022E8', + "prod;": '\U0000220F', + "profalar;": '\U0000232E', + "profline;": '\U00002312', + "profsurf;": '\U00002313', + "prop;": '\U0000221D', + "propto;": '\U0000221D', + "prsim;": '\U0000227E', + "prurel;": '\U000022B0', + "pscr;": '\U0001D4C5', + "psi;": '\U000003C8', + "puncsp;": '\U00002008', + "qfr;": '\U0001D52E', + "qint;": '\U00002A0C', + "qopf;": '\U0001D562', + "qprime;": '\U00002057', + "qscr;": '\U0001D4C6', + "quaternions;": '\U0000210D', + "quatint;": '\U00002A16', + "quest;": '\U0000003F', + "questeq;": '\U0000225F', + "quot;": '\U00000022', + "rAarr;": '\U000021DB', + "rArr;": '\U000021D2', + "rAtail;": '\U0000291C', + "rBarr;": '\U0000290F', + "rHar;": '\U00002964', + "racute;": '\U00000155', + "radic;": '\U0000221A', + "raemptyv;": '\U000029B3', + "rang;": '\U000027E9', + "rangd;": '\U00002992', + "range;": '\U000029A5', + "rangle;": '\U000027E9', + "raquo;": '\U000000BB', + "rarr;": '\U00002192', + "rarrap;": '\U00002975', + "rarrb;": '\U000021E5', + "rarrbfs;": '\U00002920', + "rarrc;": '\U00002933', + "rarrfs;": '\U0000291E', + "rarrhk;": '\U000021AA', + "rarrlp;": '\U000021AC', + "rarrpl;": '\U00002945', + "rarrsim;": '\U00002974', + "rarrtl;": '\U000021A3', + "rarrw;": '\U0000219D', + "ratail;": '\U0000291A', + "ratio;": '\U00002236', + "rationals;": '\U0000211A', + "rbarr;": '\U0000290D', + "rbbrk;": '\U00002773', + "rbrace;": '\U0000007D', + "rbrack;": '\U0000005D', + "rbrke;": '\U0000298C', + "rbrksld;": '\U0000298E', + "rbrkslu;": '\U00002990', + "rcaron;": '\U00000159', + "rcedil;": '\U00000157', + "rceil;": '\U00002309', + "rcub;": '\U0000007D', + "rcy;": '\U00000440', + "rdca;": '\U00002937', + "rdldhar;": '\U00002969', + "rdquo;": '\U0000201D', + "rdquor;": '\U0000201D', + "rdsh;": '\U000021B3', + "real;": '\U0000211C', + "realine;": '\U0000211B', + "realpart;": '\U0000211C', + "reals;": '\U0000211D', + "rect;": '\U000025AD', + "reg;": '\U000000AE', + "rfisht;": '\U0000297D', + "rfloor;": '\U0000230B', + "rfr;": '\U0001D52F', + "rhard;": '\U000021C1', + "rharu;": '\U000021C0', + "rharul;": '\U0000296C', + "rho;": '\U000003C1', + "rhov;": '\U000003F1', + "rightarrow;": '\U00002192', + "rightarrowtail;": '\U000021A3', + "rightharpoondown;": '\U000021C1', + "rightharpoonup;": '\U000021C0', + "rightleftarrows;": '\U000021C4', + "rightleftharpoons;": '\U000021CC', + "rightrightarrows;": '\U000021C9', + "rightsquigarrow;": '\U0000219D', + "rightthreetimes;": '\U000022CC', + "ring;": '\U000002DA', + "risingdotseq;": '\U00002253', + "rlarr;": '\U000021C4', + "rlhar;": '\U000021CC', + "rlm;": '\U0000200F', + "rmoust;": '\U000023B1', + "rmoustache;": '\U000023B1', + "rnmid;": '\U00002AEE', + "roang;": '\U000027ED', + "roarr;": '\U000021FE', + "robrk;": '\U000027E7', + "ropar;": '\U00002986', + "ropf;": '\U0001D563', + "roplus;": '\U00002A2E', + "rotimes;": '\U00002A35', + "rpar;": '\U00000029', + "rpargt;": '\U00002994', + "rppolint;": '\U00002A12', + "rrarr;": '\U000021C9', + "rsaquo;": '\U0000203A', + "rscr;": '\U0001D4C7', + "rsh;": '\U000021B1', + "rsqb;": '\U0000005D', + "rsquo;": '\U00002019', + "rsquor;": '\U00002019', + "rthree;": '\U000022CC', + "rtimes;": '\U000022CA', + "rtri;": '\U000025B9', + "rtrie;": '\U000022B5', + "rtrif;": '\U000025B8', + "rtriltri;": '\U000029CE', + "ruluhar;": '\U00002968', + "rx;": '\U0000211E', + "sacute;": '\U0000015B', + "sbquo;": '\U0000201A', + "sc;": '\U0000227B', + "scE;": '\U00002AB4', + "scap;": '\U00002AB8', + "scaron;": '\U00000161', + "sccue;": '\U0000227D', + "sce;": '\U00002AB0', + "scedil;": '\U0000015F', + "scirc;": '\U0000015D', + "scnE;": '\U00002AB6', + "scnap;": '\U00002ABA', + "scnsim;": '\U000022E9', + "scpolint;": '\U00002A13', + "scsim;": '\U0000227F', + "scy;": '\U00000441', + "sdot;": '\U000022C5', + "sdotb;": '\U000022A1', + "sdote;": '\U00002A66', + "seArr;": '\U000021D8', + "searhk;": '\U00002925', + "searr;": '\U00002198', + "searrow;": '\U00002198', + "sect;": '\U000000A7', + "semi;": '\U0000003B', + "seswar;": '\U00002929', + "setminus;": '\U00002216', + "setmn;": '\U00002216', + "sext;": '\U00002736', + "sfr;": '\U0001D530', + "sfrown;": '\U00002322', + "sharp;": '\U0000266F', + "shchcy;": '\U00000449', + "shcy;": '\U00000448', + "shortmid;": '\U00002223', + "shortparallel;": '\U00002225', + "shy;": '\U000000AD', + "sigma;": '\U000003C3', + "sigmaf;": '\U000003C2', + "sigmav;": '\U000003C2', + "sim;": '\U0000223C', + "simdot;": '\U00002A6A', + "sime;": '\U00002243', + "simeq;": '\U00002243', + "simg;": '\U00002A9E', + "simgE;": '\U00002AA0', + "siml;": '\U00002A9D', + "simlE;": '\U00002A9F', + "simne;": '\U00002246', + "simplus;": '\U00002A24', + "simrarr;": '\U00002972', + "slarr;": '\U00002190', + "smallsetminus;": '\U00002216', + "smashp;": '\U00002A33', + "smeparsl;": '\U000029E4', + "smid;": '\U00002223', + "smile;": '\U00002323', + "smt;": '\U00002AAA', + "smte;": '\U00002AAC', + "softcy;": '\U0000044C', + "sol;": '\U0000002F', + "solb;": '\U000029C4', + "solbar;": '\U0000233F', + "sopf;": '\U0001D564', + "spades;": '\U00002660', + "spadesuit;": '\U00002660', + "spar;": '\U00002225', + "sqcap;": '\U00002293', + "sqcup;": '\U00002294', + "sqsub;": '\U0000228F', + "sqsube;": '\U00002291', + "sqsubset;": '\U0000228F', + "sqsubseteq;": '\U00002291', + "sqsup;": '\U00002290', + "sqsupe;": '\U00002292', + "sqsupset;": '\U00002290', + "sqsupseteq;": '\U00002292', + "squ;": '\U000025A1', + "square;": '\U000025A1', + "squarf;": '\U000025AA', + "squf;": '\U000025AA', + "srarr;": '\U00002192', + "sscr;": '\U0001D4C8', + "ssetmn;": '\U00002216', + "ssmile;": '\U00002323', + "sstarf;": '\U000022C6', + "star;": '\U00002606', + "starf;": '\U00002605', + "straightepsilon;": '\U000003F5', + "straightphi;": '\U000003D5', + "strns;": '\U000000AF', + "sub;": '\U00002282', + "subE;": '\U00002AC5', + "subdot;": '\U00002ABD', + "sube;": '\U00002286', + "subedot;": '\U00002AC3', + "submult;": '\U00002AC1', + "subnE;": '\U00002ACB', + "subne;": '\U0000228A', + "subplus;": '\U00002ABF', + "subrarr;": '\U00002979', + "subset;": '\U00002282', + "subseteq;": '\U00002286', + "subseteqq;": '\U00002AC5', + "subsetneq;": '\U0000228A', + "subsetneqq;": '\U00002ACB', + "subsim;": '\U00002AC7', + "subsub;": '\U00002AD5', + "subsup;": '\U00002AD3', + "succ;": '\U0000227B', + "succapprox;": '\U00002AB8', + "succcurlyeq;": '\U0000227D', + "succeq;": '\U00002AB0', + "succnapprox;": '\U00002ABA', + "succneqq;": '\U00002AB6', + "succnsim;": '\U000022E9', + "succsim;": '\U0000227F', + "sum;": '\U00002211', + "sung;": '\U0000266A', + "sup;": '\U00002283', + "sup1;": '\U000000B9', + "sup2;": '\U000000B2', + "sup3;": '\U000000B3', + "supE;": '\U00002AC6', + "supdot;": '\U00002ABE', + "supdsub;": '\U00002AD8', + "supe;": '\U00002287', + "supedot;": '\U00002AC4', + "suphsol;": '\U000027C9', + "suphsub;": '\U00002AD7', + "suplarr;": '\U0000297B', + "supmult;": '\U00002AC2', + "supnE;": '\U00002ACC', + "supne;": '\U0000228B', + "supplus;": '\U00002AC0', + "supset;": '\U00002283', + "supseteq;": '\U00002287', + "supseteqq;": '\U00002AC6', + "supsetneq;": '\U0000228B', + "supsetneqq;": '\U00002ACC', + "supsim;": '\U00002AC8', + "supsub;": '\U00002AD4', + "supsup;": '\U00002AD6', + "swArr;": '\U000021D9', + "swarhk;": '\U00002926', + "swarr;": '\U00002199', + "swarrow;": '\U00002199', + "swnwar;": '\U0000292A', + "szlig;": '\U000000DF', + "target;": '\U00002316', + "tau;": '\U000003C4', + "tbrk;": '\U000023B4', + "tcaron;": '\U00000165', + "tcedil;": '\U00000163', + "tcy;": '\U00000442', + "tdot;": '\U000020DB', + "telrec;": '\U00002315', + "tfr;": '\U0001D531', + "there4;": '\U00002234', + "therefore;": '\U00002234', + "theta;": '\U000003B8', + "thetasym;": '\U000003D1', + "thetav;": '\U000003D1', + "thickapprox;": '\U00002248', + "thicksim;": '\U0000223C', + "thinsp;": '\U00002009', + "thkap;": '\U00002248', + "thksim;": '\U0000223C', + "thorn;": '\U000000FE', + "tilde;": '\U000002DC', + "times;": '\U000000D7', + "timesb;": '\U000022A0', + "timesbar;": '\U00002A31', + "timesd;": '\U00002A30', + "tint;": '\U0000222D', + "toea;": '\U00002928', + "top;": '\U000022A4', + "topbot;": '\U00002336', + "topcir;": '\U00002AF1', + "topf;": '\U0001D565', + "topfork;": '\U00002ADA', + "tosa;": '\U00002929', + "tprime;": '\U00002034', + "trade;": '\U00002122', + "triangle;": '\U000025B5', + "triangledown;": '\U000025BF', + "triangleleft;": '\U000025C3', + "trianglelefteq;": '\U000022B4', + "triangleq;": '\U0000225C', + "triangleright;": '\U000025B9', + "trianglerighteq;": '\U000022B5', + "tridot;": '\U000025EC', + "trie;": '\U0000225C', + "triminus;": '\U00002A3A', + "triplus;": '\U00002A39', + "trisb;": '\U000029CD', + "tritime;": '\U00002A3B', + "trpezium;": '\U000023E2', + "tscr;": '\U0001D4C9', + "tscy;": '\U00000446', + "tshcy;": '\U0000045B', + "tstrok;": '\U00000167', + "twixt;": '\U0000226C', + "twoheadleftarrow;": '\U0000219E', + "twoheadrightarrow;": '\U000021A0', + "uArr;": '\U000021D1', + "uHar;": '\U00002963', + "uacute;": '\U000000FA', + "uarr;": '\U00002191', + "ubrcy;": '\U0000045E', + "ubreve;": '\U0000016D', + "ucirc;": '\U000000FB', + "ucy;": '\U00000443', + "udarr;": '\U000021C5', + "udblac;": '\U00000171', + "udhar;": '\U0000296E', + "ufisht;": '\U0000297E', + "ufr;": '\U0001D532', + "ugrave;": '\U000000F9', + "uharl;": '\U000021BF', + "uharr;": '\U000021BE', + "uhblk;": '\U00002580', + "ulcorn;": '\U0000231C', + "ulcorner;": '\U0000231C', + "ulcrop;": '\U0000230F', + "ultri;": '\U000025F8', + "umacr;": '\U0000016B', + "uml;": '\U000000A8', + "uogon;": '\U00000173', + "uopf;": '\U0001D566', + "uparrow;": '\U00002191', + "updownarrow;": '\U00002195', + "upharpoonleft;": '\U000021BF', + "upharpoonright;": '\U000021BE', + "uplus;": '\U0000228E', + "upsi;": '\U000003C5', + "upsih;": '\U000003D2', + "upsilon;": '\U000003C5', + "upuparrows;": '\U000021C8', + "urcorn;": '\U0000231D', + "urcorner;": '\U0000231D', + "urcrop;": '\U0000230E', + "uring;": '\U0000016F', + "urtri;": '\U000025F9', + "uscr;": '\U0001D4CA', + "utdot;": '\U000022F0', + "utilde;": '\U00000169', + "utri;": '\U000025B5', + "utrif;": '\U000025B4', + "uuarr;": '\U000021C8', + "uuml;": '\U000000FC', + "uwangle;": '\U000029A7', + "vArr;": '\U000021D5', + "vBar;": '\U00002AE8', + "vBarv;": '\U00002AE9', + "vDash;": '\U000022A8', + "vangrt;": '\U0000299C', + "varepsilon;": '\U000003F5', + "varkappa;": '\U000003F0', + "varnothing;": '\U00002205', + "varphi;": '\U000003D5', + "varpi;": '\U000003D6', + "varpropto;": '\U0000221D', + "varr;": '\U00002195', + "varrho;": '\U000003F1', + "varsigma;": '\U000003C2', + "vartheta;": '\U000003D1', + "vartriangleleft;": '\U000022B2', + "vartriangleright;": '\U000022B3', + "vcy;": '\U00000432', + "vdash;": '\U000022A2', + "vee;": '\U00002228', + "veebar;": '\U000022BB', + "veeeq;": '\U0000225A', + "vellip;": '\U000022EE', + "verbar;": '\U0000007C', + "vert;": '\U0000007C', + "vfr;": '\U0001D533', + "vltri;": '\U000022B2', + "vopf;": '\U0001D567', + "vprop;": '\U0000221D', + "vrtri;": '\U000022B3', + "vscr;": '\U0001D4CB', + "vzigzag;": '\U0000299A', + "wcirc;": '\U00000175', + "wedbar;": '\U00002A5F', + "wedge;": '\U00002227', + "wedgeq;": '\U00002259', + "weierp;": '\U00002118', + "wfr;": '\U0001D534', + "wopf;": '\U0001D568', + "wp;": '\U00002118', + "wr;": '\U00002240', + "wreath;": '\U00002240', + "wscr;": '\U0001D4CC', + "xcap;": '\U000022C2', + "xcirc;": '\U000025EF', + "xcup;": '\U000022C3', + "xdtri;": '\U000025BD', + "xfr;": '\U0001D535', + "xhArr;": '\U000027FA', + "xharr;": '\U000027F7', + "xi;": '\U000003BE', + "xlArr;": '\U000027F8', + "xlarr;": '\U000027F5', + "xmap;": '\U000027FC', + "xnis;": '\U000022FB', + "xodot;": '\U00002A00', + "xopf;": '\U0001D569', + "xoplus;": '\U00002A01', + "xotime;": '\U00002A02', + "xrArr;": '\U000027F9', + "xrarr;": '\U000027F6', + "xscr;": '\U0001D4CD', + "xsqcup;": '\U00002A06', + "xuplus;": '\U00002A04', + "xutri;": '\U000025B3', + "xvee;": '\U000022C1', + "xwedge;": '\U000022C0', + "yacute;": '\U000000FD', + "yacy;": '\U0000044F', + "ycirc;": '\U00000177', + "ycy;": '\U0000044B', + "yen;": '\U000000A5', + "yfr;": '\U0001D536', + "yicy;": '\U00000457', + "yopf;": '\U0001D56A', + "yscr;": '\U0001D4CE', + "yucy;": '\U0000044E', + "yuml;": '\U000000FF', + "zacute;": '\U0000017A', + "zcaron;": '\U0000017E', + "zcy;": '\U00000437', + "zdot;": '\U0000017C', + "zeetrf;": '\U00002128', + "zeta;": '\U000003B6', + "zfr;": '\U0001D537', + "zhcy;": '\U00000436', + "zigrarr;": '\U000021DD', + "zopf;": '\U0001D56B', + "zscr;": '\U0001D4CF', + "zwj;": '\U0000200D', + "zwnj;": '\U0000200C', + "AElig": '\U000000C6', + "AMP": '\U00000026', + "Aacute": '\U000000C1', + "Acirc": '\U000000C2', + "Agrave": '\U000000C0', + "Aring": '\U000000C5', + "Atilde": '\U000000C3', + "Auml": '\U000000C4', + "COPY": '\U000000A9', + "Ccedil": '\U000000C7', + "ETH": '\U000000D0', + "Eacute": '\U000000C9', + "Ecirc": '\U000000CA', + "Egrave": '\U000000C8', + "Euml": '\U000000CB', + "GT": '\U0000003E', + "Iacute": '\U000000CD', + "Icirc": '\U000000CE', + "Igrave": '\U000000CC', + "Iuml": '\U000000CF', + "LT": '\U0000003C', + "Ntilde": '\U000000D1', + "Oacute": '\U000000D3', + "Ocirc": '\U000000D4', + "Ograve": '\U000000D2', + "Oslash": '\U000000D8', + "Otilde": '\U000000D5', + "Ouml": '\U000000D6', + "QUOT": '\U00000022', + "REG": '\U000000AE', + "THORN": '\U000000DE', + "Uacute": '\U000000DA', + "Ucirc": '\U000000DB', + "Ugrave": '\U000000D9', + "Uuml": '\U000000DC', + "Yacute": '\U000000DD', + "aacute": '\U000000E1', + "acirc": '\U000000E2', + "acute": '\U000000B4', + "aelig": '\U000000E6', + "agrave": '\U000000E0', + "amp": '\U00000026', + "aring": '\U000000E5', + "atilde": '\U000000E3', + "auml": '\U000000E4', + "brvbar": '\U000000A6', + "ccedil": '\U000000E7', + "cedil": '\U000000B8', + "cent": '\U000000A2', + "copy": '\U000000A9', + "curren": '\U000000A4', + "deg": '\U000000B0', + "divide": '\U000000F7', + "eacute": '\U000000E9', + "ecirc": '\U000000EA', + "egrave": '\U000000E8', + "eth": '\U000000F0', + "euml": '\U000000EB', + "frac12": '\U000000BD', + "frac14": '\U000000BC', + "frac34": '\U000000BE', + "gt": '\U0000003E', + "iacute": '\U000000ED', + "icirc": '\U000000EE', + "iexcl": '\U000000A1', + "igrave": '\U000000EC', + "iquest": '\U000000BF', + "iuml": '\U000000EF', + "laquo": '\U000000AB', + "lt": '\U0000003C', + "macr": '\U000000AF', + "micro": '\U000000B5', + "middot": '\U000000B7', + "nbsp": '\U000000A0', + "not": '\U000000AC', + "ntilde": '\U000000F1', + "oacute": '\U000000F3', + "ocirc": '\U000000F4', + "ograve": '\U000000F2', + "ordf": '\U000000AA', + "ordm": '\U000000BA', + "oslash": '\U000000F8', + "otilde": '\U000000F5', + "ouml": '\U000000F6', + "para": '\U000000B6', + "plusmn": '\U000000B1', + "pound": '\U000000A3', + "quot": '\U00000022', + "raquo": '\U000000BB', + "reg": '\U000000AE', + "sect": '\U000000A7', + "shy": '\U000000AD', + "sup1": '\U000000B9', + "sup2": '\U000000B2', + "sup3": '\U000000B3', + "szlig": '\U000000DF', + "thorn": '\U000000FE', + "times": '\U000000D7', + "uacute": '\U000000FA', + "ucirc": '\U000000FB', + "ugrave": '\U000000F9', + "uml": '\U000000A8', + "uuml": '\U000000FC', + "yacute": '\U000000FD', + "yen": '\U000000A5', + "yuml": '\U000000FF', +} + +// HTML entities that are two unicode codepoints. +var entity2 = map[string][2]rune{ + // TODO(nigeltao): Handle replacements that are wider than their names. + // "nLt;": {'\u226A', '\u20D2'}, + // "nGt;": {'\u226B', '\u20D2'}, + "NotEqualTilde;": {'\u2242', '\u0338'}, + "NotGreaterFullEqual;": {'\u2267', '\u0338'}, + "NotGreaterGreater;": {'\u226B', '\u0338'}, + "NotGreaterSlantEqual;": {'\u2A7E', '\u0338'}, + "NotHumpDownHump;": {'\u224E', '\u0338'}, + "NotHumpEqual;": {'\u224F', '\u0338'}, + "NotLeftTriangleBar;": {'\u29CF', '\u0338'}, + "NotLessLess;": {'\u226A', '\u0338'}, + "NotLessSlantEqual;": {'\u2A7D', '\u0338'}, + "NotNestedGreaterGreater;": {'\u2AA2', '\u0338'}, + "NotNestedLessLess;": {'\u2AA1', '\u0338'}, + "NotPrecedesEqual;": {'\u2AAF', '\u0338'}, + "NotRightTriangleBar;": {'\u29D0', '\u0338'}, + "NotSquareSubset;": {'\u228F', '\u0338'}, + "NotSquareSuperset;": {'\u2290', '\u0338'}, + "NotSubset;": {'\u2282', '\u20D2'}, + "NotSucceedsEqual;": {'\u2AB0', '\u0338'}, + "NotSucceedsTilde;": {'\u227F', '\u0338'}, + "NotSuperset;": {'\u2283', '\u20D2'}, + "ThickSpace;": {'\u205F', '\u200A'}, + "acE;": {'\u223E', '\u0333'}, + "bne;": {'\u003D', '\u20E5'}, + "bnequiv;": {'\u2261', '\u20E5'}, + "caps;": {'\u2229', '\uFE00'}, + "cups;": {'\u222A', '\uFE00'}, + "fjlig;": {'\u0066', '\u006A'}, + "gesl;": {'\u22DB', '\uFE00'}, + "gvertneqq;": {'\u2269', '\uFE00'}, + "gvnE;": {'\u2269', '\uFE00'}, + "lates;": {'\u2AAD', '\uFE00'}, + "lesg;": {'\u22DA', '\uFE00'}, + "lvertneqq;": {'\u2268', '\uFE00'}, + "lvnE;": {'\u2268', '\uFE00'}, + "nGg;": {'\u22D9', '\u0338'}, + "nGtv;": {'\u226B', '\u0338'}, + "nLl;": {'\u22D8', '\u0338'}, + "nLtv;": {'\u226A', '\u0338'}, + "nang;": {'\u2220', '\u20D2'}, + "napE;": {'\u2A70', '\u0338'}, + "napid;": {'\u224B', '\u0338'}, + "nbump;": {'\u224E', '\u0338'}, + "nbumpe;": {'\u224F', '\u0338'}, + "ncongdot;": {'\u2A6D', '\u0338'}, + "nedot;": {'\u2250', '\u0338'}, + "nesim;": {'\u2242', '\u0338'}, + "ngE;": {'\u2267', '\u0338'}, + "ngeqq;": {'\u2267', '\u0338'}, + "ngeqslant;": {'\u2A7E', '\u0338'}, + "nges;": {'\u2A7E', '\u0338'}, + "nlE;": {'\u2266', '\u0338'}, + "nleqq;": {'\u2266', '\u0338'}, + "nleqslant;": {'\u2A7D', '\u0338'}, + "nles;": {'\u2A7D', '\u0338'}, + "notinE;": {'\u22F9', '\u0338'}, + "notindot;": {'\u22F5', '\u0338'}, + "nparsl;": {'\u2AFD', '\u20E5'}, + "npart;": {'\u2202', '\u0338'}, + "npre;": {'\u2AAF', '\u0338'}, + "npreceq;": {'\u2AAF', '\u0338'}, + "nrarrc;": {'\u2933', '\u0338'}, + "nrarrw;": {'\u219D', '\u0338'}, + "nsce;": {'\u2AB0', '\u0338'}, + "nsubE;": {'\u2AC5', '\u0338'}, + "nsubset;": {'\u2282', '\u20D2'}, + "nsubseteqq;": {'\u2AC5', '\u0338'}, + "nsucceq;": {'\u2AB0', '\u0338'}, + "nsupE;": {'\u2AC6', '\u0338'}, + "nsupset;": {'\u2283', '\u20D2'}, + "nsupseteqq;": {'\u2AC6', '\u0338'}, + "nvap;": {'\u224D', '\u20D2'}, + "nvge;": {'\u2265', '\u20D2'}, + "nvgt;": {'\u003E', '\u20D2'}, + "nvle;": {'\u2264', '\u20D2'}, + "nvlt;": {'\u003C', '\u20D2'}, + "nvltrie;": {'\u22B4', '\u20D2'}, + "nvrtrie;": {'\u22B5', '\u20D2'}, + "nvsim;": {'\u223C', '\u20D2'}, + "race;": {'\u223D', '\u0331'}, + "smtes;": {'\u2AAC', '\uFE00'}, + "sqcaps;": {'\u2293', '\uFE00'}, + "sqcups;": {'\u2294', '\uFE00'}, + "varsubsetneq;": {'\u228A', '\uFE00'}, + "varsubsetneqq;": {'\u2ACB', '\uFE00'}, + "varsupsetneq;": {'\u228B', '\uFE00'}, + "varsupsetneqq;": {'\u2ACC', '\uFE00'}, + "vnsub;": {'\u2282', '\u20D2'}, + "vnsup;": {'\u2283', '\u20D2'}, + "vsubnE;": {'\u2ACB', '\uFE00'}, + "vsubne;": {'\u228A', '\uFE00'}, + "vsupnE;": {'\u2ACC', '\uFE00'}, + "vsupne;": {'\u228B', '\uFE00'}, +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/entity_test.go b/Godeps/_workspace/src/golang.org/x/net/html/entity_test.go new file mode 100644 index 0000000..b53f866 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/entity_test.go @@ -0,0 +1,29 @@ +// Copyright 2010 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 html + +import ( + "testing" + "unicode/utf8" +) + +func TestEntityLength(t *testing.T) { + // We verify that the length of UTF-8 encoding of each value is <= 1 + len(key). + // The +1 comes from the leading "&". This property implies that the length of + // unescaped text is <= the length of escaped text. + for k, v := range entity { + if 1+len(k) < utf8.RuneLen(v) { + t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v)) + } + if len(k) > longestEntityWithoutSemicolon && k[len(k)-1] != ';' { + t.Errorf("entity name %s is %d characters, but longestEntityWithoutSemicolon=%d", k, len(k), longestEntityWithoutSemicolon) + } + } + for k, v := range entity2 { + if 1+len(k) < utf8.RuneLen(v[0])+utf8.RuneLen(v[1]) { + t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v[0]) + string(v[1])) + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/escape.go b/Godeps/_workspace/src/golang.org/x/net/html/escape.go new file mode 100644 index 0000000..d856139 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/escape.go @@ -0,0 +1,258 @@ +// Copyright 2010 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 html + +import ( + "bytes" + "strings" + "unicode/utf8" +) + +// These replacements permit compatibility with old numeric entities that +// assumed Windows-1252 encoding. +// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference +var replacementTable = [...]rune{ + '\u20AC', // First entry is what 0x80 should be replaced with. + '\u0081', + '\u201A', + '\u0192', + '\u201E', + '\u2026', + '\u2020', + '\u2021', + '\u02C6', + '\u2030', + '\u0160', + '\u2039', + '\u0152', + '\u008D', + '\u017D', + '\u008F', + '\u0090', + '\u2018', + '\u2019', + '\u201C', + '\u201D', + '\u2022', + '\u2013', + '\u2014', + '\u02DC', + '\u2122', + '\u0161', + '\u203A', + '\u0153', + '\u009D', + '\u017E', + '\u0178', // Last entry is 0x9F. + // 0x00->'\uFFFD' is handled programmatically. + // 0x0D->'\u000D' is a no-op. +} + +// unescapeEntity reads an entity like "<" from b[src:] and writes the +// corresponding "<" to b[dst:], returning the incremented dst and src cursors. +// Precondition: b[src] == '&' && dst <= src. +// attribute should be true if parsing an attribute value. +func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) { + // https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference + + // i starts at 1 because we already know that s[0] == '&'. + i, s := 1, b[src:] + + if len(s) <= 1 { + b[dst] = b[src] + return dst + 1, src + 1 + } + + if s[i] == '#' { + if len(s) <= 3 { // We need to have at least "&#.". + b[dst] = b[src] + return dst + 1, src + 1 + } + i++ + c := s[i] + hex := false + if c == 'x' || c == 'X' { + hex = true + i++ + } + + x := '\x00' + for i < len(s) { + c = s[i] + i++ + if hex { + if '0' <= c && c <= '9' { + x = 16*x + rune(c) - '0' + continue + } else if 'a' <= c && c <= 'f' { + x = 16*x + rune(c) - 'a' + 10 + continue + } else if 'A' <= c && c <= 'F' { + x = 16*x + rune(c) - 'A' + 10 + continue + } + } else if '0' <= c && c <= '9' { + x = 10*x + rune(c) - '0' + continue + } + if c != ';' { + i-- + } + break + } + + if i <= 3 { // No characters matched. + b[dst] = b[src] + return dst + 1, src + 1 + } + + if 0x80 <= x && x <= 0x9F { + // Replace characters from Windows-1252 with UTF-8 equivalents. + x = replacementTable[x-0x80] + } else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF { + // Replace invalid characters with the replacement character. + x = '\uFFFD' + } + + return dst + utf8.EncodeRune(b[dst:], x), src + i + } + + // Consume the maximum number of characters possible, with the + // consumed characters matching one of the named references. + + for i < len(s) { + c := s[i] + i++ + // Lower-cased characters are more common in entities, so we check for them first. + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { + continue + } + if c != ';' { + i-- + } + break + } + + entityName := string(s[1:i]) + if entityName == "" { + // No-op. + } else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' { + // No-op. + } else if x := entity[entityName]; x != 0 { + return dst + utf8.EncodeRune(b[dst:], x), src + i + } else if x := entity2[entityName]; x[0] != 0 { + dst1 := dst + utf8.EncodeRune(b[dst:], x[0]) + return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i + } else if !attribute { + maxLen := len(entityName) - 1 + if maxLen > longestEntityWithoutSemicolon { + maxLen = longestEntityWithoutSemicolon + } + for j := maxLen; j > 1; j-- { + if x := entity[entityName[:j]]; x != 0 { + return dst + utf8.EncodeRune(b[dst:], x), src + j + 1 + } + } + } + + dst1, src1 = dst+i, src+i + copy(b[dst:dst1], b[src:src1]) + return dst1, src1 +} + +// unescape unescapes b's entities in-place, so that "a<b" becomes "a': + esc = ">" + case '"': + // """ is shorter than """. + esc = """ + case '\r': + esc = " " + default: + panic("unrecognized escape character") + } + s = s[i+1:] + if _, err := w.WriteString(esc); err != nil { + return err + } + i = strings.IndexAny(s, escapedChars) + } + _, err := w.WriteString(s) + return err +} + +// EscapeString escapes special characters like "<" to become "<". It +// escapes only five such characters: <, >, &, ' and ". +// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't +// always true. +func EscapeString(s string) string { + if strings.IndexAny(s, escapedChars) == -1 { + return s + } + var buf bytes.Buffer + escape(&buf, s) + return buf.String() +} + +// UnescapeString unescapes entities like "<" to become "<". It unescapes a +// larger range of entities than EscapeString escapes. For example, "á" +// unescapes to "á", as does "á" and "&xE1;". +// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't +// always true. +func UnescapeString(s string) string { + for _, c := range s { + if c == '&' { + return string(unescape([]byte(s), false)) + } + } + return s +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/escape_test.go b/Godeps/_workspace/src/golang.org/x/net/html/escape_test.go new file mode 100644 index 0000000..b405d4b --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/escape_test.go @@ -0,0 +1,97 @@ +// 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 html + +import "testing" + +type unescapeTest struct { + // A short description of the test case. + desc string + // The HTML text. + html string + // The unescaped text. + unescaped string +} + +var unescapeTests = []unescapeTest{ + // Handle no entities. + { + "copy", + "A\ttext\nstring", + "A\ttext\nstring", + }, + // Handle simple named entities. + { + "simple", + "& > <", + "& > <", + }, + // Handle hitting the end of the string. + { + "stringEnd", + "& &", + "& &", + }, + // Handle entities with two codepoints. + { + "multiCodepoint", + "text ⋛︀ blah", + "text \u22db\ufe00 blah", + }, + // Handle decimal numeric entities. + { + "decimalEntity", + "Delta = Δ ", + "Delta = Δ ", + }, + // Handle hexadecimal numeric entities. + { + "hexadecimalEntity", + "Lambda = λ = λ ", + "Lambda = λ = λ ", + }, + // Handle numeric early termination. + { + "numericEnds", + "&# &#x €43 © = ©f = ©", + "&# &#x €43 © = ©f = ©", + }, + // Handle numeric ISO-8859-1 entity replacements. + { + "numericReplacements", + "Footnote‡", + "Footnote‡", + }, +} + +func TestUnescape(t *testing.T) { + for _, tt := range unescapeTests { + unescaped := UnescapeString(tt.html) + if unescaped != tt.unescaped { + t.Errorf("TestUnescape %s: want %q, got %q", tt.desc, tt.unescaped, unescaped) + } + } +} + +func TestUnescapeEscape(t *testing.T) { + ss := []string{ + ``, + `abc def`, + `a & b`, + `a&b`, + `a & b`, + `"`, + `"`, + `"<&>"`, + `"<&>"`, + `3&5==1 && 0<1, "0<1", a+acute=á`, + `The special characters are: <, >, &, ' and "`, + } + for _, s := range ss { + if got := UnescapeString(EscapeString(s)); got != s { + t.Errorf("got %q want %q", got, s) + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/example_test.go b/Godeps/_workspace/src/golang.org/x/net/html/example_test.go new file mode 100644 index 0000000..90c486c --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/example_test.go @@ -0,0 +1,40 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This example demonstrates parsing HTML data and walking the resulting tree. +package html_test + +import ( + "fmt" + "log" + "strings" + + "github.com/appc/acpush/Godeps/_workspace/src/golang.org/x/net/html" +) + +func ExampleParse() { + s := `

Links:

` + doc, err := html.Parse(strings.NewReader(s)) + if err != nil { + log.Fatal(err) + } + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + for _, a := range n.Attr { + if a.Key == "href" { + fmt.Println(a.Val) + break + } + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + // Output: + // foo + // /bar/baz +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/foreign.go b/Godeps/_workspace/src/golang.org/x/net/html/foreign.go new file mode 100644 index 0000000..d3b3844 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/foreign.go @@ -0,0 +1,226 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "strings" +) + +func adjustAttributeNames(aa []Attribute, nameMap map[string]string) { + for i := range aa { + if newName, ok := nameMap[aa[i].Key]; ok { + aa[i].Key = newName + } + } +} + +func adjustForeignAttributes(aa []Attribute) { + for i, a := range aa { + if a.Key == "" || a.Key[0] != 'x' { + continue + } + switch a.Key { + case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", + "xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink": + j := strings.Index(a.Key, ":") + aa[i].Namespace = a.Key[:j] + aa[i].Key = a.Key[j+1:] + } + } +} + +func htmlIntegrationPoint(n *Node) bool { + if n.Type != ElementNode { + return false + } + switch n.Namespace { + case "math": + if n.Data == "annotation-xml" { + for _, a := range n.Attr { + if a.Key == "encoding" { + val := strings.ToLower(a.Val) + if val == "text/html" || val == "application/xhtml+xml" { + return true + } + } + } + } + case "svg": + switch n.Data { + case "desc", "foreignObject", "title": + return true + } + } + return false +} + +func mathMLTextIntegrationPoint(n *Node) bool { + if n.Namespace != "math" { + return false + } + switch n.Data { + case "mi", "mo", "mn", "ms", "mtext": + return true + } + return false +} + +// Section 12.2.5.5. +var breakout = map[string]bool{ + "b": true, + "big": true, + "blockquote": true, + "body": true, + "br": true, + "center": true, + "code": true, + "dd": true, + "div": true, + "dl": true, + "dt": true, + "em": true, + "embed": true, + "h1": true, + "h2": true, + "h3": true, + "h4": true, + "h5": true, + "h6": true, + "head": true, + "hr": true, + "i": true, + "img": true, + "li": true, + "listing": true, + "menu": true, + "meta": true, + "nobr": true, + "ol": true, + "p": true, + "pre": true, + "ruby": true, + "s": true, + "small": true, + "span": true, + "strong": true, + "strike": true, + "sub": true, + "sup": true, + "table": true, + "tt": true, + "u": true, + "ul": true, + "var": true, +} + +// Section 12.2.5.5. +var svgTagNameAdjustments = map[string]string{ + "altglyph": "altGlyph", + "altglyphdef": "altGlyphDef", + "altglyphitem": "altGlyphItem", + "animatecolor": "animateColor", + "animatemotion": "animateMotion", + "animatetransform": "animateTransform", + "clippath": "clipPath", + "feblend": "feBlend", + "fecolormatrix": "feColorMatrix", + "fecomponenttransfer": "feComponentTransfer", + "fecomposite": "feComposite", + "feconvolvematrix": "feConvolveMatrix", + "fediffuselighting": "feDiffuseLighting", + "fedisplacementmap": "feDisplacementMap", + "fedistantlight": "feDistantLight", + "feflood": "feFlood", + "fefunca": "feFuncA", + "fefuncb": "feFuncB", + "fefuncg": "feFuncG", + "fefuncr": "feFuncR", + "fegaussianblur": "feGaussianBlur", + "feimage": "feImage", + "femerge": "feMerge", + "femergenode": "feMergeNode", + "femorphology": "feMorphology", + "feoffset": "feOffset", + "fepointlight": "fePointLight", + "fespecularlighting": "feSpecularLighting", + "fespotlight": "feSpotLight", + "fetile": "feTile", + "feturbulence": "feTurbulence", + "foreignobject": "foreignObject", + "glyphref": "glyphRef", + "lineargradient": "linearGradient", + "radialgradient": "radialGradient", + "textpath": "textPath", +} + +// Section 12.2.5.1 +var mathMLAttributeAdjustments = map[string]string{ + "definitionurl": "definitionURL", +} + +var svgAttributeAdjustments = map[string]string{ + "attributename": "attributeName", + "attributetype": "attributeType", + "basefrequency": "baseFrequency", + "baseprofile": "baseProfile", + "calcmode": "calcMode", + "clippathunits": "clipPathUnits", + "contentscripttype": "contentScriptType", + "contentstyletype": "contentStyleType", + "diffuseconstant": "diffuseConstant", + "edgemode": "edgeMode", + "externalresourcesrequired": "externalResourcesRequired", + "filterres": "filterRes", + "filterunits": "filterUnits", + "glyphref": "glyphRef", + "gradienttransform": "gradientTransform", + "gradientunits": "gradientUnits", + "kernelmatrix": "kernelMatrix", + "kernelunitlength": "kernelUnitLength", + "keypoints": "keyPoints", + "keysplines": "keySplines", + "keytimes": "keyTimes", + "lengthadjust": "lengthAdjust", + "limitingconeangle": "limitingConeAngle", + "markerheight": "markerHeight", + "markerunits": "markerUnits", + "markerwidth": "markerWidth", + "maskcontentunits": "maskContentUnits", + "maskunits": "maskUnits", + "numoctaves": "numOctaves", + "pathlength": "pathLength", + "patterncontentunits": "patternContentUnits", + "patterntransform": "patternTransform", + "patternunits": "patternUnits", + "pointsatx": "pointsAtX", + "pointsaty": "pointsAtY", + "pointsatz": "pointsAtZ", + "preservealpha": "preserveAlpha", + "preserveaspectratio": "preserveAspectRatio", + "primitiveunits": "primitiveUnits", + "refx": "refX", + "refy": "refY", + "repeatcount": "repeatCount", + "repeatdur": "repeatDur", + "requiredextensions": "requiredExtensions", + "requiredfeatures": "requiredFeatures", + "specularconstant": "specularConstant", + "specularexponent": "specularExponent", + "spreadmethod": "spreadMethod", + "startoffset": "startOffset", + "stddeviation": "stdDeviation", + "stitchtiles": "stitchTiles", + "surfacescale": "surfaceScale", + "systemlanguage": "systemLanguage", + "tablevalues": "tableValues", + "targetx": "targetX", + "targety": "targetY", + "textlength": "textLength", + "viewbox": "viewBox", + "viewtarget": "viewTarget", + "xchannelselector": "xChannelSelector", + "ychannelselector": "yChannelSelector", + "zoomandpan": "zoomAndPan", +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/node.go b/Godeps/_workspace/src/golang.org/x/net/html/node.go new file mode 100644 index 0000000..6d2eec5 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/node.go @@ -0,0 +1,193 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "github.com/appc/acpush/Godeps/_workspace/src/golang.org/x/net/html/atom" +) + +// A NodeType is the type of a Node. +type NodeType uint32 + +const ( + ErrorNode NodeType = iota + TextNode + DocumentNode + ElementNode + CommentNode + DoctypeNode + scopeMarkerNode +) + +// Section 12.2.3.3 says "scope markers are inserted when entering applet +// elements, buttons, object elements, marquees, table cells, and table +// captions, and are used to prevent formatting from 'leaking'". +var scopeMarker = Node{Type: scopeMarkerNode} + +// A Node consists of a NodeType and some Data (tag name for element nodes, +// content for text) and are part of a tree of Nodes. Element nodes may also +// have a Namespace and contain a slice of Attributes. Data is unescaped, so +// that it looks like "a 0 { + return (*s)[i-1] + } + return nil +} + +// index returns the index of the top-most occurrence of n in the stack, or -1 +// if n is not present. +func (s *nodeStack) index(n *Node) int { + for i := len(*s) - 1; i >= 0; i-- { + if (*s)[i] == n { + return i + } + } + return -1 +} + +// insert inserts a node at the given index. +func (s *nodeStack) insert(i int, n *Node) { + (*s) = append(*s, nil) + copy((*s)[i+1:], (*s)[i:]) + (*s)[i] = n +} + +// remove removes a node from the stack. It is a no-op if n is not present. +func (s *nodeStack) remove(n *Node) { + i := s.index(n) + if i == -1 { + return + } + copy((*s)[i:], (*s)[i+1:]) + j := len(*s) - 1 + (*s)[j] = nil + *s = (*s)[:j] +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/node_test.go b/Godeps/_workspace/src/golang.org/x/net/html/node_test.go new file mode 100644 index 0000000..471102f --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/node_test.go @@ -0,0 +1,146 @@ +// Copyright 2010 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 html + +import ( + "fmt" +) + +// checkTreeConsistency checks that a node and its descendants are all +// consistent in their parent/child/sibling relationships. +func checkTreeConsistency(n *Node) error { + return checkTreeConsistency1(n, 0) +} + +func checkTreeConsistency1(n *Node, depth int) error { + if depth == 1e4 { + return fmt.Errorf("html: tree looks like it contains a cycle") + } + if err := checkNodeConsistency(n); err != nil { + return err + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + if err := checkTreeConsistency1(c, depth+1); err != nil { + return err + } + } + return nil +} + +// checkNodeConsistency checks that a node's parent/child/sibling relationships +// are consistent. +func checkNodeConsistency(n *Node) error { + if n == nil { + return nil + } + + nParent := 0 + for p := n.Parent; p != nil; p = p.Parent { + nParent++ + if nParent == 1e4 { + return fmt.Errorf("html: parent list looks like an infinite loop") + } + } + + nForward := 0 + for c := n.FirstChild; c != nil; c = c.NextSibling { + nForward++ + if nForward == 1e6 { + return fmt.Errorf("html: forward list of children looks like an infinite loop") + } + if c.Parent != n { + return fmt.Errorf("html: inconsistent child/parent relationship") + } + } + + nBackward := 0 + for c := n.LastChild; c != nil; c = c.PrevSibling { + nBackward++ + if nBackward == 1e6 { + return fmt.Errorf("html: backward list of children looks like an infinite loop") + } + if c.Parent != n { + return fmt.Errorf("html: inconsistent child/parent relationship") + } + } + + if n.Parent != nil { + if n.Parent == n { + return fmt.Errorf("html: inconsistent parent relationship") + } + if n.Parent == n.FirstChild { + return fmt.Errorf("html: inconsistent parent/first relationship") + } + if n.Parent == n.LastChild { + return fmt.Errorf("html: inconsistent parent/last relationship") + } + if n.Parent == n.PrevSibling { + return fmt.Errorf("html: inconsistent parent/prev relationship") + } + if n.Parent == n.NextSibling { + return fmt.Errorf("html: inconsistent parent/next relationship") + } + + parentHasNAsAChild := false + for c := n.Parent.FirstChild; c != nil; c = c.NextSibling { + if c == n { + parentHasNAsAChild = true + break + } + } + if !parentHasNAsAChild { + return fmt.Errorf("html: inconsistent parent/child relationship") + } + } + + if n.PrevSibling != nil && n.PrevSibling.NextSibling != n { + return fmt.Errorf("html: inconsistent prev/next relationship") + } + if n.NextSibling != nil && n.NextSibling.PrevSibling != n { + return fmt.Errorf("html: inconsistent next/prev relationship") + } + + if (n.FirstChild == nil) != (n.LastChild == nil) { + return fmt.Errorf("html: inconsistent first/last relationship") + } + if n.FirstChild != nil && n.FirstChild == n.LastChild { + // We have a sole child. + if n.FirstChild.PrevSibling != nil || n.FirstChild.NextSibling != nil { + return fmt.Errorf("html: inconsistent sole child's sibling relationship") + } + } + + seen := map[*Node]bool{} + + var last *Node + for c := n.FirstChild; c != nil; c = c.NextSibling { + if seen[c] { + return fmt.Errorf("html: inconsistent repeated child") + } + seen[c] = true + last = c + } + if last != n.LastChild { + return fmt.Errorf("html: inconsistent last relationship") + } + + var first *Node + for c := n.LastChild; c != nil; c = c.PrevSibling { + if !seen[c] { + return fmt.Errorf("html: inconsistent missing child") + } + delete(seen, c) + first = c + } + if first != n.FirstChild { + return fmt.Errorf("html: inconsistent first relationship") + } + + if len(seen) != 0 { + return fmt.Errorf("html: inconsistent forwards/backwards child list") + } + + return nil +} diff --git a/Godeps/_workspace/src/golang.org/x/net/html/parse.go b/Godeps/_workspace/src/golang.org/x/net/html/parse.go new file mode 100644 index 0000000..5555df3 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/parse.go @@ -0,0 +1,2094 @@ +// Copyright 2010 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 html + +import ( + "errors" + "fmt" + "io" + "strings" + + a "github.com/appc/acpush/Godeps/_workspace/src/golang.org/x/net/html/atom" +) + +// A parser implements the HTML5 parsing algorithm: +// https://html.spec.whatwg.org/multipage/syntax.html#tree-construction +type parser struct { + // tokenizer provides the tokens for the parser. + tokenizer *Tokenizer + // tok is the most recently read token. + tok Token + // Self-closing tags like
are treated as start tags, except that + // hasSelfClosingToken is set while they are being processed. + hasSelfClosingToken bool + // doc is the document root element. + doc *Node + // The stack of open elements (section 12.2.3.2) and active formatting + // elements (section 12.2.3.3). + oe, afe nodeStack + // Element pointers (section 12.2.3.4). + head, form *Node + // Other parsing state flags (section 12.2.3.5). + scripting, framesetOK bool + // im is the current insertion mode. + im insertionMode + // originalIM is the insertion mode to go back to after completing a text + // or inTableText insertion mode. + originalIM insertionMode + // fosterParenting is whether new elements should be inserted according to + // the foster parenting rules (section 12.2.5.3). + fosterParenting bool + // quirks is whether the parser is operating in "quirks mode." + quirks bool + // fragment is whether the parser is parsing an HTML fragment. + fragment bool + // context is the context element when parsing an HTML fragment + // (section 12.4). + context *Node +} + +func (p *parser) top() *Node { + if n := p.oe.top(); n != nil { + return n + } + return p.doc +} + +// Stop tags for use in popUntil. These come from section 12.2.3.2. +var ( + defaultScopeStopTags = map[string][]a.Atom{ + "": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template}, + "math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext}, + "svg": {a.Desc, a.ForeignObject, a.Title}, + } +) + +type scope int + +const ( + defaultScope scope = iota + listItemScope + buttonScope + tableScope + tableRowScope + tableBodyScope + selectScope +) + +// popUntil pops the stack of open elements at the highest element whose tag +// is in matchTags, provided there is no higher element in the scope's stop +// tags (as defined in section 12.2.3.2). It returns whether or not there was +// such an element. If there was not, popUntil leaves the stack unchanged. +// +// For example, the set of stop tags for table scope is: "html", "table". If +// the stack was: +// ["html", "body", "font", "table", "b", "i", "u"] +// then popUntil(tableScope, "font") would return false, but +// popUntil(tableScope, "i") would return true and the stack would become: +// ["html", "body", "font", "table", "b"] +// +// If an element's tag is in both the stop tags and matchTags, then the stack +// will be popped and the function returns true (provided, of course, there was +// no higher element in the stack that was also in the stop tags). For example, +// popUntil(tableScope, "table") returns true and leaves: +// ["html", "body", "font"] +func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool { + if i := p.indexOfElementInScope(s, matchTags...); i != -1 { + p.oe = p.oe[:i] + return true + } + return false +} + +// indexOfElementInScope returns the index in p.oe of the highest element whose +// tag is in matchTags that is in scope. If no matching element is in scope, it +// returns -1. +func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + if p.oe[i].Namespace == "" { + for _, t := range matchTags { + if t == tagAtom { + return i + } + } + switch s { + case defaultScope: + // No-op. + case listItemScope: + if tagAtom == a.Ol || tagAtom == a.Ul { + return -1 + } + case buttonScope: + if tagAtom == a.Button { + return -1 + } + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + return -1 + } + case selectScope: + if tagAtom != a.Optgroup && tagAtom != a.Option { + return -1 + } + default: + panic("unreachable") + } + } + switch s { + case defaultScope, listItemScope, buttonScope: + for _, t := range defaultScopeStopTags[p.oe[i].Namespace] { + if t == tagAtom { + return -1 + } + } + } + } + return -1 +} + +// elementInScope is like popUntil, except that it doesn't modify the stack of +// open elements. +func (p *parser) elementInScope(s scope, matchTags ...a.Atom) bool { + return p.indexOfElementInScope(s, matchTags...) != -1 +} + +// clearStackToContext pops elements off the stack of open elements until a +// scope-defined element is found. +func (p *parser) clearStackToContext(s scope) { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + switch s { + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + p.oe = p.oe[:i+1] + return + } + case tableRowScope: + if tagAtom == a.Html || tagAtom == a.Tr { + p.oe = p.oe[:i+1] + return + } + case tableBodyScope: + if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead { + p.oe = p.oe[:i+1] + return + } + default: + panic("unreachable") + } + } +} + +// generateImpliedEndTags pops nodes off the stack of open elements as long as +// the top node has a tag name of dd, dt, li, option, optgroup, p, rp, or rt. +// If exceptions are specified, nodes with that name will not be popped off. +func (p *parser) generateImpliedEndTags(exceptions ...string) { + var i int +loop: + for i = len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if n.Type == ElementNode { + switch n.DataAtom { + case a.Dd, a.Dt, a.Li, a.Option, a.Optgroup, a.P, a.Rp, a.Rt: + for _, except := range exceptions { + if n.Data == except { + break loop + } + } + continue + } + } + break + } + + p.oe = p.oe[:i+1] +} + +// addChild adds a child node n to the top element, and pushes n onto the stack +// of open elements if it is an element node. +func (p *parser) addChild(n *Node) { + if p.shouldFosterParent() { + p.fosterParent(n) + } else { + p.top().AppendChild(n) + } + + if n.Type == ElementNode { + p.oe = append(p.oe, n) + } +} + +// shouldFosterParent returns whether the next node to be added should be +// foster parented. +func (p *parser) shouldFosterParent() bool { + if p.fosterParenting { + switch p.top().DataAtom { + case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: + return true + } + } + return false +} + +// fosterParent adds a child node according to the foster parenting rules. +// Section 12.2.5.3, "foster parenting". +func (p *parser) fosterParent(n *Node) { + var table, parent, prev *Node + var i int + for i = len(p.oe) - 1; i >= 0; i-- { + if p.oe[i].DataAtom == a.Table { + table = p.oe[i] + break + } + } + + if table == nil { + // The foster parent is the html element. + parent = p.oe[0] + } else { + parent = table.Parent + } + if parent == nil { + parent = p.oe[i-1] + } + + if table != nil { + prev = table.PrevSibling + } else { + prev = parent.LastChild + } + if prev != nil && prev.Type == TextNode && n.Type == TextNode { + prev.Data += n.Data + return + } + + parent.InsertBefore(n, table) +} + +// addText adds text to the preceding node if it is a text node, or else it +// calls addChild with a new text node. +func (p *parser) addText(text string) { + if text == "" { + return + } + + if p.shouldFosterParent() { + p.fosterParent(&Node{ + Type: TextNode, + Data: text, + }) + return + } + + t := p.top() + if n := t.LastChild; n != nil && n.Type == TextNode { + n.Data += text + return + } + p.addChild(&Node{ + Type: TextNode, + Data: text, + }) +} + +// addElement adds a child element based on the current token. +func (p *parser) addElement() { + p.addChild(&Node{ + Type: ElementNode, + DataAtom: p.tok.DataAtom, + Data: p.tok.Data, + Attr: p.tok.Attr, + }) +} + +// Section 12.2.3.3. +func (p *parser) addFormattingElement() { + tagAtom, attr := p.tok.DataAtom, p.tok.Attr + p.addElement() + + // Implement the Noah's Ark clause, but with three per family instead of two. + identicalElements := 0 +findIdenticalElements: + for i := len(p.afe) - 1; i >= 0; i-- { + n := p.afe[i] + if n.Type == scopeMarkerNode { + break + } + if n.Type != ElementNode { + continue + } + if n.Namespace != "" { + continue + } + if n.DataAtom != tagAtom { + continue + } + if len(n.Attr) != len(attr) { + continue + } + compareAttributes: + for _, t0 := range n.Attr { + for _, t1 := range attr { + if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val { + // Found a match for this attribute, continue with the next attribute. + continue compareAttributes + } + } + // If we get here, there is no attribute that matches a. + // Therefore the element is not identical to the new one. + continue findIdenticalElements + } + + identicalElements++ + if identicalElements >= 3 { + p.afe.remove(n) + } + } + + p.afe = append(p.afe, p.top()) +} + +// Section 12.2.3.3. +func (p *parser) clearActiveFormattingElements() { + for { + n := p.afe.pop() + if len(p.afe) == 0 || n.Type == scopeMarkerNode { + return + } + } +} + +// Section 12.2.3.3. +func (p *parser) reconstructActiveFormattingElements() { + n := p.afe.top() + if n == nil { + return + } + if n.Type == scopeMarkerNode || p.oe.index(n) != -1 { + return + } + i := len(p.afe) - 1 + for n.Type != scopeMarkerNode && p.oe.index(n) == -1 { + if i == 0 { + i = -1 + break + } + i-- + n = p.afe[i] + } + for { + i++ + clone := p.afe[i].clone() + p.addChild(clone) + p.afe[i] = clone + if i == len(p.afe)-1 { + break + } + } +} + +// Section 12.2.4. +func (p *parser) acknowledgeSelfClosingTag() { + p.hasSelfClosingToken = false +} + +// An insertion mode (section 12.2.3.1) is the state transition function from +// a particular state in the HTML5 parser's state machine. It updates the +// parser's fields depending on parser.tok (where ErrorToken means EOF). +// It returns whether the token was consumed. +type insertionMode func(*parser) bool + +// setOriginalIM sets the insertion mode to return to after completing a text or +// inTableText insertion mode. +// Section 12.2.3.1, "using the rules for". +func (p *parser) setOriginalIM() { + if p.originalIM != nil { + panic("html: bad parser state: originalIM was set twice") + } + p.originalIM = p.im +} + +// Section 12.2.3.1, "reset the insertion mode". +func (p *parser) resetInsertionMode() { + for i := len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if i == 0 && p.context != nil { + n = p.context + } + + switch n.DataAtom { + case a.Select: + p.im = inSelectIM + case a.Td, a.Th: + p.im = inCellIM + case a.Tr: + p.im = inRowIM + case a.Tbody, a.Thead, a.Tfoot: + p.im = inTableBodyIM + case a.Caption: + p.im = inCaptionIM + case a.Colgroup: + p.im = inColumnGroupIM + case a.Table: + p.im = inTableIM + case a.Head: + p.im = inBodyIM + case a.Body: + p.im = inBodyIM + case a.Frameset: + p.im = inFramesetIM + case a.Html: + p.im = beforeHeadIM + default: + continue + } + return + } + p.im = inBodyIM +} + +const whitespace = " \t\r\n\f" + +// Section 12.2.5.4.1. +func initialIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + n, quirks := parseDoctype(p.tok.Data) + p.doc.AppendChild(n) + p.quirks = quirks + p.im = beforeHTMLIM + return true + } + p.quirks = true + p.im = beforeHTMLIM + return false +} + +// Section 12.2.5.4.2. +func beforeHTMLIM(p *parser) bool { + switch p.tok.Type { + case DoctypeToken: + // Ignore the token. + return true + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + if p.tok.DataAtom == a.Html { + p.addElement() + p.im = beforeHeadIM + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + } + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false +} + +// Section 12.2.5.4.3. +func beforeHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Head: + p.addElement() + p.head = p.top() + p.im = inHeadIM + return true + case a.Html: + return inBodyIM(p) + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.4. +func inHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta: + p.addElement() + p.oe.pop() + p.acknowledgeSelfClosingTag() + return true + case a.Script, a.Title, a.Noscript, a.Noframes, a.Style: + p.addElement() + p.setOriginalIM() + p.im = textIM + return true + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head: + n := p.oe.pop() + if n.DataAtom != a.Head { + panic("html: bad parser state: element not found, in the in-head insertion mode") + } + p.im = afterHeadIM + return true + case a.Body, a.Html, a.Br: + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.6. +func afterHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Body: + p.addElement() + p.framesetOK = false + p.im = inBodyIM + return true + case a.Frameset: + p.addElement() + p.im = inFramesetIM + return true + case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title: + p.oe = append(p.oe, p.head) + defer p.oe.remove(p.head) + return inHeadIM(p) + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Body, a.Html, a.Br: + // Drop down to creating an implied tag. + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) + p.framesetOK = true + return false +} + +// copyAttributes copies attributes of src not found on dst to dst. +func copyAttributes(dst *Node, src Token) { + if len(src.Attr) == 0 { + return + } + attr := map[string]string{} + for _, t := range dst.Attr { + attr[t.Key] = t.Val + } + for _, t := range src.Attr { + if _, ok := attr[t.Key]; !ok { + dst.Attr = append(dst.Attr, t) + attr[t.Key] = t.Val + } + } +} + +// Section 12.2.5.4.7. +func inBodyIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + d := p.tok.Data + switch n := p.oe.top(); n.DataAtom { + case a.Pre, a.Listing: + if n.FirstChild == nil { + // Ignore a newline at the start of a
 block.
+				if d != "" && d[0] == '\r' {
+					d = d[1:]
+				}
+				if d != "" && d[0] == '\n' {
+					d = d[1:]
+				}
+			}
+		}
+		d = strings.Replace(d, "\x00", "", -1)
+		if d == "" {
+			return true
+		}
+		p.reconstructActiveFormattingElements()
+		p.addText(d)
+		if p.framesetOK && strings.TrimLeft(d, whitespace) != "" {
+			// There were non-whitespace characters inserted.
+			p.framesetOK = false
+		}
+	case StartTagToken:
+		switch p.tok.DataAtom {
+		case a.Html:
+			copyAttributes(p.oe[0], p.tok)
+		case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
+			return inHeadIM(p)
+		case a.Body:
+			if len(p.oe) >= 2 {
+				body := p.oe[1]
+				if body.Type == ElementNode && body.DataAtom == a.Body {
+					p.framesetOK = false
+					copyAttributes(body, p.tok)
+				}
+			}
+		case a.Frameset:
+			if !p.framesetOK || len(p.oe) < 2 || p.oe[1].DataAtom != a.Body {
+				// Ignore the token.
+				return true
+			}
+			body := p.oe[1]
+			if body.Parent != nil {
+				body.Parent.RemoveChild(body)
+			}
+			p.oe = p.oe[:1]
+			p.addElement()
+			p.im = inFramesetIM
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(buttonScope, a.P)
+			switch n := p.top(); n.DataAtom {
+			case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+				p.oe.pop()
+			}
+			p.addElement()
+		case a.Pre, a.Listing:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			// The newline, if any, will be dealt with by the TextToken case.
+			p.framesetOK = false
+		case a.Form:
+			if p.form == nil {
+				p.popUntil(buttonScope, a.P)
+				p.addElement()
+				p.form = p.top()
+			}
+		case a.Li:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Li:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Dd, a.Dt:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Dd, a.Dt:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Plaintext:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Button:
+			p.popUntil(defaultScope, a.Button)
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+		case a.A:
+			for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- {
+				if n := p.afe[i]; n.Type == ElementNode && n.DataAtom == a.A {
+					p.inBodyEndTagFormatting(a.A)
+					p.oe.remove(n)
+					p.afe.remove(n)
+					break
+				}
+			}
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.Nobr:
+			p.reconstructActiveFormattingElements()
+			if p.elementInScope(defaultScope, a.Nobr) {
+				p.inBodyEndTagFormatting(a.Nobr)
+				p.reconstructActiveFormattingElements()
+			}
+			p.addFormattingElement()
+		case a.Applet, a.Marquee, a.Object:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.afe = append(p.afe, &scopeMarker)
+			p.framesetOK = false
+		case a.Table:
+			if !p.quirks {
+				p.popUntil(buttonScope, a.P)
+			}
+			p.addElement()
+			p.framesetOK = false
+			p.im = inTableIM
+			return true
+		case a.Area, a.Br, a.Embed, a.Img, a.Input, a.Keygen, a.Wbr:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			if p.tok.DataAtom == a.Input {
+				for _, t := range p.tok.Attr {
+					if t.Key == "type" {
+						if strings.ToLower(t.Val) == "hidden" {
+							// Skip setting framesetOK = false
+							return true
+						}
+					}
+				}
+			}
+			p.framesetOK = false
+		case a.Param, a.Source, a.Track:
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+		case a.Hr:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			p.framesetOK = false
+		case a.Image:
+			p.tok.DataAtom = a.Img
+			p.tok.Data = a.Img.String()
+			return false
+		case a.Isindex:
+			if p.form != nil {
+				// Ignore the token.
+				return true
+			}
+			action := ""
+			prompt := "This is a searchable index. Enter search keywords: "
+			attr := []Attribute{{Key: "name", Val: "isindex"}}
+			for _, t := range p.tok.Attr {
+				switch t.Key {
+				case "action":
+					action = t.Val
+				case "name":
+					// Ignore the attribute.
+				case "prompt":
+					prompt = t.Val
+				default:
+					attr = append(attr, t)
+				}
+			}
+			p.acknowledgeSelfClosingTag()
+			p.popUntil(buttonScope, a.P)
+			p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
+			if action != "" {
+				p.form.Attr = []Attribute{{Key: "action", Val: action}}
+			}
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(StartTagToken, a.Label, a.Label.String())
+			p.addText(prompt)
+			p.addChild(&Node{
+				Type:     ElementNode,
+				DataAtom: a.Input,
+				Data:     a.Input.String(),
+				Attr:     attr,
+			})
+			p.oe.pop()
+			p.parseImpliedToken(EndTagToken, a.Label, a.Label.String())
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(EndTagToken, a.Form, a.Form.String())
+		case a.Textarea:
+			p.addElement()
+			p.setOriginalIM()
+			p.framesetOK = false
+			p.im = textIM
+		case a.Xmp:
+			p.popUntil(buttonScope, a.P)
+			p.reconstructActiveFormattingElements()
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Iframe:
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Noembed, a.Noscript:
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Select:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+			p.im = inSelectIM
+			return true
+		case a.Optgroup, a.Option:
+			if p.top().DataAtom == a.Option {
+				p.oe.pop()
+			}
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		case a.Rp, a.Rt:
+			if p.elementInScope(defaultScope, a.Ruby) {
+				p.generateImpliedEndTags()
+			}
+			p.addElement()
+		case a.Math, a.Svg:
+			p.reconstructActiveFormattingElements()
+			if p.tok.DataAtom == a.Math {
+				adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
+			} else {
+				adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
+			}
+			adjustForeignAttributes(p.tok.Attr)
+			p.addElement()
+			p.top().Namespace = p.tok.Data
+			if p.hasSelfClosingToken {
+				p.oe.pop()
+				p.acknowledgeSelfClosingTag()
+			}
+			return true
+		case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
+			// Ignore the token.
+		default:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		}
+	case EndTagToken:
+		switch p.tok.DataAtom {
+		case a.Body:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.im = afterBodyIM
+			}
+		case a.Html:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.parseImpliedToken(EndTagToken, a.Body, a.Body.String())
+				return false
+			}
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.Form:
+			node := p.form
+			p.form = nil
+			i := p.indexOfElementInScope(defaultScope, a.Form)
+			if node == nil || i == -1 || p.oe[i] != node {
+				// Ignore the token.
+				return true
+			}
+			p.generateImpliedEndTags()
+			p.oe.remove(node)
+		case a.P:
+			if !p.elementInScope(buttonScope, a.P) {
+				p.parseImpliedToken(StartTagToken, a.P, a.P.String())
+			}
+			p.popUntil(buttonScope, a.P)
+		case a.Li:
+			p.popUntil(listItemScope, a.Li)
+		case a.Dd, a.Dt:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(defaultScope, a.H1, a.H2, a.H3, a.H4, a.H5, a.H6)
+		case a.A, a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.Nobr, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.inBodyEndTagFormatting(p.tok.DataAtom)
+		case a.Applet, a.Marquee, a.Object:
+			if p.popUntil(defaultScope, p.tok.DataAtom) {
+				p.clearActiveFormattingElements()
+			}
+		case a.Br:
+			p.tok.Type = StartTagToken
+			return false
+		default:
+			p.inBodyEndTagOther(p.tok.DataAtom)
+		}
+	case CommentToken:
+		p.addChild(&Node{
+			Type: CommentNode,
+			Data: p.tok.Data,
+		})
+	}
+
+	return true
+}
+
+func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
+	// This is the "adoption agency" algorithm, described at
+	// https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency
+
+	// TODO: this is a fairly literal line-by-line translation of that algorithm.
+	// Once the code successfully parses the comprehensive test suite, we should
+	// refactor this code to be more idiomatic.
+
+	// Steps 1-4. The outer loop.
+	for i := 0; i < 8; i++ {
+		// Step 5. Find the formatting element.
+		var formattingElement *Node
+		for j := len(p.afe) - 1; j >= 0; j-- {
+			if p.afe[j].Type == scopeMarkerNode {
+				break
+			}
+			if p.afe[j].DataAtom == tagAtom {
+				formattingElement = p.afe[j]
+				break
+			}
+		}
+		if formattingElement == nil {
+			p.inBodyEndTagOther(tagAtom)
+			return
+		}
+		feIndex := p.oe.index(formattingElement)
+		if feIndex == -1 {
+			p.afe.remove(formattingElement)
+			return
+		}
+		if !p.elementInScope(defaultScope, tagAtom) {
+			// Ignore the tag.
+			return
+		}
+
+		// Steps 9-10. Find the furthest block.
+		var furthestBlock *Node
+		for _, e := range p.oe[feIndex:] {
+			if isSpecialElement(e) {
+				furthestBlock = e
+				break
+			}
+		}
+		if furthestBlock == nil {
+			e := p.oe.pop()
+			for e != formattingElement {
+				e = p.oe.pop()
+			}
+			p.afe.remove(e)
+			return
+		}
+
+		// Steps 11-12. Find the common ancestor and bookmark node.
+		commonAncestor := p.oe[feIndex-1]
+		bookmark := p.afe.index(formattingElement)
+
+		// Step 13. The inner loop. Find the lastNode to reparent.
+		lastNode := furthestBlock
+		node := furthestBlock
+		x := p.oe.index(node)
+		// Steps 13.1-13.2
+		for j := 0; j < 3; j++ {
+			// Step 13.3.
+			x--
+			node = p.oe[x]
+			// Step 13.4 - 13.5.
+			if p.afe.index(node) == -1 {
+				p.oe.remove(node)
+				continue
+			}
+			// Step 13.6.
+			if node == formattingElement {
+				break
+			}
+			// Step 13.7.
+			clone := node.clone()
+			p.afe[p.afe.index(node)] = clone
+			p.oe[p.oe.index(node)] = clone
+			node = clone
+			// Step 13.8.
+			if lastNode == furthestBlock {
+				bookmark = p.afe.index(node) + 1
+			}
+			// Step 13.9.
+			if lastNode.Parent != nil {
+				lastNode.Parent.RemoveChild(lastNode)
+			}
+			node.AppendChild(lastNode)
+			// Step 13.10.
+			lastNode = node
+		}
+
+		// Step 14. Reparent lastNode to the common ancestor,
+		// or for misnested table nodes, to the foster parent.
+		if lastNode.Parent != nil {
+			lastNode.Parent.RemoveChild(lastNode)
+		}
+		switch commonAncestor.DataAtom {
+		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
+			p.fosterParent(lastNode)
+		default:
+			commonAncestor.AppendChild(lastNode)
+		}
+
+		// Steps 15-17. Reparent nodes from the furthest block's children
+		// to a clone of the formatting element.
+		clone := formattingElement.clone()
+		reparentChildren(clone, furthestBlock)
+		furthestBlock.AppendChild(clone)
+
+		// Step 18. Fix up the list of active formatting elements.
+		if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
+			// Move the bookmark with the rest of the list.
+			bookmark--
+		}
+		p.afe.remove(formattingElement)
+		p.afe.insert(bookmark, clone)
+
+		// Step 19. Fix up the stack of open elements.
+		p.oe.remove(formattingElement)
+		p.oe.insert(p.oe.index(furthestBlock)+1, clone)
+	}
+}
+
+// inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
+// "Any other end tag" handling from 12.2.5.5 The rules for parsing tokens in foreign content
+// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign
+func (p *parser) inBodyEndTagOther(tagAtom a.Atom) {
+	for i := len(p.oe) - 1; i >= 0; i-- {
+		if p.oe[i].DataAtom == tagAtom {
+			p.oe = p.oe[:i]
+			break
+		}
+		if isSpecialElement(p.oe[i]) {
+			break
+		}
+	}
+}
+
+// Section 12.2.5.4.8.
+func textIM(p *parser) bool {
+	switch p.tok.Type {
+	case ErrorToken:
+		p.oe.pop()
+	case TextToken:
+		d := p.tok.Data
+		if n := p.oe.top(); n.DataAtom == a.Textarea && n.FirstChild == nil {
+			// Ignore a newline at the start of a -->
+#errors
+#document
+| 
+|   
+|   
+|     -->
+#errors
+#document
+| 
+|   
+|   
+|     
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+#document
+| 
+|   
+|   
+|     
+#errors
+Line: 1 Col: 9 Unexpected end tag (strong). Expected DOCTYPE.
+Line: 1 Col: 9 Unexpected end tag (strong) after the (implied) root element.
+Line: 1 Col: 13 Unexpected end tag (b) after the (implied) root element.
+Line: 1 Col: 18 Unexpected end tag (em) after the (implied) root element.
+Line: 1 Col: 22 Unexpected end tag (i) after the (implied) root element.
+Line: 1 Col: 26 Unexpected end tag (u) after the (implied) root element.
+Line: 1 Col: 35 Unexpected end tag (strike) after the (implied) root element.
+Line: 1 Col: 39 Unexpected end tag (s) after the (implied) root element.
+Line: 1 Col: 47 Unexpected end tag (blink) after the (implied) root element.
+Line: 1 Col: 52 Unexpected end tag (tt) after the (implied) root element.
+Line: 1 Col: 58 Unexpected end tag (pre) after the (implied) root element.
+Line: 1 Col: 64 Unexpected end tag (big) after the (implied) root element.
+Line: 1 Col: 72 Unexpected end tag (small) after the (implied) root element.
+Line: 1 Col: 79 Unexpected end tag (font) after the (implied) root element.
+Line: 1 Col: 88 Unexpected end tag (select) after the (implied) root element.
+Line: 1 Col: 93 Unexpected end tag (h1) after the (implied) root element.
+Line: 1 Col: 98 Unexpected end tag (h2) after the (implied) root element.
+Line: 1 Col: 103 Unexpected end tag (h3) after the (implied) root element.
+Line: 1 Col: 108 Unexpected end tag (h4) after the (implied) root element.
+Line: 1 Col: 113 Unexpected end tag (h5) after the (implied) root element.
+Line: 1 Col: 118 Unexpected end tag (h6) after the (implied) root element.
+Line: 1 Col: 125 Unexpected end tag (body) after the (implied) root element.
+Line: 1 Col: 130 Unexpected end tag (br). Treated as br element.
+Line: 1 Col: 134 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 140 This element (img) has no end tag.
+Line: 1 Col: 148 Unexpected end tag (title). Ignored.
+Line: 1 Col: 155 Unexpected end tag (span). Ignored.
+Line: 1 Col: 163 Unexpected end tag (style). Ignored.
+Line: 1 Col: 172 Unexpected end tag (script). Ignored.
+Line: 1 Col: 180 Unexpected end tag (table). Ignored.
+Line: 1 Col: 185 Unexpected end tag (th). Ignored.
+Line: 1 Col: 190 Unexpected end tag (td). Ignored.
+Line: 1 Col: 195 Unexpected end tag (tr). Ignored.
+Line: 1 Col: 203 This element (frame) has no end tag.
+Line: 1 Col: 210 This element (area) has no end tag.
+Line: 1 Col: 217 Unexpected end tag (link). Ignored.
+Line: 1 Col: 225 This element (param) has no end tag.
+Line: 1 Col: 230 This element (hr) has no end tag.
+Line: 1 Col: 238 This element (input) has no end tag.
+Line: 1 Col: 244 Unexpected end tag (col). Ignored.
+Line: 1 Col: 251 Unexpected end tag (base). Ignored.
+Line: 1 Col: 258 Unexpected end tag (meta). Ignored.
+Line: 1 Col: 269 This element (basefont) has no end tag.
+Line: 1 Col: 279 This element (bgsound) has no end tag.
+Line: 1 Col: 287 This element (embed) has no end tag.
+Line: 1 Col: 296 This element (spacer) has no end tag.
+Line: 1 Col: 300 Unexpected end tag (p). Ignored.
+Line: 1 Col: 305 End tag (dd) seen too early. Expected other end tag.
+Line: 1 Col: 310 End tag (dt) seen too early. Expected other end tag.
+Line: 1 Col: 320 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 331 Unexpected end tag (colgroup). Ignored.
+Line: 1 Col: 339 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 347 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 355 Unexpected end tag (thead). Ignored.
+Line: 1 Col: 365 End tag (address) seen too early. Expected other end tag.
+Line: 1 Col: 378 End tag (blockquote) seen too early. Expected other end tag.
+Line: 1 Col: 387 End tag (center) seen too early. Expected other end tag.
+Line: 1 Col: 393 Unexpected end tag (dir). Ignored.
+Line: 1 Col: 399 End tag (div) seen too early. Expected other end tag.
+Line: 1 Col: 404 End tag (dl) seen too early. Expected other end tag.
+Line: 1 Col: 415 End tag (fieldset) seen too early. Expected other end tag.
+Line: 1 Col: 425 End tag (listing) seen too early. Expected other end tag.
+Line: 1 Col: 432 End tag (menu) seen too early. Expected other end tag.
+Line: 1 Col: 437 End tag (ol) seen too early. Expected other end tag.
+Line: 1 Col: 442 End tag (ul) seen too early. Expected other end tag.
+Line: 1 Col: 447 End tag (li) seen too early. Expected other end tag.
+Line: 1 Col: 454 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 460 This element (wbr) has no end tag.
+Line: 1 Col: 476 End tag (button) seen too early. Expected other end tag.
+Line: 1 Col: 486 End tag (marquee) seen too early. Expected other end tag.
+Line: 1 Col: 495 End tag (object) seen too early. Expected other end tag.
+Line: 1 Col: 513 Unexpected end tag (html). Ignored.
+Line: 1 Col: 513 Unexpected end tag (frameset). Ignored.
+Line: 1 Col: 520 Unexpected end tag (head). Ignored.
+Line: 1 Col: 529 Unexpected end tag (iframe). Ignored.
+Line: 1 Col: 537 This element (image) has no end tag.
+Line: 1 Col: 547 This element (isindex) has no end tag.
+Line: 1 Col: 557 Unexpected end tag (noembed). Ignored.
+Line: 1 Col: 568 Unexpected end tag (noframes). Ignored.
+Line: 1 Col: 579 Unexpected end tag (noscript). Ignored.
+Line: 1 Col: 590 Unexpected end tag (optgroup). Ignored.
+Line: 1 Col: 599 Unexpected end tag (option). Ignored.
+Line: 1 Col: 611 Unexpected end tag (plaintext). Ignored.
+Line: 1 Col: 622 Unexpected end tag (textarea). Ignored.
+#document
+| 
+|   
+|   
+|     
+|

+ +#data +

+#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end tag (strong) in table context caused voodoo mode. +Line: 1 Col: 20 End tag (strong) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 24 Unexpected end tag (b) in table context caused voodoo mode. +Line: 1 Col: 24 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 29 Unexpected end tag (em) in table context caused voodoo mode. +Line: 1 Col: 29 End tag (em) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 33 Unexpected end tag (i) in table context caused voodoo mode. +Line: 1 Col: 33 End tag (i) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 37 Unexpected end tag (u) in table context caused voodoo mode. +Line: 1 Col: 37 End tag (u) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 46 Unexpected end tag (strike) in table context caused voodoo mode. +Line: 1 Col: 46 End tag (strike) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 50 Unexpected end tag (s) in table context caused voodoo mode. +Line: 1 Col: 50 End tag (s) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 58 Unexpected end tag (blink) in table context caused voodoo mode. +Line: 1 Col: 58 Unexpected end tag (blink). Ignored. +Line: 1 Col: 63 Unexpected end tag (tt) in table context caused voodoo mode. +Line: 1 Col: 63 End tag (tt) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 69 Unexpected end tag (pre) in table context caused voodoo mode. +Line: 1 Col: 69 End tag (pre) seen too early. Expected other end tag. +Line: 1 Col: 75 Unexpected end tag (big) in table context caused voodoo mode. +Line: 1 Col: 75 End tag (big) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 83 Unexpected end tag (small) in table context caused voodoo mode. +Line: 1 Col: 83 End tag (small) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 90 Unexpected end tag (font) in table context caused voodoo mode. +Line: 1 Col: 90 End tag (font) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 99 Unexpected end tag (select) in table context caused voodoo mode. +Line: 1 Col: 99 Unexpected end tag (select). Ignored. +Line: 1 Col: 104 Unexpected end tag (h1) in table context caused voodoo mode. +Line: 1 Col: 104 End tag (h1) seen too early. Expected other end tag. +Line: 1 Col: 109 Unexpected end tag (h2) in table context caused voodoo mode. +Line: 1 Col: 109 End tag (h2) seen too early. Expected other end tag. +Line: 1 Col: 114 Unexpected end tag (h3) in table context caused voodoo mode. +Line: 1 Col: 114 End tag (h3) seen too early. Expected other end tag. +Line: 1 Col: 119 Unexpected end tag (h4) in table context caused voodoo mode. +Line: 1 Col: 119 End tag (h4) seen too early. Expected other end tag. +Line: 1 Col: 124 Unexpected end tag (h5) in table context caused voodoo mode. +Line: 1 Col: 124 End tag (h5) seen too early. Expected other end tag. +Line: 1 Col: 129 Unexpected end tag (h6) in table context caused voodoo mode. +Line: 1 Col: 129 End tag (h6) seen too early. Expected other end tag. +Line: 1 Col: 136 Unexpected end tag (body) in the table row phase. Ignored. +Line: 1 Col: 141 Unexpected end tag (br) in table context caused voodoo mode. +Line: 1 Col: 141 Unexpected end tag (br). Treated as br element. +Line: 1 Col: 145 Unexpected end tag (a) in table context caused voodoo mode. +Line: 1 Col: 145 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 151 Unexpected end tag (img) in table context caused voodoo mode. +Line: 1 Col: 151 This element (img) has no end tag. +Line: 1 Col: 159 Unexpected end tag (title) in table context caused voodoo mode. +Line: 1 Col: 159 Unexpected end tag (title). Ignored. +Line: 1 Col: 166 Unexpected end tag (span) in table context caused voodoo mode. +Line: 1 Col: 166 Unexpected end tag (span). Ignored. +Line: 1 Col: 174 Unexpected end tag (style) in table context caused voodoo mode. +Line: 1 Col: 174 Unexpected end tag (style). Ignored. +Line: 1 Col: 183 Unexpected end tag (script) in table context caused voodoo mode. +Line: 1 Col: 183 Unexpected end tag (script). Ignored. +Line: 1 Col: 196 Unexpected end tag (th). Ignored. +Line: 1 Col: 201 Unexpected end tag (td). Ignored. +Line: 1 Col: 206 Unexpected end tag (tr). Ignored. +Line: 1 Col: 214 This element (frame) has no end tag. +Line: 1 Col: 221 This element (area) has no end tag. +Line: 1 Col: 228 Unexpected end tag (link). Ignored. +Line: 1 Col: 236 This element (param) has no end tag. +Line: 1 Col: 241 This element (hr) has no end tag. +Line: 1 Col: 249 This element (input) has no end tag. +Line: 1 Col: 255 Unexpected end tag (col). Ignored. +Line: 1 Col: 262 Unexpected end tag (base). Ignored. +Line: 1 Col: 269 Unexpected end tag (meta). Ignored. +Line: 1 Col: 280 This element (basefont) has no end tag. +Line: 1 Col: 290 This element (bgsound) has no end tag. +Line: 1 Col: 298 This element (embed) has no end tag. +Line: 1 Col: 307 This element (spacer) has no end tag. +Line: 1 Col: 311 Unexpected end tag (p). Ignored. +Line: 1 Col: 316 End tag (dd) seen too early. Expected other end tag. +Line: 1 Col: 321 End tag (dt) seen too early. Expected other end tag. +Line: 1 Col: 331 Unexpected end tag (caption). Ignored. +Line: 1 Col: 342 Unexpected end tag (colgroup). Ignored. +Line: 1 Col: 350 Unexpected end tag (tbody). Ignored. +Line: 1 Col: 358 Unexpected end tag (tfoot). Ignored. +Line: 1 Col: 366 Unexpected end tag (thead). Ignored. +Line: 1 Col: 376 End tag (address) seen too early. Expected other end tag. +Line: 1 Col: 389 End tag (blockquote) seen too early. Expected other end tag. +Line: 1 Col: 398 End tag (center) seen too early. Expected other end tag. +Line: 1 Col: 404 Unexpected end tag (dir). Ignored. +Line: 1 Col: 410 End tag (div) seen too early. Expected other end tag. +Line: 1 Col: 415 End tag (dl) seen too early. Expected other end tag. +Line: 1 Col: 426 End tag (fieldset) seen too early. Expected other end tag. +Line: 1 Col: 436 End tag (listing) seen too early. Expected other end tag. +Line: 1 Col: 443 End tag (menu) seen too early. Expected other end tag. +Line: 1 Col: 448 End tag (ol) seen too early. Expected other end tag. +Line: 1 Col: 453 End tag (ul) seen too early. Expected other end tag. +Line: 1 Col: 458 End tag (li) seen too early. Expected other end tag. +Line: 1 Col: 465 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 471 This element (wbr) has no end tag. +Line: 1 Col: 487 End tag (button) seen too early. Expected other end tag. +Line: 1 Col: 497 End tag (marquee) seen too early. Expected other end tag. +Line: 1 Col: 506 End tag (object) seen too early. Expected other end tag. +Line: 1 Col: 524 Unexpected end tag (html). Ignored. +Line: 1 Col: 524 Unexpected end tag (frameset). Ignored. +Line: 1 Col: 531 Unexpected end tag (head). Ignored. +Line: 1 Col: 540 Unexpected end tag (iframe). Ignored. +Line: 1 Col: 548 This element (image) has no end tag. +Line: 1 Col: 558 This element (isindex) has no end tag. +Line: 1 Col: 568 Unexpected end tag (noembed). Ignored. +Line: 1 Col: 579 Unexpected end tag (noframes). Ignored. +Line: 1 Col: 590 Unexpected end tag (noscript). Ignored. +Line: 1 Col: 601 Unexpected end tag (optgroup). Ignored. +Line: 1 Col: 610 Unexpected end tag (option). Ignored. +Line: 1 Col: 622 Unexpected end tag (plaintext). Ignored. +Line: 1 Col: 633 Unexpected end tag (textarea). Ignored. +#document +| +| +| +|
+| +| +| +|

+ +#data + +#errors +Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE. +Line: 1 Col: 10 Expected closing tag. Unexpected end of file. +#document +| +| +| diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests10.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests10.dat new file mode 100644 index 0000000..4f8df86 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests10.dat @@ -0,0 +1,799 @@ +#data + +#errors +#document +| +| +| +| +| + +#data +a +#errors +29: Bogus comment +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| + +#data + +#errors +35: Stray “svg” start tag. +42: Stray end tag “svg” +#document +| +| +| +| +| +#errors +43: Stray “svg” start tag. +50: Stray end tag “svg” +#document +| +| +| +| +|

+#errors +34: Start tag “svg” seen in “table”. +41: Stray end tag “svg”. +#document +| +| +| +| +| +| + +#data +
foo
+#errors +34: Start tag “svg” seen in “table”. +46: Stray end tag “g”. +53: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| + +#data +
foobar
+#errors +34: Start tag “svg” seen in “table”. +46: Stray end tag “g”. +58: Stray end tag “g”. +65: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| + +#data +
foobar
+#errors +41: Start tag “svg” seen in “table”. +53: Stray end tag “g”. +65: Stray end tag “g”. +72: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| +| + +#data +
foobar
+#errors +45: Start tag “svg” seen in “table”. +57: Stray end tag “g”. +69: Stray end tag “g”. +76: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| +| +| + +#data +
foobar
+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" + +#data +
foobar

baz

+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +
foobar

baz

+#errors +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +
foobar

baz

quux +#errors +70: HTML start tag “p” in a foreign namespace context. +81: “table” closed but “caption” was still open. +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" +|

+| "quux" + +#data +
foobarbaz

quux +#errors +78: “table” closed but “caption” was still open. +78: Unclosed elements on stack. +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +| "baz" +|

+| "quux" + +#data +foobar

baz

quux +#errors +44: Start tag “svg” seen in “table”. +56: Stray end tag “g”. +68: Stray end tag “g”. +71: HTML start tag “p” in a foreign namespace context. +71: Start tag “p” seen in “table”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" +| +| +|

+| "quux" + +#data +

quux +#errors +50: Stray “svg” start tag. +54: Stray “g” start tag. +62: Stray end tag “g” +66: Stray “g” start tag. +74: Stray end tag “g” +77: Stray “p” start tag. +88: “table” end tag with “select” open. +#document +| +| +| +| +| +| +| +|
+|

quux +#errors +36: Start tag “select” seen in “table”. +42: Stray “svg” start tag. +46: Stray “g” start tag. +54: Stray end tag “g” +58: Stray “g” start tag. +66: Stray end tag “g” +69: Stray “p” start tag. +80: “table” end tag with “select” open. +#document +| +| +| +| +| +|

+| "quux" + +#data +foobar

baz +#errors +41: Stray “svg” start tag. +68: HTML start tag “p” in a foreign namespace context. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +foobar

baz +#errors +34: Stray “svg” start tag. +61: HTML start tag “p” in a foreign namespace context. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +

+#errors +31: Stray “svg” start tag. +35: Stray “g” start tag. +40: Stray end tag “g” +44: Stray “g” start tag. +49: Stray end tag “g” +52: Stray “p” start tag. +58: Stray “span” start tag. +58: End of file seen and there were open elements. +#document +| +| +| +| + +#data +

+#errors +42: Stray “svg” start tag. +46: Stray “g” start tag. +51: Stray end tag “g” +55: Stray “g” start tag. +60: Stray end tag “g” +63: Stray “p” start tag. +69: Stray “span” start tag. +#document +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| +| xlink href="foo" + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" + +#data +bar +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" +| "bar" + +#data + +#errors +#document +| +| +| +| + +#data +

a +#errors +#document +| +| +| +|
+| +| "a" + +#data +
a +#errors +#document +| +| +| +|
+| +| +| "a" + +#data +
+#errors +#document +| +| +| +|
+| +| +| + +#data +
a +#errors +#document +| +| +| +|
+| +| +| +| +| "a" + +#data +

a +#errors +#document +| +| +| +|

+| +| +| +|

+| "a" + +#data +
    a +#errors +40: HTML start tag “ul” in a foreign namespace context. +41: End of file in a foreign namespace context. +#document +| +| +| +| +| +| +|
    +| +|
      +| "a" + +#data +
        a +#errors +35: HTML start tag “ul” in a foreign namespace context. +36: End of file in a foreign namespace context. +#document +| +| +| +| +| +| +| +|
          +| "a" + +#data +

          +#errors +#document +| +| +| +| +|

          +| +| +|

          + +#data +

          +#errors +#document +| +| +| +| +|

          +| +| +|

          + +#data +

          +#errors +#document +| +| +| +|

          +| +| +| +|

          +|

          + +#data +
          +#errors +#document +| +| +| +| +| +|
          +| +|
          +| +| + +#data +
          +#errors +#document +| +| +| +| +| +| +| +|
          +|
          +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data +

+#errors +#document +| +| +| +| +|
+| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| + +#data +
+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests11.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests11.dat new file mode 100644 index 0000000..638cde4 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests11.dat @@ -0,0 +1,482 @@ +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributename="" +| attributetype="" +| basefrequency="" +| baseprofile="" +| calcmode="" +| clippathunits="" +| contentscripttype="" +| contentstyletype="" +| diffuseconstant="" +| edgemode="" +| externalresourcesrequired="" +| filterres="" +| filterunits="" +| glyphref="" +| gradienttransform="" +| gradientunits="" +| kernelmatrix="" +| kernelunitlength="" +| keypoints="" +| keysplines="" +| keytimes="" +| lengthadjust="" +| limitingconeangle="" +| markerheight="" +| markerunits="" +| markerwidth="" +| maskcontentunits="" +| maskunits="" +| numoctaves="" +| pathlength="" +| patterncontentunits="" +| patterntransform="" +| patternunits="" +| pointsatx="" +| pointsaty="" +| pointsatz="" +| preservealpha="" +| preserveaspectratio="" +| primitiveunits="" +| refx="" +| refy="" +| repeatcount="" +| repeatdur="" +| requiredextensions="" +| requiredfeatures="" +| specularconstant="" +| specularexponent="" +| spreadmethod="" +| startoffset="" +| stddeviation="" +| stitchtiles="" +| surfacescale="" +| systemlanguage="" +| tablevalues="" +| targetx="" +| targety="" +| textlength="" +| viewbox="" +| viewtarget="" +| xchannelselector="" +| ychannelselector="" +| zoomandpan="" + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests12.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests12.dat new file mode 100644 index 0000000..63107d2 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests12.dat @@ -0,0 +1,62 @@ +#data +

foobazeggs

spam

quuxbar +#errors +#document +| +| +| +| +|

+| "foo" +| +| +| +| "baz" +| +| +| +| +| "eggs" +| +| +|

+| "spam" +| +| +| +|
+| +| +| "quux" +| "bar" + +#data +foobazeggs

spam
quuxbar +#errors +#document +| +| +| +| +| "foo" +| +| +| +| "baz" +| +| +| +| +| "eggs" +| +| +|

+| "spam" +| +| +| +|
+| +| +| "quux" +| "bar" diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests14.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests14.dat new file mode 100644 index 0000000..b8713f8 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests14.dat @@ -0,0 +1,74 @@ +#data + +#errors +#document +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +15: Unexpected start tag html +#document +| +| +| abc:def="gh" +| +| +| + +#data + +#errors +15: Unexpected start tag html +#document +| +| +| xml:lang="bar" +| +| + +#data + +#errors +#document +| +| +| 123="456" +| +| + +#data + +#errors +#document +| +| +| 123="456" +| 789="012" +| +| + +#data + +#errors +#document +| +| +| +| +| 789="012" diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests15.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests15.dat new file mode 100644 index 0000000..6ce1c0d --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests15.dat @@ -0,0 +1,208 @@ +#data +

X +#errors +Line: 1 Col: 31 Unexpected end tag (p). Ignored. +Line: 1 Col: 36 Expected closing tag. Unexpected end of file. +#document +| +| +| +| +|

+| +| +| +| +| +| +| " " +|

+| "X" + +#data +

+

X +#errors +Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end tag (p). Ignored. +Line: 2 Col: 4 Expected closing tag. Unexpected end of file. +#document +| +| +| +|

+| +| +| +| +| +| +| " +" +|

+| "X" + +#data + +#errors +Line: 1 Col: 22 Unexpected end tag (html) after the (implied) root element. +#document +| +| +| +| +| " " + +#data + +#errors +Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element. +#document +| +| +| +| +| + +#data + +#errors +Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end tag (html) after the (implied) root element. +#document +| +| +| +| + +#data +X +#errors +Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element. +#document +| +| +| +| +| +| "X" + +#data +<!doctype html><table> X<meta></table> +#errors +Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode. +Line: 1 Col: 30 Unexpected start tag (meta) in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " X" +| <meta> +| <table> + +#data +<!doctype html><table> x</table> +#errors +Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x" +| <table> + +#data +<!doctype html><table> x </table> +#errors +Line: 1 Col: 25 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x " +| <table> + +#data +<!doctype html><table><tr> x</table> +#errors +Line: 1 Col: 28 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table>X<style> <tr>x </style> </table> +#errors +Line: 1 Col: 23 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "X" +| <table> +| <style> +| " <tr>x " +| " " + +#data +<!doctype html><div><table><a>foo</a> <tr><td>bar</td> </tr></table></div> +#errors +Line: 1 Col: 30 Unexpected start tag (a) in table context caused voodoo mode. +Line: 1 Col: 37 Unexpected end tag (a) in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| <a> +| "foo" +| <table> +| " " +| <tbody> +| <tr> +| <td> +| "bar" +| " " + +#data +<frame></frame></frame><frameset><frame><frameset><frame></frameset><noframes></frameset><noframes> +#errors +6: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”. +13: Stray start tag “frame”. +21: Stray end tag “frame”. +29: Stray end tag “frame”. +39: “frameset” start tag after “body” already open. +105: End of file seen inside an [R]CDATA element. +105: End of file seen and there were open elements. +XXX: These errors are wrong, please fix me! +#document +| <html> +| <head> +| <frameset> +| <frame> +| <frameset> +| <frame> +| <noframes> +| "</frameset><noframes>" + +#data +<!DOCTYPE html><object></html> +#errors +1: Expected closing tag. Unexpected end of file +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <object> diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests16.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests16.dat new file mode 100644 index 0000000..c8ef66f --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests16.dat @@ -0,0 +1,2299 @@ +#data +<!doctype html><script> +#errors +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script>a +#errors +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "a" +| <body> + +#data +<!doctype html><script>< +#errors +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<" +| <body> + +#data +<!doctype html><script></ +#errors +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</" +| <body> + +#data +<!doctype html><script></S +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</S" +| <body> + +#data +<!doctype html><script></SC +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SC" +| <body> + +#data +<!doctype html><script></SCR +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCR" +| <body> + +#data +<!doctype html><script></SCRI +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRI" +| <body> + +#data +<!doctype html><script></SCRIP +#errors +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRIP" +| <body> + +#data +<!doctype html><script></SCRIPT +#errors +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRIPT" +| <body> + +#data +<!doctype html><script></SCRIPT +#errors +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script></s +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</s" +| <body> + +#data +<!doctype html><script></sc +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</sc" +| <body> + +#data +<!doctype html><script></scr +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scr" +| <body> + +#data +<!doctype html><script></scri +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scri" +| <body> + +#data +<!doctype html><script></scrip +#errors +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scrip" +| <body> + +#data +<!doctype html><script></script +#errors +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</script" +| <body> + +#data +<!doctype html><script></script +#errors +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script><! +#errors +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!" +| <body> + +#data +<!doctype html><script><!a +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!a" +| <body> + +#data +<!doctype html><script><!- +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!-" +| <body> + +#data +<!doctype html><script><!-a +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!-a" +| <body> + +#data +<!doctype html><script><!-- +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<!doctype html><script><!--a +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--a" +| <body> + +#data +<!doctype html><script><!--< +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<" +| <body> + +#data +<!doctype html><script><!--<a +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<a" +| <body> + +#data +<!doctype html><script><!--</ +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--</" +| <body> + +#data +<!doctype html><script><!--</script +#errors +Line: 1 Col: 35 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--</script" +| <body> + +#data +<!doctype html><script><!--</script +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<!doctype html><script><!--<s +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<s" +| <body> + +#data +<!doctype html><script><!--<script +#errors +Line: 1 Col: 34 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script" +| <body> + +#data +<!doctype html><script><!--<script +#errors +Line: 1 Col: 35 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script " +| <body> + +#data +<!doctype html><script><!--<script < +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script <" +| <body> + +#data +<!doctype html><script><!--<script <a +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script <a" +| <body> + +#data +<!doctype html><script><!--<script </ +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </" +| <body> + +#data +<!doctype html><script><!--<script </s +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </s" +| <body> + +#data +<!doctype html><script><!--<script </script +#errors +Line: 1 Col: 43 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script" +| <body> + +#data +<!doctype html><script><!--<script </scripta +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </scripta" +| <body> + +#data +<!doctype html><script><!--<script </script +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script> +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script>" +| <body> + +#data +<!doctype html><script><!--<script </script/ +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script/" +| <body> + +#data +<!doctype html><script><!--<script </script < +#errors +Line: 1 Col: 45 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script <" +| <body> + +#data +<!doctype html><script><!--<script </script <a +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script <a" +| <body> + +#data +<!doctype html><script><!--<script </script </ +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script </" +| <body> + +#data +<!doctype html><script><!--<script </script </script +#errors +Line: 1 Col: 52 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script </script" +| <body> + +#data +<!doctype html><script><!--<script </script </script +#errors +Line: 1 Col: 53 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script </script/ +#errors +Line: 1 Col: 53 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script </script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script - +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -" +| <body> + +#data +<!doctype html><script><!--<script -a +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -a" +| <body> + +#data +<!doctype html><script><!--<script -< +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -<" +| <body> + +#data +<!doctype html><script><!--<script -- +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --" +| <body> + +#data +<!doctype html><script><!--<script --a +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --a" +| <body> + +#data +<!doctype html><script><!--<script --< +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --<" +| <body> + +#data +<!doctype html><script><!--<script --> +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script -->< +#errors +Line: 1 Col: 39 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --><" +| <body> + +#data +<!doctype html><script><!--<script --></ +#errors +Line: 1 Col: 40 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --></" +| <body> + +#data +<!doctype html><script><!--<script --></script +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --></script" +| <body> + +#data +<!doctype html><script><!--<script --></script +#errors +Line: 1 Col: 47 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script --></script/ +#errors +Line: 1 Col: 47 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script --></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script><\/script>--></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script><\/script>-->" +| <body> + +#data +<!doctype html><script><!--<script></scr'+'ipt>--></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt>-->" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>--><!--</script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>--><!--" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>-- ></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>-- >" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>- -></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- ->" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>- - ></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- - >" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>-></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>->" +| <body> + +#data +<!doctype html><script><!--<script>--!></script>X +#errors +Line: 1 Col: 49 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script>--!></script>X" +| <body> + +#data +<!doctype html><script><!--<scr'+'ipt></script>--></script> +#errors +Line: 1 Col: 59 Unexpected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<scr'+'ipt>" +| <body> +| "-->" + +#data +<!doctype html><script><!--<script></scr'+'ipt></script>X +#errors +Line: 1 Col: 57 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt></script>X" +| <body> + +#data +<!doctype html><style><!--<style></style>--></style> +#errors +Line: 1 Col: 52 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--<style>" +| <body> +| "-->" + +#data +<!doctype html><style><!--</style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--" +| <body> +| "X" + +#data +<!doctype html><style><!--...</style>...--></style> +#errors +Line: 1 Col: 51 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--..." +| <body> +| "...-->" + +#data +<!doctype html><style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>" +| <body> +| "X" + +#data +<!doctype html><style><!--...<style><!--...--!></style>--></style> +#errors +Line: 1 Col: 66 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--...<style><!--...--!>" +| <body> +| "-->" + +#data +<!doctype html><style><!--...</style><!-- --><style>@import ...</style> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--..." +| <!-- --> +| <style> +| "@import ..." +| <body> + +#data +<!doctype html><style>...<style><!--...</style><!-- --></style> +#errors +Line: 1 Col: 63 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "...<style><!--..." +| <!-- --> +| <body> + +#data +<!doctype html><style>...<!--[if IE]><style>...</style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "...<!--[if IE]><style>..." +| <body> +| "X" + +#data +<!doctype html><title><!--<title>--> +#errors +Line: 1 Col: 52 Unexpected end tag (title). +#document +| +| +| +| +| "<!--<title>" +| <body> +| "-->" + +#data +<!doctype html><title></title> +#errors +#document +| +| +| +| +| "" +| + +#data +foo/title><link></head><body>X +#errors +Line: 1 Col: 52 Unexpected end of file. Expected end tag (title). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <title> +| "foo/title><link></head><body>X" +| <body> + +#data +<!doctype html><noscript><!--<noscript></noscript>--></noscript> +#errors +Line: 1 Col: 64 Unexpected end tag (noscript). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<!--<noscript>" +| <body> +| "-->" + +#data +<!doctype html><noscript><!--</noscript>X<noscript>--></noscript> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<!--" +| <body> +| "X" +| <noscript> +| "-->" + +#data +<!doctype html><noscript><iframe></noscript>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<iframe>" +| <body> +| "X" + +#data +<!doctype html><noframes><!--<noframes></noframes>--></noframes> +#errors +Line: 1 Col: 64 Unexpected end tag (noframes). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noframes> +| "<!--<noframes>" +| <body> +| "-->" + +#data +<!doctype html><noframes><body><script><!--...</script></body></noframes></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noframes> +| "<body><script><!--...</script></body>" +| <body> + +#data +<!doctype html><textarea><!--<textarea></textarea>--></textarea> +#errors +Line: 1 Col: 64 Unexpected end tag (textarea). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "<!--<textarea>" +| "-->" + +#data +<!doctype html><textarea></textarea></textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "</textarea>" + +#data +<!doctype html><textarea><</textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "<" + +#data +<!doctype html><textarea>a<b</textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "a<b" + +#data +<!doctype html><iframe><!--<iframe></iframe>--></iframe> +#errors +Line: 1 Col: 56 Unexpected end tag (iframe). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> +| "<!--<iframe>" +| "-->" + +#data +<!doctype html><iframe>...<!--X->...<!--/X->...</iframe> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> +| "...<!--X->...<!--/X->..." + +#data +<!doctype html><xmp><!--<xmp></xmp>--></xmp> +#errors +Line: 1 Col: 44 Unexpected end tag (xmp). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <xmp> +| "<!--<xmp>" +| "-->" + +#data +<!doctype html><noembed><!--<noembed></noembed>--></noembed> +#errors +Line: 1 Col: 60 Unexpected end tag (noembed). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <noembed> +| "<!--<noembed>" +| "-->" + +#data +<script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 8 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script>a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 9 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "a" +| <body> + +#data +<script>< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 9 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<" +| <body> + +#data +<script></ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 10 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</" +| <body> + +#data +<script></S +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</S" +| <body> + +#data +<script></SC +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SC" +| <body> + +#data +<script></SCR +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCR" +| <body> + +#data +<script></SCRI +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRI" +| <body> + +#data +<script></SCRIP +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 15 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRIP" +| <body> + +#data +<script></SCRIPT +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRIPT" +| <body> + +#data +<script></SCRIPT +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 17 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script></s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</s" +| <body> + +#data +<script></sc +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</sc" +| <body> + +#data +<script></scr +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scr" +| <body> + +#data +<script></scri +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scri" +| <body> + +#data +<script></scrip +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 15 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scrip" +| <body> + +#data +<script></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</script" +| <body> + +#data +<script></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 17 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script><! +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 10 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!" +| <body> + +#data +<script><!a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!a" +| <body> + +#data +<script><!- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!-" +| <body> + +#data +<script><!-a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!-a" +| <body> + +#data +<script><!-- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<script><!--a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--a" +| <body> + +#data +<script><!--< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<" +| <body> + +#data +<script><!--<a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<a" +| <body> + +#data +<script><!--</ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--</" +| <body> + +#data +<script><!--</script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--</script" +| <body> + +#data +<script><!--</script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<script><!--<s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<s" +| <body> + +#data +<script><!--<script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 19 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script" +| <body> + +#data +<script><!--<script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script " +| <body> + +#data +<script><!--<script < +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script <" +| <body> + +#data +<script><!--<script <a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script <a" +| <body> + +#data +<script><!--<script </ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </" +| <body> + +#data +<script><!--<script </s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </s" +| <body> + +#data +<script><!--<script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script" +| <body> + +#data +<script><!--<script </scripta +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </scripta" +| <body> + +#data +<script><!--<script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script>" +| <body> + +#data +<script><!--<script </script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script/" +| <body> + +#data +<script><!--<script </script < +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script <" +| <body> + +#data +<script><!--<script </script <a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script <a" +| <body> + +#data +<script><!--<script </script </ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script </" +| <body> + +#data +<script><!--<script </script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script </script" +| <body> + +#data +<script><!--<script </script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script </script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script </script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script - +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -" +| <body> + +#data +<script><!--<script -a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -a" +| <body> + +#data +<script><!--<script -- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --" +| <body> + +#data +<script><!--<script --a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --a" +| <body> + +#data +<script><!--<script --> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script -->< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --><" +| <body> + +#data +<script><!--<script --></ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --></" +| <body> + +#data +<script><!--<script --></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --></script" +| <body> + +#data +<script><!--<script --></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script --></script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script --></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script><\/script>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script><\/script>-->" +| <body> + +#data +<script><!--<script></scr'+'ipt>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt>-->" +| <body> + +#data +<script><!--<script></script><script></script></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>" +| <body> + +#data +<script><!--<script></script><script></script>--><!--</script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>--><!--" +| <body> + +#data +<script><!--<script></script><script></script>-- ></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>-- >" +| <body> + +#data +<script><!--<script></script><script></script>- -></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- ->" +| <body> + +#data +<script><!--<script></script><script></script>- - ></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- - >" +| <body> + +#data +<script><!--<script></script><script></script>-></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>->" +| <body> + +#data +<script><!--<script>--!></script>X +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 34 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script>--!></script>X" +| <body> + +#data +<script><!--<scr'+'ipt></script>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 44 Unexpected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<scr'+'ipt>" +| <body> +| "-->" + +#data +<script><!--<script></scr'+'ipt></script>X +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 42 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt></script>X" +| <body> + +#data +<style><!--<style></style>--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--<style>" +| <body> +| "-->" + +#data +<style><!--</style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--" +| <body> +| "X" + +#data +<style><!--...</style>...--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 36 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--..." +| <body> +| "...-->" + +#data +<style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>" +| <body> +| "X" + +#data +<style><!--...<style><!--...--!></style>--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 51 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--...<style><!--...--!>" +| <body> +| "-->" + +#data +<style><!--...</style><!-- --><style>@import ...</style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--..." +| <!-- --> +| <style> +| "@import ..." +| <body> + +#data +<style>...<style><!--...</style><!-- --></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 48 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "...<style><!--..." +| <!-- --> +| <body> + +#data +<style>...<!--[if IE]><style>...</style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "...<!--[if IE]><style>..." +| <body> +| "X" + +#data +<title><!--<title>--> +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end tag (title). +#document +| +| +| +| "<!--<title>" +| <body> +| "-->" + +#data +<title></title> +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +#document +| +| +| +| "" +| + +#data +foo/title><link></head><body>X +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end of file. Expected end tag (title). +#document +| <html> +| <head> +| <title> +| "foo/title><link></head><body>X" +| <body> + +#data +<noscript><!--<noscript></noscript>--></noscript> +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (noscript). +#document +| <html> +| <head> +| <noscript> +| "<!--<noscript>" +| <body> +| "-->" + +#data +<noscript><!--</noscript>X<noscript>--></noscript> +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +#document +| <html> +| <head> +| <noscript> +| "<!--" +| <body> +| "X" +| <noscript> +| "-->" + +#data +<noscript><iframe></noscript>X +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +#document +| <html> +| <head> +| <noscript> +| "<iframe>" +| <body> +| "X" + +#data +<noframes><!--<noframes></noframes>--></noframes> +#errors +Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (noframes). +#document +| <html> +| <head> +| <noframes> +| "<!--<noframes>" +| <body> +| "-->" + +#data +<noframes><body><script><!--...</script></body></noframes></html> +#errors +Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE. +#document +| <html> +| <head> +| <noframes> +| "<body><script><!--...</script></body>" +| <body> + +#data +<textarea><!--<textarea></textarea>--></textarea> +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (textarea). +#document +| <html> +| <head> +| <body> +| <textarea> +| "<!--<textarea>" +| "-->" + +#data +<textarea></textarea></textarea> +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| <textarea> +| "</textarea>" + +#data +<iframe><!--<iframe></iframe>--></iframe> +#errors +Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE. +Line: 1 Col: 41 Unexpected end tag (iframe). +#document +| <html> +| <head> +| <body> +| <iframe> +| "<!--<iframe>" +| "-->" + +#data +<iframe>...<!--X->...<!--/X->...</iframe> +#errors +Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| <iframe> +| "...<!--X->...<!--/X->..." + +#data +<xmp><!--<xmp></xmp>--></xmp> +#errors +Line: 1 Col: 5 Unexpected start tag (xmp). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end tag (xmp). +#document +| <html> +| <head> +| <body> +| <xmp> +| "<!--<xmp>" +| "-->" + +#data +<noembed><!--<noembed></noembed>--></noembed> +#errors +Line: 1 Col: 9 Unexpected start tag (noembed). Expected DOCTYPE. +Line: 1 Col: 45 Unexpected end tag (noembed). +#document +| <html> +| <head> +| <body> +| <noembed> +| "<!--<noembed>" +| "-->" + +#data +<!doctype html><table> + +#errors +Line 2 Col 0 Unexpected end of file. Expected table content. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| " +" + +#data +<!doctype html><table><td><span><font></span><span> +#errors +Line 1 Col 26 Unexpected table cell start tag (td) in the table body phase. +Line 1 Col 45 Unexpected end tag (span). +Line 1 Col 51 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <span> +| <font> +| <font> +| <span> + +#data +<!doctype html><form><table></form><form></table></form> +#errors +35: Stray end tag “form”. +41: Start tag “form” seen in “table”. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <table> +| <form> diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests17.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests17.dat new file mode 100644 index 0000000..7b555f8 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests17.dat @@ -0,0 +1,153 @@ +#data +<!doctype html><table><tbody><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><tr><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <table> +| <tbody> +| <tr> +| <td> + +#data +<!doctype html><table><tr><td><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <select> +| <td> + +#data +<!doctype html><table><tr><th><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <th> +| <select> +| <td> + +#data +<!doctype html><table><caption><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <select> +| <tbody> +| <tr> + +#data +<!doctype html><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><th> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><tbody> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><thead> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><tfoot> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><caption> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><table><tr></table>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| "a" diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests18.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests18.dat new file mode 100644 index 0000000..680e1f0 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests18.dat @@ -0,0 +1,269 @@ +#data +<!doctype html><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> + +#data +<!doctype html><table><tbody><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> + +#data +<!doctype html><table><tbody><tr><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><tbody><tr><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><td><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><caption><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><tr><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <tbody> +| <tr> +| <style> +| "</script>" + +#data +<!doctype html><table><tr><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <tbody> +| <tr> +| <script> +| "</style>" + +#data +<!doctype html><table><caption><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <style> +| "</script>" +| "abc" + +#data +<!doctype html><table><td><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <style> +| "</script>" +| "abc" + +#data +<!doctype html><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" + +#data +<!doctype html><table><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" +| <table> + +#data +<!doctype html><table><tr><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><frameset></frameset><noframes>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" + +#data +<!doctype html><frameset></frameset><noframes>abc</noframes><!--abc--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" +| <!-- abc --> + +#data +<!doctype html><frameset></frameset></html><noframes>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" + +#data +<!doctype html><frameset></frameset></html><noframes>abc</noframes><!--abc--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" +| <!-- abc --> + +#data +<!doctype html><table><tr></tbody><tfoot> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <tfoot> + +#data +<!doctype html><table><td><svg></svg>abc<td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <svg svg> +| "abc" +| <td> diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests19.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests19.dat new file mode 100644 index 0000000..0d62f5a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests19.dat @@ -0,0 +1,1237 @@ +#data +<!doctype html><math><mn DefinitionUrl="foo"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> +| <math mn> +| definitionURL="foo" + +#data +<!doctype html><html></p><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <!-- foo --> +| <head> +| <body> + +#data +<!doctype html><head></head></p><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <!-- foo --> +| <body> + +#data +<!doctype html><body><p><pre> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <pre> + +#data +<!doctype html><body><p><listing> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <listing> + +#data +<!doctype html><p><plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <plaintext> + +#data +<!doctype html><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <h1> + +#data +<!doctype html><form><isindex> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> + +#data +<!doctype html><isindex action="POST"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| action="POST" +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><isindex prompt="this is isindex"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "this is isindex" +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><isindex type="hidden"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| type="hidden" +| <hr> + +#data +<!doctype html><isindex name="foo"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><ruby><p><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <p> +| <rp> + +#data +<!doctype html><ruby><div><span><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <span> +| <rp> + +#data +<!doctype html><ruby><div><p><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <p> +| <rp> + +#data +<!doctype html><ruby><p><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <p> +| <rt> + +#data +<!doctype html><ruby><div><span><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <span> +| <rt> + +#data +<!doctype html><ruby><div><p><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <p> +| <rt> + +#data +<!doctype html><math/><foo> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> +| <foo> + +#data +<!doctype html><svg/><foo> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <svg svg> +| <foo> + +#data +<!doctype html><div></body><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| <!-- foo --> + +#data +<!doctype html><h1><div><h3><span></h1>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <h1> +| <div> +| <h3> +| <span> +| "foo" + +#data +<!doctype html><p></h3>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "foo" + +#data +<!doctype html><h3><li>abc</h2>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <h3> +| <li> +| "abc" +| "foo" + +#data +<!doctype html><table>abc<!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <!-- foo --> + +#data +<!doctype html><table> <!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| " " +| <!-- foo --> + +#data +<!doctype html><table> b <!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " b " +| <table> +| <!-- foo --> + +#data +<!doctype html><select><option><option> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> +| <option> + +#data +<!doctype html><select><option></optgroup> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> + +#data +<!doctype html><select><option></optgroup> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> + +#data +<!doctype html><p><math><mi><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mi> +| <p> +| <h1> + +#data +<!doctype html><p><math><mo><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mo> +| <p> +| <h1> + +#data +<!doctype html><p><math><mn><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mn> +| <p> +| <h1> + +#data +<!doctype html><p><math><ms><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math ms> +| <p> +| <h1> + +#data +<!doctype html><p><math><mtext><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mtext> +| <p> +| <h1> + +#data +<!doctype html><frameset></noframes> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html c=d><body></html><html a=b> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <body> + +#data +<!doctype html><html c=d><frameset></frameset></html><html a=b> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <!-- foo --> + +#data +<!doctype html><html><frameset></frameset></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| " " + +#data +<!doctype html><html><frameset></frameset></html>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html><p> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html></p> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<html><frameset></frameset></html><!doctype html> +#errors +#document +| <html> +| <head> +| <frameset> + +#data +<!doctype html><body><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> + +#data +<!doctype html><p><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><p>a<frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "a" + +#data +<!doctype html><p> <frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><pre><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <pre> + +#data +<!doctype html><listing><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <listing> + +#data +<!doctype html><li><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <li> + +#data +<!doctype html><dd><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dd> + +#data +<!doctype html><dt><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dt> + +#data +<!doctype html><button><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <button> + +#data +<!doctype html><applet><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <applet> + +#data +<!doctype html><marquee><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <marquee> + +#data +<!doctype html><object><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <object> + +#data +<!doctype html><table><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> + +#data +<!doctype html><area><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <area> + +#data +<!doctype html><basefont><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <basefont> +| <frameset> + +#data +<!doctype html><bgsound><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <bgsound> +| <frameset> + +#data +<!doctype html><br><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <br> + +#data +<!doctype html><embed><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <embed> + +#data +<!doctype html><img><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <img> + +#data +<!doctype html><input><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <input> + +#data +<!doctype html><keygen><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <keygen> + +#data +<!doctype html><wbr><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <wbr> + +#data +<!doctype html><hr><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <hr> + +#data +<!doctype html><textarea></textarea><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> + +#data +<!doctype html><xmp></xmp><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <xmp> + +#data +<!doctype html><iframe></iframe><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> + +#data +<!doctype html><select></select><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><svg></svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><math></math><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><svg><foreignObject><div> <frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><svg>a</svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <svg svg> +| "a" + +#data +<!doctype html><svg> </svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<html>aaa<frameset></frameset> +#errors +#document +| <html> +| <head> +| <body> +| "aaa" + +#data +<html> a <frameset></frameset> +#errors +#document +| <html> +| <head> +| <body> +| "a " + +#data +<!doctype html><div><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><div><body><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> + +#data +<!doctype html><p><math></p>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| "a" + +#data +<!doctype html><p><math><mn><span></p>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mn> +| <span> +| <p> +| "a" + +#data +<!doctype html><math></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> + +#data +<!doctype html><meta charset="ascii"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <meta> +| charset="ascii" +| <body> + +#data +<!doctype html><meta http-equiv="content-type" content="text/html;charset=ascii"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <meta> +| content="text/html;charset=ascii" +| http-equiv="content-type" +| <body> + +#data +<!doctype html><head><!--aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa--><meta charset="utf8"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <!-- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --> +| <meta> +| charset="utf8" +| <body> + +#data +<!doctype html><html a=b><head></head><html c=d> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <body> + +#data +<!doctype html><image/> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <img> + +#data +<!doctype html>a<i>b<table>c<b>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "a" +| <i> +| "bc" +| <b> +| "de" +| "f" +| <table> + +#data +<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" +| <table> + +#data +<!doctype html><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" + +#data +<!doctype html><table><i>a<b>b<div>c</i> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <i> +| "c" +| <table> + +#data +<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" +| <table> + +#data +<!doctype html><table><i>a<div>b<tr>c<b>d</i>e +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <div> +| "b" +| <i> +| "c" +| <b> +| "d" +| <b> +| "e" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><td><table><i>a<div>b<b>c</i>d +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <i> +| "a" +| <div> +| <i> +| "b" +| <b> +| "c" +| <b> +| "d" +| <table> + +#data +<!doctype html><body><bgsound> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <bgsound> + +#data +<!doctype html><body><basefont> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <basefont> + +#data +<!doctype html><a><b></a><basefont> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <a> +| <b> +| <basefont> + +#data +<!doctype html><a><b></a><bgsound> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <a> +| <b> +| <bgsound> + +#data +<!doctype html><figcaption><article></figcaption>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <figcaption> +| <article> +| "a" + +#data +<!doctype html><summary><article></summary>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <summary> +| <article> +| "a" + +#data +<!doctype html><p><a><plaintext>b +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <a> +| <plaintext> +| <a> +| "b" + +#data +<!DOCTYPE html><div>a<a></div>b<p>c</p>d +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| "a" +| <a> +| <a> +| "b" +| <p> +| "c" +| "d" diff --git a/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests2.dat b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests2.dat new file mode 100644 index 0000000..60d8592 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/html/testdata/webkit/tests2.dat @@ -0,0 +1,763 @@ +#data +<!DOCTYPE html>Test +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "Test" + +#data +<textarea>test</div>test +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +Line: 1 Col: 24 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <textarea> +| "test</div>test" + +#data +<table><td> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase. +Line: 1 Col: 11 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> + +#data +<table><td>test</tbody></table> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase. +#document +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| "test" + +#data +<frame>test +#errors +Line: 1 Col: 7 Unexpected start tag (frame). Expected DOCTYPE. +Line: 1 Col: 7 Unexpected start tag frame. Ignored. +#document +| <html> +| <head> +| <body> +| "test" + +#data +<!DOCTYPE html><frameset>test +#errors +Line: 1 Col: 29 Unepxected characters in the frameset phase. Characters ignored. +Line: 1 Col: 29 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!DOCTYPE html><frameset><!DOCTYPE html> +#errors +Line: 1 Col: 40 Unexpected DOCTYPE. Ignored. +Line: 1 Col: 40 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!DOCTYPE html><font><p><b>test</font> +#errors +Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm. +Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <font> +| <p> +| <font> +| <b> +| "test" + +#data +<!DOCTYPE html><dt><div><dd> +#errors +Line: 1 Col: 28 Missing end tag (div, dt). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dt> +| <div> +| <dd> + +#data +<script></x +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</x" +| <body> + +#data +<table><plaintext><td> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 18 Unexpected start tag (plaintext) in table context caused voodoo mode. +Line: 1 Col: 22 Unexpected end of file. Expected table content. +#document +| <html> +| <head> +| <body> +| <plaintext> +| "<td>" +| <table> + +#data +<plaintext></plaintext> +#errors +Line: 1 Col: 11 Unexpected start tag (plaintext). Expected DOCTYPE. +Line: 1 Col: 23 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" + +#data +<!DOCTYPE html><table><tr>TEST +#errors +Line: 1 Col: 30 Unexpected non-space characters in table context caused voodoo mode. +Line: 1 Col: 30 Unexpected end of file. Expected table content. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "TEST" +| <table> +| <tbody> +| <tr> + +#data +<!DOCTYPE html><body t1=1><body t2=2><body t3=3 t4=4> +#errors +Line: 1 Col: 37 Unexpected start tag (body). +Line: 1 Col: 53 Unexpected start tag (body). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| t1="1" +| t2="2" +| t3="3" +| t4="4" + +#data +</b test +#errors +Line: 1 Col: 8 Unexpected end of file in attribute name. +Line: 1 Col: 8 End tag contains unexpected attributes. +Line: 1 Col: 8 Unexpected end tag (b). Expected DOCTYPE. +Line: 1 Col: 8 Unexpected end tag (b) after the (implied) root element. +#document +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html></b test<b &=&>X +#errors +Line: 1 Col: 32 Named entity didn't end with ';'. +Line: 1 Col: 33 End tag contains unexpected attributes. +Line: 1 Col: 33 Unexpected end tag (b) after the (implied) root element. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "X" + +#data +<!doctypehtml><scrIPt type=text/x-foobar;baz>X</SCRipt +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +Line: 1 Col: 54 Unexpected end of file in the tag name. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| type="text/x-foobar;baz" +| "X</SCRipt" +| <body> + +#data +& +#errors +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&" + +#data +&# +#errors +Line: 1 Col: 1 Numeric entity expected. Got end of file instead. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#" + +#data +&#X +#errors +Line: 1 Col: 3 Numeric entity expected but none found. +Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#X" + +#data +&#x +#errors +Line: 1 Col: 3 Numeric entity expected but none found. +Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#x" + +#data +- +#errors +Line: 1 Col: 4 Numeric entity didn't end with ';'. +Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "-" + +#data +&x-test +#errors +Line: 1 Col: 1 Named entity expected. Got none. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&x-test" + +#data +<!doctypehtml><p><li> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <li> + +#data +<!doctypehtml><p><dt> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <dt> + +#data +<!doctypehtml><p><dd> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <dd> + +#data +<!doctypehtml><p><form> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +Line: 1 Col: 23 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <form> + +#data +<!DOCTYPE html><p></P>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "X" + +#data +& +#errors +Line: 1 Col: 4 Named entity didn't end with ';'. +Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&" + +#data +&AMp; +#errors +Line: 1 Col: 1 Named entity expected. Got none. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&AMp;" + +#data +<!DOCTYPE html><html><head></head><body><thisISasillyTESTelementNameToMakeSureCrazyTagNamesArePARSEDcorrectLY> +#errors +Line: 1 Col: 110 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <thisisasillytestelementnametomakesurecrazytagnamesareparsedcorrectly> + +#data +<!DOCTYPE html>X</body>X +#errors +Line: 1 Col: 24 Unexpected non-space characters in the after body phase. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "XX" + +#data +<!DOCTYPE html><!-- X +#errors +Line: 1 Col: 21 Unexpected end of file in comment. +#document +| <!DOCTYPE html> +| <!-- X --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><table><caption>test TEST</caption><td>test +#errors +Line: 1 Col: 54 Unexpected table cell start tag (td) in the table body phase. +Line: 1 Col: 58 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| "test TEST" +| <tbody> +| <tr> +| <td> +| "test" + +#data +<!DOCTYPE html><select><option><optgroup> +#errors +Line: 1 Col: 41 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> +| <optgroup> + +#data +<!DOCTYPE html><select><optgroup><option></optgroup><option><select><option> +#errors +Line: 1 Col: 68 Unexpected select start tag in the select phase treated as select end tag. +Line: 1 Col: 76 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> +| <option> +| <option> +| <option> + +#data +<!DOCTYPE html><select><optgroup><option><optgroup> +#errors +Line: 1 Col: 51 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> +| <option> +| <optgroup> + +#data +<!DOCTYPE html><datalist><option>foo</datalist>bar +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <datalist> +| <option> +| "foo" +| "bar" + +#data +<!DOCTYPE html><font><input><input></font> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <font> +| <input> +| <input> + +#data +<!DOCTYPE html><!-- XXX - XXX --> +#errors +#document +| <!DOCTYPE html> +| <!-- XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><!-- XXX - XXX +#errors +Line: 1 Col: 29 Unexpected end of file in comment (-) +#document +| <!DOCTYPE html> +| <!-- XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><!-- XXX - XXX - XXX --> +#errors +#document +| <!DOCTYPE html> +| <!-- XXX - XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<isindex test=x name=x> +#errors +Line: 1 Col: 23 Unexpected start tag (isindex). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected start tag isindex. Don't use it! +#document +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| test="x" +| <hr> + +#data +test +test +#errors +Line: 2 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "test +test" + +#data +<!DOCTYPE html><body><title>test</body> +#errors +#document +| +| +| +| +| +| "test</body>" + +#data +<!DOCTYPE html><body><title>X +#errors +#document +| +| +| +| +| +| "X" +| <meta> +| name="z" +| <link> +| rel="foo" +| <style> +| " +x { content:"</style" } " + +#data +<!DOCTYPE html><select><optgroup></optgroup></select> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> + +#data + + +#errors +Line: 2 Col: 1 Unexpected End of file. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html> <html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><script> +</script> <title>x +#errors +#document +| +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected start tag (script) that can be in head. Moved. +#document +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +Line: 1 Col: 28 Unexpected start tag (style) that can be in head. Moved. +#document +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +#document +| +| +| +| +| "x" +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (style). +#document +| +| +| --> x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +|

+#errors +#document +| +| +| +| +| +| ddd +#errors +#document +| +| +| +#errors +#document +| +| +| +| +|
  • +| +| ", + "