<commit_before>package wikiio

import (
	. "github.com/OUCC/syaro/logger"
	"github.com/OUCC/syaro/setting"
	"github.com/OUCC/syaro/util"

	"github.com/libgit2/git2go"
	"gopkg.in/fsnotify.v1"

	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var (
	WikiRoot    *WikiFile
	searchIndex map[string][]*WikiFile
	repo        *git.Repository

	// file system watcher
	watcher *fsnotify.Watcher

	// if true, BuildIndex is called
	refreshRequired = true
)

var (
	ErrNotExist     = errors.New("file not exist")
	ErrNotFound     = errors.New("file not found")
	ErrRepoNotReady = errors.New("repository contains uncommited changes")
)

func OpenRepository() error {
	var err error
	repo, err = git.OpenRepository(setting.WikiRoot)
	if err != nil {
		return err
	}

	// check if repository contains uncommited changes
	opt := new(git.StatusOptions)
	opt.Flags = git.StatusOptIncludeUntracked
	opt.Show = git.StatusShowIndexAndWorkdir
	statuses, err := repo.StatusList(opt)
	if err != nil {
		return err
	} else {
		defer statuses.Free()
	}
	if c, _ := statuses.EntryCount(); c != 0 {
		return ErrRepoNotReady
	}

	return nil
}

func InitWatcher() {
	var err error
	watcher, err = fsnotify.NewWatcher()
	if err != nil {
		Log.Fatal(err)
	}

	// event loop for watcher
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				Log.Debug("%s", event)
				switch {
				case event.Op&fsnotify.Create != 0:
					Log.Info("New file Created (%s)", event.Name)
					refreshRequired = true

				case event.Op&fsnotify.Remove != 0:
					Log.Info("File removed (%s)", event.Name)
					refreshRequired = true
				}

			case err := <-watcher.Errors:
				Log.Fatal(err)
			}
		}
	}()

	filepath.Walk(setting.WikiRoot, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			Log.Error(err.Error())
		}

		// dont add hidden dir (ex. .git)
		if info.IsDir() && !strings.Contains(path, "/.") && !strings.HasPrefix(path, ".") {
			watcher.Add(path)
			Log.Debug("%s added to watcher", path)
		}

		return nil
	})
}

func CloseWatcher() {
	watcher.Close()
}

// must be called after setting.WikiRoot is set
func buildIndex() {
	Log.Debug("Index building start")

	info, err := os.Stat(setting.WikiRoot)
	if err != nil {
		Log.Fatal(err)
	}

	WikiRoot = &WikiFile{
		parentDir: nil,
		wikiPath:  "/",
		fileInfo:  info,
	}
	searchIndex = make(map[string][]*WikiFile)

	// anonymous recursive function
	var walkfunc func(*WikiFile)
	walkfunc = func(dir *WikiFile) {
		infos, _ := ioutil.ReadDir(filepath.Join(setting.WikiRoot, dir.WikiPath()))

		dir.files = make([]*WikiFile, 0, len(infos))
		for _, info := range infos {
			// skip hidden file
			if info.Name()[:1] == "." {
				continue
			}

			file := &WikiFile{
				parentDir: dir,
				wikiPath:  filepath.Join(dir.WikiPath(), info.Name()),
				fileInfo:  info,
			}
			dir.files = append(dir.files, file)

			// register to searchIndex
			elem, present := searchIndex[file.Name()]
			if present {
				searchIndex[file.Name()] = append(elem, file)
			} else {
				searchIndex[file.Name()] = []*WikiFile{file}
			}

			elem, present = searchIndex[file.NameWithoutExt()]
			if present {
				searchIndex[file.NameWithoutExt()] = append(elem, file)
			} else {
				searchIndex[file.NameWithoutExt()] = []*WikiFile{file}
			}

			if info.IsDir() {
				walkfunc(file)
			}
		}
	}
	walkfunc(WikiRoot)

	Log.Debug("Index building end")

	refreshRequired = false
}

func Load(wpath string) (*WikiFile, error) {
	Log.Debug("wpath: %s", wpath)

	if refreshRequired {
		buildIndex()
	}

	// wiki root
	if wpath == "/" || wpath == "." || wpath == "" {
		return WikiRoot, nil
	}

	sl := strings.Split(wpath, "/")
	ret := WikiRoot
	for _, s := range sl {
		if s == "" {
			continue
		}

		tmp := ret
		for _, f := range ret.Files() {
			if f.Name() == s || util.RemoveExt(f.Name()) == s {
				ret = f
				break
			}
		}
		// not found
		if ret == tmp {
			Log.Debug("wikiio.Load: not exist")
			return nil, ErrNotExist
		}
	}

	return ret, nil
}

func Search(name string) ([]*WikiFile, error) {
	Log.Debug("name: %s", name)

	if refreshRequired {
		buildIndex()
	}

	files, present := searchIndex[name]
	if !present {
		Log.Debug("not found")
		return nil, ErrNotFound
	}

	// for debug output
	found := make([]string, len(files))
	for i := 0; i < len(found); i++ {
		found[i] = files[i].WikiPath()
	}
	Log.Debug("found %v", found)

	return files, nil
}

func Create(wpath string) error {
	Log.Debug("wpath: %s", wpath)

	initialText := util.RemoveExt(filepath.Base(wpath)) + "\n====\n"

	// check if file is already exists
	file, _ := Load(wpath)
	if file != nil {
		// if exists, return error
		return os.ErrExist
	}

	if !util.IsMarkdown(wpath) {
		wpath += ".md"
	}

	path := filepath.Join(setting.WikiRoot, wpath)
	os.MkdirAll(filepath.Dir(path), 0755)
	err := ioutil.WriteFile(path, []byte(initialText), 0644)
	if err != nil {
		Log.Debug(err.Error())
		return err
	}

	// git commit
	if setting.GitMode {
		// get signature
		sig := getDefaultSignature()

		commit, err := commitChange(
			func(idx *git.Index) error {
				if err := idx.AddByPath(wpath[1:]); err != nil {
					return err
				}
				return nil
			},
			sig,
			"Created "+filepath.Base(wpath))
		if err != nil {
			Log.Error("Git error: %s", err)
			return nil // dont send git error to client
		}
		defer commit.Free()
		logCommit(commit)
	}
	refreshRequired = true

	return nil
}

func Rename(oldpath string, newpath string) error {
	Log.Debug("oldpath: %s, newpath: %s", oldpath, newpath)

	f, err := Load(oldpath)
	if err != nil {
		return err
	}

	if !f.IsDir() && f.IsMarkdown() && !util.IsMarkdown(newpath) {
		newpath += ".md"
	}

	path := filepath.Join(setting.WikiRoot, newpath)
	os.MkdirAll(filepath.Dir(path), 0755)
	if err := os.Rename(f.FilePath(), path); err != nil {
		Log.Debug("can't rename: %s", err)
		return err
	}

	// git commit
	if setting.GitMode {
		// get signature
		sig := getDefaultSignature()

		commit, err := commitChange(
			func(idx *git.Index) error {
				if err := idx.RemoveByPath(oldpath[1:]); err != nil {
					return err
				}
				if err := idx.AddByPath(newpath[1:]); err != nil {
					return err
				}
				return nil
			},
			sig,
			fmt.Sprintf("Renamed %s\n\n%s -> %s", filepath.Base(oldpath), oldpath, newpath))

		if err != nil {
			Log.Error("Git error: %s", err)
			return nil // dont send git error to client
		}
		defer commit.Free()
		logCommit(commit)
	}
	refreshRequired = true

	return nil
}
<commit_msg>[CLE] clean wikiio.go<commit_after>package wikiio

import (
	. "github.com/OUCC/syaro/logger"
	"github.com/OUCC/syaro/setting"
	"github.com/OUCC/syaro/util"

	"github.com/libgit2/git2go"
	"gopkg.in/fsnotify.v1"

	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var (
	WikiRoot    *WikiFile
	searchIndex map[string][]*WikiFile

	// git repository
	repo *git.Repository

	// file system watcher
	watcher *fsnotify.Watcher

	// if true, BuildIndex is called
	refreshRequired = true
)

var (
	ErrNotExist     = errors.New("file not exist")
	ErrNotFound     = errors.New("file not found")
	ErrRepoNotReady = errors.New("repository contains uncommited changes")
)

func OpenRepository() error {
	var err error
	repo, err = git.OpenRepository(setting.WikiRoot)
	if err != nil {
		return err
	}

	// check if repository contains uncommited changes
	opt := new(git.StatusOptions)
	opt.Flags = git.StatusOptIncludeUntracked
	opt.Show = git.StatusShowIndexAndWorkdir
	statuses, err := repo.StatusList(opt)
	if err != nil {
		return err
	} else {
		defer statuses.Free()
	}
	if c, _ := statuses.EntryCount(); c != 0 {
		return ErrRepoNotReady
	}

	return nil
}

func InitWatcher() {
	var err error
	watcher, err = fsnotify.NewWatcher()
	if err != nil {
		Log.Fatal(err)
	}

	// event loop for watcher
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				Log.Debug("%s", event)
				switch {
				case event.Op&fsnotify.Create != 0:
					Log.Info("New file Created (%s)", event.Name)
					refreshRequired = true

				case event.Op&fsnotify.Remove != 0:
					Log.Info("File removed (%s)", event.Name)
					refreshRequired = true
				}

			case err := <-watcher.Errors:
				Log.Fatal(err)
			}
		}
	}()

	filepath.Walk(setting.WikiRoot, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			Log.Error(err.Error())
		}

		// dont add hidden dir (ex. .git)
		if info.IsDir() && !strings.Contains(path, "/.") && !strings.HasPrefix(path, ".") {
			watcher.Add(path)
			Log.Debug("%s added to watcher", path)
		}

		return nil
	})
}

func CloseWatcher() {
	watcher.Close()
}

// must be called after setting.WikiRoot is set
func buildIndex() {
	Log.Debug("Index building start")

	info, err := os.Stat(setting.WikiRoot)
	if err != nil {
		Log.Fatal(err)
	}

	WikiRoot = &WikiFile{
		parentDir: nil,
		wikiPath:  "/",
		fileInfo:  info,
	}
	searchIndex = make(map[string][]*WikiFile)

	// anonymous recursive function
	var walkfunc func(*WikiFile)
	walkfunc = func(dir *WikiFile) {
		infos, _ := ioutil.ReadDir(filepath.Join(setting.WikiRoot, dir.WikiPath()))

		dir.files = make([]*WikiFile, 0, len(infos))
		for _, info := range infos {
			// skip hidden file
			if info.Name()[:1] == "." {
				continue
			}

			file := &WikiFile{
				parentDir: dir,
				wikiPath:  filepath.Join(dir.WikiPath(), info.Name()),
				fileInfo:  info,
			}
			dir.files = append(dir.files, file)

			// register to searchIndex
			elem, present := searchIndex[file.Name()]
			if present {
				searchIndex[file.Name()] = append(elem, file)
			} else {
				searchIndex[file.Name()] = []*WikiFile{file}
			}

			elem, present = searchIndex[file.NameWithoutExt()]
			if present {
				searchIndex[file.NameWithoutExt()] = append(elem, file)
			} else {
				searchIndex[file.NameWithoutExt()] = []*WikiFile{file}
			}

			if info.IsDir() {
				walkfunc(file)
			}
		}
	}
	walkfunc(WikiRoot)

	Log.Debug("Index building end")
	Log.Info("File index refreshed")

	refreshRequired = false
}

func Load(wpath string) (*WikiFile, error) {
	Log.Debug("wpath: %s", wpath)

	if refreshRequired {
		buildIndex()
	}

	// wiki root
	if wpath == "/" || wpath == "." || wpath == "" {
		return WikiRoot, nil
	}

	sl := strings.Split(wpath, "/")
	ret := WikiRoot
	for _, s := range sl {
		if s == "" {
			continue
		}

		tmp := ret
		for _, f := range ret.Files() {
			if f.Name() == s || util.RemoveExt(f.Name()) == s {
				ret = f
				break
			}
		}
		// not found
		if ret == tmp {
			Log.Debug("wikiio.Load: not exist")
			return nil, ErrNotExist
		}
	}

	return ret, nil
}

func Search(name string) ([]*WikiFile, error) {
	Log.Debug("name: %s", name)

	if refreshRequired {
		buildIndex()
	}

	files, present := searchIndex[name]
	if !present {
		Log.Debug("not found")
		return nil, ErrNotFound
	}

	// for debug output
	found := make([]string, len(files))
	for i := 0; i < len(found); i++ {
		found[i] = files[i].WikiPath()
	}
	Log.Debug("found %v", found)

	return files, nil
}

func Create(wpath string) error {
	Log.Debug("wpath: %s", wpath)

	initialText := util.RemoveExt(filepath.Base(wpath)) + "\n====\n"

	// check if file is already exists
	file, _ := Load(wpath)
	if file != nil {
		// if exists, return error
		return os.ErrExist
	}

	if !util.IsMarkdown(wpath) {
		wpath += ".md"
	}

	path := filepath.Join(setting.WikiRoot, wpath)
	os.MkdirAll(filepath.Dir(path), 0755)
	err := ioutil.WriteFile(path, []byte(initialText), 0644)
	if err != nil {
		Log.Debug(err.Error())
		return err
	}

	refreshRequired = true

	// git commit
	if setting.GitMode {
		// get signature
		sig := getDefaultSignature()

		commit, err := commitChange(
			func(idx *git.Index) error {
				if err := idx.AddByPath(wpath[1:]); err != nil {
					return err
				}
				return nil
			},
			sig,
			"Created "+filepath.Base(wpath))
		if err != nil {
			Log.Error("Git error: %s", err)
			return nil // dont send git error to client
		}
		defer commit.Free()
		logCommit(commit)
	}

	return nil
}

func Rename(oldpath string, newpath string) error {
	Log.Debug("oldpath: %s, newpath: %s", oldpath, newpath)

	f, err := Load(oldpath)
	if err != nil {
		return err
	}

	if !f.IsDir() && f.IsMarkdown() && !util.IsMarkdown(newpath) {
		newpath += ".md"
	}

	path := filepath.Join(setting.WikiRoot, newpath)
	os.MkdirAll(filepath.Dir(path), 0755)
	if err := os.Rename(f.FilePath(), path); err != nil {
		Log.Debug("can't rename: %s", err)
		return err
	}

	refreshRequired = true

	// git commit
	if setting.GitMode {
		// get signature
		sig := getDefaultSignature()

		commit, err := commitChange(
			func(idx *git.Index) error {
				if err := idx.RemoveByPath(oldpath[1:]); err != nil {
					return err
				}
				if err := idx.AddByPath(newpath[1:]); err != nil {
					return err
				}
				return nil
			},
			sig,
			fmt.Sprintf("Renamed %s\n\n%s -> %s", filepath.Base(oldpath), oldpath, newpath))

		if err != nil {
			Log.Error("Git error: %s", err)
			return nil // dont send git error to client
		}
		defer commit.Free()
		logCommit(commit)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2011, 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package charm_test

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"syscall"

	jc "github.com/juju/testing/checkers"
	gc "launchpad.net/gocheck"
	"launchpad.net/goyaml"

	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/utils/set"
)

type BundleSuite struct {
	repo       *testing.Repo
	bundlePath string
}

var _ = gc.Suite(&BundleSuite{})

func (s *BundleSuite) SetUpSuite(c *gc.C) {
	s.bundlePath = testing.Charms.BundlePath(c.MkDir(), "dummy")
}

var dummyManifest = []string{
	"config.yaml",
	"empty",
	"hooks",
	"hooks/install",
	"metadata.yaml",
	"revision",
	"src",
	"src/hello.c",
}

func (s *BundleSuite) TestReadBundle(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)
	checkDummy(c, bundle, s.bundlePath)
}

func (s *BundleSuite) TestReadBundleWithoutConfig(c *gc.C) {
	path := testing.Charms.BundlePath(c.MkDir(), "varnish")
	bundle, err := charm.ReadBundle(path)
	c.Assert(err, gc.IsNil)

	// A lacking config.yaml file still causes a proper
	// Config value to be returned.
	c.Assert(bundle.Config().Options, gc.HasLen, 0)
}

func (s *BundleSuite) TestReadBundleBytes(c *gc.C) {
	data, err := ioutil.ReadFile(s.bundlePath)
	c.Assert(err, gc.IsNil)

	bundle, err := charm.ReadBundleBytes(data)
	c.Assert(err, gc.IsNil)
	checkDummy(c, bundle, "")
}

func (s *BundleSuite) TestManifest(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)
	manifest, err := bundle.Manifest()
	c.Assert(err, gc.IsNil)
	c.Assert(manifest, jc.DeepEquals, set.NewStrings(dummyManifest...))
}

func (s *BundleSuite) TestManifestNoRevision(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)
	dirPath := c.MkDir()
	err = bundle.ExpandTo(dirPath)
	c.Assert(err, gc.IsNil)
	err = os.Remove(filepath.Join(dirPath, "revision"))
	c.Assert(err, gc.IsNil)

	bundle = extBundleDir(c, dirPath)
	manifest, err := bundle.Manifest()
	c.Assert(err, gc.IsNil)
	c.Assert(manifest, gc.DeepEquals, set.NewStrings(dummyManifest...))
}

func (s *BundleSuite) TestManifestSymlink(c *gc.C) {
	srcPath := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	if err := os.Symlink("../target", filepath.Join(srcPath, "hooks/symlink")); err != nil {
		c.Skip("cannot symlink")
	}
	expected := append([]string{"hooks/symlink"}, dummyManifest...)

	bundle := bundleDir(c, srcPath)
	manifest, err := bundle.Manifest()
	c.Assert(err, gc.IsNil)
	c.Assert(manifest, gc.DeepEquals, set.NewStrings(expected...))
}

func (s *BundleSuite) TestExpandTo(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	dir, err := charm.ReadDir(path)
	c.Assert(err, gc.IsNil)
	checkDummy(c, dir, path)
}

func (s *BundleSuite) prepareBundle(c *gc.C, charmDir *charm.Dir, bundlePath string) {
	file, err := os.Create(bundlePath)
	c.Assert(err, gc.IsNil)
	defer file.Close()
	zipw := zip.NewWriter(file)
	defer zipw.Close()

	h := &zip.FileHeader{Name: "revision"}
	h.SetMode(syscall.S_IFREG | 0644)
	w, err := zipw.CreateHeader(h)
	c.Assert(err, gc.IsNil)
	_, err = w.Write([]byte(strconv.Itoa(charmDir.Revision())))

	h = &zip.FileHeader{Name: "metadata.yaml", Method: zip.Deflate}
	h.SetMode(0644)
	w, err = zipw.CreateHeader(h)
	c.Assert(err, gc.IsNil)
	data, err := goyaml.Marshal(charmDir.Meta())
	c.Assert(err, gc.IsNil)
	_, err = w.Write(data)
	c.Assert(err, gc.IsNil)

	for name := range charmDir.Meta().Hooks() {
		hookName := filepath.Join("hooks", name)
		h = &zip.FileHeader{
			Name:   hookName,
			Method: zip.Deflate,
		}
		// Force it non-executable
		h.SetMode(0644)
		w, err := zipw.CreateHeader(h)
		c.Assert(err, gc.IsNil)
		_, err = w.Write([]byte("not important"))
		c.Assert(err, gc.IsNil)
	}
}

func (s *BundleSuite) TestExpandToSetsHooksExecutable(c *gc.C) {
	charmDir := testing.Charms.ClonedDir(c.MkDir(), "all-hooks")
	// Bundle manually, so we can check ExpandTo(), unaffected
	// by BundleTo()'s behavior
	bundlePath := filepath.Join(c.MkDir(), "bundle.charm")
	s.prepareBundle(c, charmDir, bundlePath)
	bundle, err := charm.ReadBundle(bundlePath)
	c.Assert(err, gc.IsNil)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	_, err = charm.ReadDir(path)
	c.Assert(err, gc.IsNil)

	for name := range bundle.Meta().Hooks() {
		hookName := string(name)
		info, err := os.Stat(filepath.Join(path, "hooks", hookName))
		c.Assert(err, gc.IsNil)
		perm := info.Mode() & 0777
		c.Assert(perm&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName))
	}
}

func (s *BundleSuite) TestBundleFileModes(c *gc.C) {
	// Apply subtler mode differences than can be expressed in Bazaar.
	srcPath := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	modes := []struct {
		path string
		mode os.FileMode
	}{
		{"hooks/install", 0751},
		{"empty", 0750},
		{"src/hello.c", 0614},
	}
	for _, m := range modes {
		err := os.Chmod(filepath.Join(srcPath, m.path), m.mode)
		c.Assert(err, gc.IsNil)
	}
	var haveSymlinks = true
	if err := os.Symlink("../target", filepath.Join(srcPath, "hooks/symlink")); err != nil {
		haveSymlinks = false
	}

	// Bundle and extract the charm to a new directory.
	bundle := bundleDir(c, srcPath)
	path := c.MkDir()
	err := bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	// Check sensible file modes once round-tripped.
	info, err := os.Stat(filepath.Join(path, "src", "hello.c"))
	c.Assert(err, gc.IsNil)
	c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0644))
	c.Assert(info.Mode()&os.ModeType, gc.Equals, os.FileMode(0))

	info, err = os.Stat(filepath.Join(path, "hooks", "install"))
	c.Assert(err, gc.IsNil)
	c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0755))
	c.Assert(info.Mode()&os.ModeType, gc.Equals, os.FileMode(0))

	info, err = os.Stat(filepath.Join(path, "empty"))
	c.Assert(err, gc.IsNil)
	c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0755))

	if haveSymlinks {
		target, err := os.Readlink(filepath.Join(path, "hooks", "symlink"))
		c.Assert(err, gc.IsNil)
		c.Assert(target, gc.Equals, "../target")
	}
}

func (s *BundleSuite) TestBundleRevisionFile(c *gc.C) {
	charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	revPath := filepath.Join(charmDir, "revision")

	// Missing revision file
	err := os.Remove(revPath)
	c.Assert(err, gc.IsNil)

	bundle := extBundleDir(c, charmDir)
	c.Assert(bundle.Revision(), gc.Equals, 0)

	// Missing revision file with old revision in metadata
	file, err := os.OpenFile(filepath.Join(charmDir, "metadata.yaml"), os.O_WRONLY|os.O_APPEND, 0)
	c.Assert(err, gc.IsNil)
	_, err = file.Write([]byte("\nrevision: 1234\n"))
	c.Assert(err, gc.IsNil)

	bundle = extBundleDir(c, charmDir)
	c.Assert(bundle.Revision(), gc.Equals, 1234)

	// Revision file with bad content
	err = ioutil.WriteFile(revPath, []byte("garbage"), 0666)
	c.Assert(err, gc.IsNil)

	path := extBundleDirPath(c, charmDir)
	bundle, err = charm.ReadBundle(path)
	c.Assert(err, gc.ErrorMatches, "invalid revision file")
	c.Assert(bundle, gc.IsNil)
}

func (s *BundleSuite) TestBundleSetRevision(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)

	c.Assert(bundle.Revision(), gc.Equals, 1)
	bundle.SetRevision(42)
	c.Assert(bundle.Revision(), gc.Equals, 42)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	dir, err := charm.ReadDir(path)
	c.Assert(err, gc.IsNil)
	c.Assert(dir.Revision(), gc.Equals, 42)
}

func (s *BundleSuite) TestExpandToWithBadLink(c *gc.C) {
	charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	badLink := filepath.Join(charmDir, "hooks", "badlink")

	// Symlink targeting a path outside of the charm.
	err := os.Symlink("../../target", badLink)
	c.Assert(err, gc.IsNil)

	bundle := extBundleDir(c, charmDir)
	c.Assert(err, gc.IsNil)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.ErrorMatches, `cannot extract "hooks/badlink": symlink "../../target" leads out of scope`)

	// Symlink targeting an absolute path.
	os.Remove(badLink)
	err = os.Symlink("/target", badLink)
	c.Assert(err, gc.IsNil)

	bundle = extBundleDir(c, charmDir)
	c.Assert(err, gc.IsNil)

	path = filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.ErrorMatches, `cannot extract "hooks/badlink": symlink "/target" is absolute`)
}

func extBundleDirPath(c *gc.C, dirpath string) string {
	path := filepath.Join(c.MkDir(), "bundle.charm")
	c.Logf("%#v", fmt.Sprintf("cd %s; zip --fifo --symlinks -r %s .", dirpath, path))
	cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("cd %s; zip --fifo --symlinks -r %s .", dirpath, path))
	output, err := cmd.CombinedOutput()
	c.Assert(err, gc.IsNil, gc.Commentf("Command output: %s", output))
	return path
}

func extBundleDir(c *gc.C, dirpath string) *charm.Bundle {
	path := extBundleDirPath(c, dirpath)
	bundle, err := charm.ReadBundle(path)
	c.Assert(err, gc.IsNil)
	return bundle
}

func bundleDir(c *gc.C, dirpath string) *charm.Bundle {
	dir, err := charm.ReadDir(dirpath)
	c.Assert(err, gc.IsNil)
	buf := new(bytes.Buffer)
	err = dir.BundleTo(buf)
	c.Assert(err, gc.IsNil)
	bundle, err := charm.ReadBundleBytes(buf.Bytes())
	c.Assert(err, gc.IsNil)
	return bundle
}
<commit_msg>Remove debug line<commit_after>// Copyright 2011, 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package charm_test

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"syscall"

	jc "github.com/juju/testing/checkers"
	gc "launchpad.net/gocheck"
	"launchpad.net/goyaml"

	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/utils/set"
)

type BundleSuite struct {
	repo       *testing.Repo
	bundlePath string
}

var _ = gc.Suite(&BundleSuite{})

func (s *BundleSuite) SetUpSuite(c *gc.C) {
	s.bundlePath = testing.Charms.BundlePath(c.MkDir(), "dummy")
}

var dummyManifest = []string{
	"config.yaml",
	"empty",
	"hooks",
	"hooks/install",
	"metadata.yaml",
	"revision",
	"src",
	"src/hello.c",
}

func (s *BundleSuite) TestReadBundle(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)
	checkDummy(c, bundle, s.bundlePath)
}

func (s *BundleSuite) TestReadBundleWithoutConfig(c *gc.C) {
	path := testing.Charms.BundlePath(c.MkDir(), "varnish")
	bundle, err := charm.ReadBundle(path)
	c.Assert(err, gc.IsNil)

	// A lacking config.yaml file still causes a proper
	// Config value to be returned.
	c.Assert(bundle.Config().Options, gc.HasLen, 0)
}

func (s *BundleSuite) TestReadBundleBytes(c *gc.C) {
	data, err := ioutil.ReadFile(s.bundlePath)
	c.Assert(err, gc.IsNil)

	bundle, err := charm.ReadBundleBytes(data)
	c.Assert(err, gc.IsNil)
	checkDummy(c, bundle, "")
}

func (s *BundleSuite) TestManifest(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)
	manifest, err := bundle.Manifest()
	c.Assert(err, gc.IsNil)
	c.Assert(manifest, jc.DeepEquals, set.NewStrings(dummyManifest...))
}

func (s *BundleSuite) TestManifestNoRevision(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)
	dirPath := c.MkDir()
	err = bundle.ExpandTo(dirPath)
	c.Assert(err, gc.IsNil)
	err = os.Remove(filepath.Join(dirPath, "revision"))
	c.Assert(err, gc.IsNil)

	bundle = extBundleDir(c, dirPath)
	manifest, err := bundle.Manifest()
	c.Assert(err, gc.IsNil)
	c.Assert(manifest, gc.DeepEquals, set.NewStrings(dummyManifest...))
}

func (s *BundleSuite) TestManifestSymlink(c *gc.C) {
	srcPath := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	if err := os.Symlink("../target", filepath.Join(srcPath, "hooks/symlink")); err != nil {
		c.Skip("cannot symlink")
	}
	expected := append([]string{"hooks/symlink"}, dummyManifest...)

	bundle := bundleDir(c, srcPath)
	manifest, err := bundle.Manifest()
	c.Assert(err, gc.IsNil)
	c.Assert(manifest, gc.DeepEquals, set.NewStrings(expected...))
}

func (s *BundleSuite) TestExpandTo(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	dir, err := charm.ReadDir(path)
	c.Assert(err, gc.IsNil)
	checkDummy(c, dir, path)
}

func (s *BundleSuite) prepareBundle(c *gc.C, charmDir *charm.Dir, bundlePath string) {
	file, err := os.Create(bundlePath)
	c.Assert(err, gc.IsNil)
	defer file.Close()
	zipw := zip.NewWriter(file)
	defer zipw.Close()

	h := &zip.FileHeader{Name: "revision"}
	h.SetMode(syscall.S_IFREG | 0644)
	w, err := zipw.CreateHeader(h)
	c.Assert(err, gc.IsNil)
	_, err = w.Write([]byte(strconv.Itoa(charmDir.Revision())))

	h = &zip.FileHeader{Name: "metadata.yaml", Method: zip.Deflate}
	h.SetMode(0644)
	w, err = zipw.CreateHeader(h)
	c.Assert(err, gc.IsNil)
	data, err := goyaml.Marshal(charmDir.Meta())
	c.Assert(err, gc.IsNil)
	_, err = w.Write(data)
	c.Assert(err, gc.IsNil)

	for name := range charmDir.Meta().Hooks() {
		hookName := filepath.Join("hooks", name)
		h = &zip.FileHeader{
			Name:   hookName,
			Method: zip.Deflate,
		}
		// Force it non-executable
		h.SetMode(0644)
		w, err := zipw.CreateHeader(h)
		c.Assert(err, gc.IsNil)
		_, err = w.Write([]byte("not important"))
		c.Assert(err, gc.IsNil)
	}
}

func (s *BundleSuite) TestExpandToSetsHooksExecutable(c *gc.C) {
	charmDir := testing.Charms.ClonedDir(c.MkDir(), "all-hooks")
	// Bundle manually, so we can check ExpandTo(), unaffected
	// by BundleTo()'s behavior
	bundlePath := filepath.Join(c.MkDir(), "bundle.charm")
	s.prepareBundle(c, charmDir, bundlePath)
	bundle, err := charm.ReadBundle(bundlePath)
	c.Assert(err, gc.IsNil)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	_, err = charm.ReadDir(path)
	c.Assert(err, gc.IsNil)

	for name := range bundle.Meta().Hooks() {
		hookName := string(name)
		info, err := os.Stat(filepath.Join(path, "hooks", hookName))
		c.Assert(err, gc.IsNil)
		perm := info.Mode() & 0777
		c.Assert(perm&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName))
	}
}

func (s *BundleSuite) TestBundleFileModes(c *gc.C) {
	// Apply subtler mode differences than can be expressed in Bazaar.
	srcPath := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	modes := []struct {
		path string
		mode os.FileMode
	}{
		{"hooks/install", 0751},
		{"empty", 0750},
		{"src/hello.c", 0614},
	}
	for _, m := range modes {
		err := os.Chmod(filepath.Join(srcPath, m.path), m.mode)
		c.Assert(err, gc.IsNil)
	}
	var haveSymlinks = true
	if err := os.Symlink("../target", filepath.Join(srcPath, "hooks/symlink")); err != nil {
		haveSymlinks = false
	}

	// Bundle and extract the charm to a new directory.
	bundle := bundleDir(c, srcPath)
	path := c.MkDir()
	err := bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	// Check sensible file modes once round-tripped.
	info, err := os.Stat(filepath.Join(path, "src", "hello.c"))
	c.Assert(err, gc.IsNil)
	c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0644))
	c.Assert(info.Mode()&os.ModeType, gc.Equals, os.FileMode(0))

	info, err = os.Stat(filepath.Join(path, "hooks", "install"))
	c.Assert(err, gc.IsNil)
	c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0755))
	c.Assert(info.Mode()&os.ModeType, gc.Equals, os.FileMode(0))

	info, err = os.Stat(filepath.Join(path, "empty"))
	c.Assert(err, gc.IsNil)
	c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0755))

	if haveSymlinks {
		target, err := os.Readlink(filepath.Join(path, "hooks", "symlink"))
		c.Assert(err, gc.IsNil)
		c.Assert(target, gc.Equals, "../target")
	}
}

func (s *BundleSuite) TestBundleRevisionFile(c *gc.C) {
	charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	revPath := filepath.Join(charmDir, "revision")

	// Missing revision file
	err := os.Remove(revPath)
	c.Assert(err, gc.IsNil)

	bundle := extBundleDir(c, charmDir)
	c.Assert(bundle.Revision(), gc.Equals, 0)

	// Missing revision file with old revision in metadata
	file, err := os.OpenFile(filepath.Join(charmDir, "metadata.yaml"), os.O_WRONLY|os.O_APPEND, 0)
	c.Assert(err, gc.IsNil)
	_, err = file.Write([]byte("\nrevision: 1234\n"))
	c.Assert(err, gc.IsNil)

	bundle = extBundleDir(c, charmDir)
	c.Assert(bundle.Revision(), gc.Equals, 1234)

	// Revision file with bad content
	err = ioutil.WriteFile(revPath, []byte("garbage"), 0666)
	c.Assert(err, gc.IsNil)

	path := extBundleDirPath(c, charmDir)
	bundle, err = charm.ReadBundle(path)
	c.Assert(err, gc.ErrorMatches, "invalid revision file")
	c.Assert(bundle, gc.IsNil)
}

func (s *BundleSuite) TestBundleSetRevision(c *gc.C) {
	bundle, err := charm.ReadBundle(s.bundlePath)
	c.Assert(err, gc.IsNil)

	c.Assert(bundle.Revision(), gc.Equals, 1)
	bundle.SetRevision(42)
	c.Assert(bundle.Revision(), gc.Equals, 42)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.IsNil)

	dir, err := charm.ReadDir(path)
	c.Assert(err, gc.IsNil)
	c.Assert(dir.Revision(), gc.Equals, 42)
}

func (s *BundleSuite) TestExpandToWithBadLink(c *gc.C) {
	charmDir := testing.Charms.ClonedDirPath(c.MkDir(), "dummy")
	badLink := filepath.Join(charmDir, "hooks", "badlink")

	// Symlink targeting a path outside of the charm.
	err := os.Symlink("../../target", badLink)
	c.Assert(err, gc.IsNil)

	bundle := extBundleDir(c, charmDir)
	c.Assert(err, gc.IsNil)

	path := filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.ErrorMatches, `cannot extract "hooks/badlink": symlink "../../target" leads out of scope`)

	// Symlink targeting an absolute path.
	os.Remove(badLink)
	err = os.Symlink("/target", badLink)
	c.Assert(err, gc.IsNil)

	bundle = extBundleDir(c, charmDir)
	c.Assert(err, gc.IsNil)

	path = filepath.Join(c.MkDir(), "charm")
	err = bundle.ExpandTo(path)
	c.Assert(err, gc.ErrorMatches, `cannot extract "hooks/badlink": symlink "/target" is absolute`)
}

func extBundleDirPath(c *gc.C, dirpath string) string {
	path := filepath.Join(c.MkDir(), "bundle.charm")
	cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("cd %s; zip --fifo --symlinks -r %s .", dirpath, path))
	output, err := cmd.CombinedOutput()
	c.Assert(err, gc.IsNil, gc.Commentf("Command output: %s", output))
	return path
}

func extBundleDir(c *gc.C, dirpath string) *charm.Bundle {
	path := extBundleDirPath(c, dirpath)
	bundle, err := charm.ReadBundle(path)
	c.Assert(err, gc.IsNil)
	return bundle
}

func bundleDir(c *gc.C, dirpath string) *charm.Bundle {
	dir, err := charm.ReadDir(dirpath)
	c.Assert(err, gc.IsNil)
	buf := new(bytes.Buffer)
	err = dir.BundleTo(buf)
	c.Assert(err, gc.IsNil)
	bundle, err := charm.ReadBundleBytes(buf.Bytes())
	c.Assert(err, gc.IsNil)
	return bundle
}
<|endoftext|>


<commit_before>// Copyright 2019 Anapaya Systems
//
// 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 metrics

import (
	"context"
	"fmt"

	"github.com/opentracing/opentracing-go"

	"github.com/scionproto/scion/go/cs/beacon"
	"github.com/scionproto/scion/go/lib/addr"
	dblib "github.com/scionproto/scion/go/lib/infra/modules/db"
	"github.com/scionproto/scion/go/lib/metrics"
	"github.com/scionproto/scion/go/lib/prom"
	"github.com/scionproto/scion/go/lib/tracing"
	"github.com/scionproto/scion/go/pkg/storage"
	storagebeacon "github.com/scionproto/scion/go/pkg/storage/beacon"
)

type Config struct {
	Driver       string
	QueriesTotal metrics.Counter
}

// WrapDB wraps the given beacon database into one that also exports metrics.
func WrapDB(beaconDB storage.BeaconDB, cfg Config) storage.BeaconDB {
	return &db{
		db:      beaconDB,
		metrics: Observer{Cfg: cfg},
	}
}

type Observer struct {
	Cfg Config
}

type Observable func(context.Context) (label string, err error)

func (o Observer) Observe(ctx context.Context, op string, action Observable) {
	span, ctx := opentracing.StartSpanFromContext(ctx, fmt.Sprintf("beacondb.%s", op))
	defer span.Finish()
	label, err := action(ctx)

	tracing.ResultLabel(span, label)
	tracing.Error(span, err)

	labels := queryLabels{
		Driver:    o.Cfg.Driver,
		Operation: op,
		Result:    label,
	}
	metrics.CounterInc(metrics.CounterWith(o.Cfg.QueriesTotal, labels.Expand()...))
}

type db struct {
	db      storage.BeaconDB
	metrics Observer
}

// below here is very boilerplaty code that implements all DB ops and calls the Observe function.

func (d *db) CandidateBeacons(
	ctx context.Context,
	setSize int,
	usage beacon.Usage,
	src addr.IA,
) ([]beacon.Beacon, error) {

	var ret []beacon.Beacon
	var err error
	d.metrics.Observe(ctx, "candidate_beacons", func(ctx context.Context) (string, error) {
		ret, err = d.db.CandidateBeacons(ctx, setSize, usage, src)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) BeaconSources(ctx context.Context) ([]addr.IA, error) {
	var ret []addr.IA
	var err error
	d.metrics.Observe(ctx, "beacon_srcs", func(ctx context.Context) (string, error) {
		ret, err = d.db.BeaconSources(ctx)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) InsertBeacon(
	ctx context.Context,
	b beacon.Beacon,
	usage beacon.Usage,
) (beacon.InsertStats, error) {

	var ret beacon.InsertStats
	var err error
	d.metrics.Observe(ctx, "insert_beacon", func(ctx context.Context) (string, error) {
		ret, err = d.db.InsertBeacon(ctx, b, usage)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) GetBeacons(
	ctx context.Context,
	q *storagebeacon.QueryParams,
) ([]storagebeacon.Beacon, error) {

	var ret []storagebeacon.Beacon
	var err error
	d.metrics.Observe(ctx, "get_beacon", func(ctx context.Context) (string, error) {
		ret, err = d.db.GetBeacons(ctx, q)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) Close() error {
	return d.db.Close()
}

type queryLabels struct {
	Driver    string
	Operation string
	Result    string
}

func (l queryLabels) Expand() []string {
	return []string{"driver", l.Driver, "operation", l.Operation, prom.LabelResult, l.Result}
}
<commit_msg>Fix typo in logging of `get_beacons` events<commit_after>// Copyright 2019 Anapaya Systems
//
// 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 metrics

import (
	"context"
	"fmt"

	"github.com/opentracing/opentracing-go"

	"github.com/scionproto/scion/go/cs/beacon"
	"github.com/scionproto/scion/go/lib/addr"
	dblib "github.com/scionproto/scion/go/lib/infra/modules/db"
	"github.com/scionproto/scion/go/lib/metrics"
	"github.com/scionproto/scion/go/lib/prom"
	"github.com/scionproto/scion/go/lib/tracing"
	"github.com/scionproto/scion/go/pkg/storage"
	storagebeacon "github.com/scionproto/scion/go/pkg/storage/beacon"
)

type Config struct {
	Driver       string
	QueriesTotal metrics.Counter
}

// WrapDB wraps the given beacon database into one that also exports metrics.
func WrapDB(beaconDB storage.BeaconDB, cfg Config) storage.BeaconDB {
	return &db{
		db:      beaconDB,
		metrics: Observer{Cfg: cfg},
	}
}

type Observer struct {
	Cfg Config
}

type Observable func(context.Context) (label string, err error)

func (o Observer) Observe(ctx context.Context, op string, action Observable) {
	span, ctx := opentracing.StartSpanFromContext(ctx, fmt.Sprintf("beacondb.%s", op))
	defer span.Finish()
	label, err := action(ctx)

	tracing.ResultLabel(span, label)
	tracing.Error(span, err)

	labels := queryLabels{
		Driver:    o.Cfg.Driver,
		Operation: op,
		Result:    label,
	}
	metrics.CounterInc(metrics.CounterWith(o.Cfg.QueriesTotal, labels.Expand()...))
}

type db struct {
	db      storage.BeaconDB
	metrics Observer
}

// below here is very boilerplaty code that implements all DB ops and calls the Observe function.

func (d *db) CandidateBeacons(
	ctx context.Context,
	setSize int,
	usage beacon.Usage,
	src addr.IA,
) ([]beacon.Beacon, error) {

	var ret []beacon.Beacon
	var err error
	d.metrics.Observe(ctx, "candidate_beacons", func(ctx context.Context) (string, error) {
		ret, err = d.db.CandidateBeacons(ctx, setSize, usage, src)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) BeaconSources(ctx context.Context) ([]addr.IA, error) {
	var ret []addr.IA
	var err error
	d.metrics.Observe(ctx, "beacon_srcs", func(ctx context.Context) (string, error) {
		ret, err = d.db.BeaconSources(ctx)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) InsertBeacon(
	ctx context.Context,
	b beacon.Beacon,
	usage beacon.Usage,
) (beacon.InsertStats, error) {

	var ret beacon.InsertStats
	var err error
	d.metrics.Observe(ctx, "insert_beacon", func(ctx context.Context) (string, error) {
		ret, err = d.db.InsertBeacon(ctx, b, usage)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) GetBeacons(
	ctx context.Context,
	q *storagebeacon.QueryParams,
) ([]storagebeacon.Beacon, error) {

	var ret []storagebeacon.Beacon
	var err error
	d.metrics.Observe(ctx, "get_beacons", func(ctx context.Context) (string, error) {
		ret, err = d.db.GetBeacons(ctx, q)
		return dblib.ErrToMetricLabel(err), err
	})
	return ret, err
}

func (d *db) Close() error {
	return d.db.Close()
}

type queryLabels struct {
	Driver    string
	Operation string
	Result    string
}

func (l queryLabels) Expand() []string {
	return []string{"driver", l.Driver, "operation", l.Operation, prom.LabelResult, l.Result}
}
<|endoftext|>


<commit_before>package fsm_test

import (
	"testing"
	"time"

	"github.com/nbio/st"
	"github.com/turnkeyautogroup/archer-memory/fsm"
)

// Thing is a minimal struct that is an fsm.Stater
type Thing struct {
	State fsm.State
}

func (t *Thing) CurrentState() fsm.State { return t.State }
func (t *Thing) SetState(s fsm.State)    { t.State = s }

func TestRulesetTransitions(t *testing.T) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	examples := []struct {
		subject fsm.Stater
		goal    fsm.State
		outcome bool
	}{
		// A Stater is responsible for setting its default state
		{&Thing{}, "started", false},
		{&Thing{}, "pending", false},
		{&Thing{}, "finished", false},

		{&Thing{State: "pending"}, "started", true},
		{&Thing{State: "pending"}, "pending", false},
		{&Thing{State: "pending"}, "finished", false},

		{&Thing{State: "started"}, "started", false},
		{&Thing{State: "started"}, "pending", false},
		{&Thing{State: "started"}, "finished", true},
	}

	for i, ex := range examples {
		st.Expect(t, rules.Permitted(ex.subject, ex.goal), ex.outcome, i)
	}
}

func TestRulesetParallelGuarding(t *testing.T) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	// Add two failing rules, the slow should be caught first
	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		time.Sleep(1 * time.Second)
		t.Error("Slow rule should have been short-circuited")
		return false
	})

	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		return false
	})

	st.Expect(t, rules.Permitted(&Thing{State: "started"}, "finished"), false)
}

func TestMachineTransition(t *testing.T) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	some_thing := Thing{State: "pending"}
	the_machine := fsm.New(fsm.WithRules(rules), fsm.WithSubject(&some_thing))

	var err error

	// should not be able to transition to the current state
	err = the_machine.Transition("pending")
	st.Expect(t, err, fsm.InvalidTransition)
	st.Expect(t, some_thing.State, fsm.State("pending"))

	// should not be able to skip states
	err = the_machine.Transition("finished")
	st.Expect(t, err, fsm.InvalidTransition)
	st.Expect(t, some_thing.State, fsm.State("pending"))

	// should be able to transition to the next valid state
	err = the_machine.Transition("started")
	st.Expect(t, err, nil)
	st.Expect(t, some_thing.State, fsm.State("started"))
}

func BenchmarkRulesetParallelGuarding(b *testing.B) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	// Add two failing rules, one very slow and the other terribly fast
	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		time.Sleep(1 * time.Second)
		return false
	})

	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		return false
	})

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(&Thing{State: "started"}, "finished")
	}
}

func BenchmarkRulesetTransitionPermitted(b *testing.B) {
	// Permitted a transaction requires the transition to be valid and all of its
	// guards to pass. Since we have to run every guard and there won't be any
	// short-circuiting, this should actually be a little bit slower as a result,
	// depending on the number of guards that must pass.
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	some_thing := &Thing{State: "started"}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(some_thing, "finished")
	}

}

func BenchmarkRulesetTransitionInvalid(b *testing.B) {
	// This should be incredibly fast, since fsm.Transition{"pending", "finished"}
	// doesn't exist in the Ruleset. We expect some small overhead from creating
	// the transition to check the internal map, but otherwise, we should be
	// bumping up against the speed of a map lookup itself.

	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	some_thing := &Thing{State: "pending"}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(some_thing, "finished")
	}
}

func BenchmarkRulesetRuleForbids(b *testing.B) {
	// Here, we explicity create a transition that is forbidden. This simulates an
	// otherwise valid transition that would be denied based on a user role or the like.
	// It should be slower than a standard invalid transition, since we have to
	// actually execute a function to perform the check. The first guard to
	// fail (returning false) will short circuit the execution, getting some some speed.

	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})

	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		return false
	})

	some_thing := &Thing{State: "started"}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(some_thing, "finished")
	}
}
<commit_msg>fix test import<commit_after>package fsm_test

import (
	"testing"
	"time"

	"github.com/nbio/st"
	"github.com/ryanfaerman/fsm"
)

// Thing is a minimal struct that is an fsm.Stater
type Thing struct {
	State fsm.State
}

func (t *Thing) CurrentState() fsm.State { return t.State }
func (t *Thing) SetState(s fsm.State)    { t.State = s }

func TestRulesetTransitions(t *testing.T) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	examples := []struct {
		subject fsm.Stater
		goal    fsm.State
		outcome bool
	}{
		// A Stater is responsible for setting its default state
		{&Thing{}, "started", false},
		{&Thing{}, "pending", false},
		{&Thing{}, "finished", false},

		{&Thing{State: "pending"}, "started", true},
		{&Thing{State: "pending"}, "pending", false},
		{&Thing{State: "pending"}, "finished", false},

		{&Thing{State: "started"}, "started", false},
		{&Thing{State: "started"}, "pending", false},
		{&Thing{State: "started"}, "finished", true},
	}

	for i, ex := range examples {
		st.Expect(t, rules.Permitted(ex.subject, ex.goal), ex.outcome, i)
	}
}

func TestRulesetParallelGuarding(t *testing.T) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	// Add two failing rules, the slow should be caught first
	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		time.Sleep(1 * time.Second)
		t.Error("Slow rule should have been short-circuited")
		return false
	})

	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		return false
	})

	st.Expect(t, rules.Permitted(&Thing{State: "started"}, "finished"), false)
}

func TestMachineTransition(t *testing.T) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	some_thing := Thing{State: "pending"}
	the_machine := fsm.New(fsm.WithRules(rules), fsm.WithSubject(&some_thing))

	var err error

	// should not be able to transition to the current state
	err = the_machine.Transition("pending")
	st.Expect(t, err, fsm.InvalidTransition)
	st.Expect(t, some_thing.State, fsm.State("pending"))

	// should not be able to skip states
	err = the_machine.Transition("finished")
	st.Expect(t, err, fsm.InvalidTransition)
	st.Expect(t, some_thing.State, fsm.State("pending"))

	// should be able to transition to the next valid state
	err = the_machine.Transition("started")
	st.Expect(t, err, nil)
	st.Expect(t, some_thing.State, fsm.State("started"))
}

func BenchmarkRulesetParallelGuarding(b *testing.B) {
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	// Add two failing rules, one very slow and the other terribly fast
	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		time.Sleep(1 * time.Second)
		return false
	})

	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		return false
	})

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(&Thing{State: "started"}, "finished")
	}
}

func BenchmarkRulesetTransitionPermitted(b *testing.B) {
	// Permitted a transaction requires the transition to be valid and all of its
	// guards to pass. Since we have to run every guard and there won't be any
	// short-circuiting, this should actually be a little bit slower as a result,
	// depending on the number of guards that must pass.
	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	some_thing := &Thing{State: "started"}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(some_thing, "finished")
	}

}

func BenchmarkRulesetTransitionInvalid(b *testing.B) {
	// This should be incredibly fast, since fsm.Transition{"pending", "finished"}
	// doesn't exist in the Ruleset. We expect some small overhead from creating
	// the transition to check the internal map, but otherwise, we should be
	// bumping up against the speed of a map lookup itself.

	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})
	rules.AddTransition(fsm.Transition{"started", "finished"})

	some_thing := &Thing{State: "pending"}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(some_thing, "finished")
	}
}

func BenchmarkRulesetRuleForbids(b *testing.B) {
	// Here, we explicity create a transition that is forbidden. This simulates an
	// otherwise valid transition that would be denied based on a user role or the like.
	// It should be slower than a standard invalid transition, since we have to
	// actually execute a function to perform the check. The first guard to
	// fail (returning false) will short circuit the execution, getting some some speed.

	rules := fsm.Ruleset{}
	rules.AddTransition(fsm.Transition{"pending", "started"})

	rules.AddRule(fsm.Transition{"started", "finished"}, func(subject fsm.Stater, goal fsm.State) bool {
		return false
	})

	some_thing := &Thing{State: "started"}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rules.Permitted(some_thing, "finished")
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Dorival Pedroso and Raul Durand. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fun

import "github.com/cpmech/gosl/io"

// global auxiliary variables
var (
	g_largestname  int    // largest length of paramter name (to make a nice table)
	g_largestsval  int    // largest length of paramter value string representation (to make a nice table)
	G_extraindent  string // extra indentation
	G_openbrackets bool   // add initial brackets
)

// Prm holds material parameter names and values
type Prm struct {

	// input
	N      string  `json:"n"`      // name of parameter
	V      float64 `json:"v"`      // value of parameter
	Min    float64 `json:"min"`    // min value
	Max    float64 `json:"max"`    // max value
	S      float64 `json:"s"`      // standard deviation
	D      string  `json:"d"`      // probability distribution type
	U      string  `json:"u"`      // unit (not verified)
	Adj    string  `json:"adj"`    // adjustable: search key
	Dep    string  `json:"dep"`    // depends on
	Extra  string  `json:"extra"`  // extra data
	Inact  bool    `json:"inact"`  // parameter is inactive in optimisation
	SetDef bool    `json:"setdef"` // tells model to use a default value
	Fcn    Func    `json:"fcn"`    // a function y=f(t,x)

	// derived
	conn []*float64 // connected variables to V
}

// Connect connects parameter to variable
func (o *Prm) Connect(V *float64) {
	o.conn = append(o.conn, V)
	*V = o.V
}

// Set sets parameter, including connected variables
func (o *Prm) Set(V float64) {
	o.V = V
	for _, v := range o.conn {
		*v = V
	}
}

// Prms holds many parameters
type Prms []*Prm

// Find finds a parameter by name
//  Note: returns nil if not found
func (o *Prms) Find(name string) *Prm {
	for _, p := range *o {
		if p.N == name {
			return p
		}
	}
	return nil
}

// Connect connects parameter
func (o *Prms) Connect(V *float64, name string) (err string) {
	prm := o.Find(name)
	if prm == nil {
		return io.Sf("cannot find parameter named %q\n", name)
	}
	prm.Connect(V)
	return
}

func (o Prms) String() (l string) {
	for _, prm := range o {
		l += io.Sf("\nN=%q, ", prm.N)
		l += io.Sf("V=%v, ", prm.V)
		l += io.Sf("Min=%v, ", prm.Min)
		l += io.Sf("Max=%v, ", prm.Max)
		l += io.Sf("S=%v, ", prm.S)
		l += io.Sf("D=%q\n", prm.D)
		l += io.Sf("U=%v, ", prm.U)
		l += io.Sf("Adj=%q, ", prm.Adj)
		l += io.Sf("Dep=%q, ", prm.Dep)
		l += io.Sf("Extra=%q, ", prm.Extra)
		l += io.Sf("Inact=%v, ", prm.Inact)
		l += io.Sf("SetDef=%v, ", prm.SetDef)
		l += io.Sf("Fcn=%v\n", prm.Fcn)
	}
	return
}
<commit_msg>fun: prms: adjustable and dependent variables are int now. prm can now be connected to another prm<commit_after>// Copyright 2015 Dorival Pedroso and Raul Durand. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fun

import "github.com/cpmech/gosl/io"

// global auxiliary variables
var (
	G_extraindent string // extra indentation
)

// Prm holds material parameter names and values
type Prm struct {

	// input
	N      string  `json:"n"`      // name of parameter
	V      float64 `json:"v"`      // value of parameter
	Min    float64 `json:"min"`    // min value
	Max    float64 `json:"max"`    // max value
	S      float64 `json:"s"`      // standard deviation
	D      string  `json:"d"`      // probability distribution type
	U      string  `json:"u"`      // unit (not verified)
	Adj    int     `json:"adj"`    // adjustable: unique ID (greater than zero)
	Dep    int     `json:"dep"`    // depends on "adj"
	Extra  string  `json:"extra"`  // extra data
	Inact  bool    `json:"inact"`  // parameter is inactive in optimisation
	SetDef bool    `json:"setdef"` // tells model to use a default value

	// auxiliary
	Fcn   Func // a function y=f(t,x)
	Other *Prm // dependency: connected parameter

	// derived
	conn []*float64 // connected variables to V
}

// Connect connects parameter to variable
func (o *Prm) Connect(V *float64) {
	o.conn = append(o.conn, V)
	*V = o.V
}

// Set sets parameter, including connected variables
func (o *Prm) Set(V float64) {
	o.V = V
	for _, v := range o.conn {
		*v = V
	}
}

// Prms holds many parameters
type Prms []*Prm

// Find finds a parameter by name
//  Note: returns nil if not found
func (o *Prms) Find(name string) *Prm {
	for _, p := range *o {
		if p.N == name {
			return p
		}
	}
	return nil
}

// Connect connects parameter
func (o *Prms) Connect(V *float64, name string) (err string) {
	prm := o.Find(name)
	if prm == nil {
		return io.Sf("cannot find parameter named %q\n", name)
	}
	prm.Connect(V)
	return
}

func (o Prms) String() (l string) {
	for i, prm := range o {
		if i > 0 {
			l += ",\n"
		}
		l += io.Sf(G_extraindent + "{")
		l += io.Sf(`"n":%q, `, prm.N)
		l += io.Sf(`"v":%v, `, prm.V)
		l += io.Sf(`"min":%v, `, prm.Min)
		l += io.Sf(`"max":%v, `, prm.Max)
		l += io.Sf(`"s":%v, `, prm.S)
		l += io.Sf(`"d":%q, `, prm.D)
		l += io.Sf(`"u":%q, `, prm.U)
		l += io.Sf(`"adj":%v, `, prm.Adj)
		l += io.Sf(`"dep":%v, `, prm.Dep)
		l += io.Sf(`"extra":%q, `, prm.Extra)
		l += io.Sf(`"inact":%v, `, prm.Inact)
		l += io.Sf(`"setdef":%v`, prm.SetDef)
		l += io.Sf("}")
	}
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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 gcp

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"time"
)

// Dump XMPP XMP conversation to stdout.
const debug = false

// Compare this to err to detect a closed connection.
var Closed = errors.New("closed")

// Interface with XMPP server.
type gcpXMPP struct {
	conn       *tls.Conn
	xmlDecoder *xml.Decoder
}

type nextPrinterResponse struct {
	gcpID string
	err   error
}

func newXMPP(xmppJID, accessToken, proxyName string) (*gcpXMPP, error) {
	var user, domain string
	if parts := strings.SplitN(xmppJID, "@", 2); len(parts) != 2 {
		return nil, fmt.Errorf("Tried to use invalid XMPP JID: %s", xmppJID)
	} else {
		user = parts[0]
		domain = parts[1]
	}

	// Anyone home?
	conn, err := dial()
	if err != nil {
		return nil, fmt.Errorf("Failed to dial XMPP service: %s", err)
	}

	var xmlEncoder *xml.Encoder
	var xmlDecoder *xml.Decoder
	if debug {
		t := &tee{conn, conn}
		xmlEncoder = xml.NewEncoder(t)
		xmlDecoder = xml.NewDecoder(t)
	} else {
		xmlEncoder = xml.NewEncoder(conn)
		xmlDecoder = xml.NewDecoder(conn)
	}

	// SASL
	if err := saslHandshake(xmlEncoder, xmlDecoder, domain, user, accessToken); err != nil {
		return nil, fmt.Errorf("Failed to perform XMPP-SASL handshake: %s", err)
	}

	// XMPP
	fullJID, err := xmppHandshake(xmlEncoder, xmlDecoder, domain, proxyName)
	if err != nil {
		return nil, fmt.Errorf("Failed to perform final XMPP handshake: %s", err)
	}

	// Subscribe
	if err := subscribe(xmlEncoder, xmlDecoder, fullJID); err != nil {
		return nil, fmt.Errorf("Failed to subscribe: %s", err)
	}

	x := gcpXMPP{conn, xmlDecoder}

	return &x, nil
}

// Returns the GCPID of the next printer with waiting jobs.
func (x *gcpXMPP) nextWaitingPrinter() (string, error) {
	var message struct {
		XMLName xml.Name `xml:"message"`
		Data    string   `xml:"push>data"`
	}

	if err := x.xmlDecoder.Decode(&message); err != nil {
		if strings.Contains(err.Error(), "use of closed network connection") {
			return "", Closed
		}
		return "", fmt.Errorf("Error while waiting for print jobs via XMPP: %s", err)
	} else {
		return message.Data, nil
	}
}

func (x *gcpXMPP) quit() {
	x.conn.Close()
}

func dial() (*tls.Conn, error) {
	tlsConfig := &tls.Config{
		ServerName: "talk.google.com",
	}
	netDialer := &net.Dialer{
		Timeout:   time.Second * 30,
		KeepAlive: time.Second * 60,
	}
	conn, err := tls.DialWithDialer(netDialer, "tcp", "talk.google.com:443", tlsConfig)
	if err != nil {
		return nil, fmt.Errorf("Failed to connect to XMPP server: %s", err)
	}
	if err = conn.VerifyHostname("talk.google.com"); err != nil {
		return nil, fmt.Errorf("Failed to verify hostname of XMPP server: %s", err)
	}

	return conn, nil
}

func saslHandshake(xmlEncoder *xml.Encoder, xmlDecoder *xml.Decoder, domain, user, accessToken string) error {
	handshake := xml.StartElement{
		Name: xml.Name{"jabber:client", "stream:stream"},
		Attr: []xml.Attr{
			xml.Attr{xml.Name{Local: "to"}, domain},
			xml.Attr{xml.Name{Local: "xml:lang"}, "en"},
			xml.Attr{xml.Name{Local: "version"}, "1.0"},
			xml.Attr{xml.Name{Local: "xmlns:stream"}, "http://etherx.jabber.org/streams"},
		},
	}
	if err := xmlEncoder.EncodeToken(handshake); err != nil {
		return fmt.Errorf("Failed to write SASL handshake: %s", err)
	}
	if err := xmlEncoder.Flush(); err != nil {
		return fmt.Errorf("Failed to flush encoding stream: %s", err)
	}

	if startElement, err := readStartElement(xmlDecoder); err != nil {
		return err
	} else if startElement.Name.Space != "http://etherx.jabber.org/streams" ||
		startElement.Name.Local != "stream" {
		return errors.New("Read unexpected SASL XML stanza")
	}

	var features struct {
		XMLName    xml.Name `xml:"http://etherx.jabber.org/streams features"`
		Mechanisms *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
		}
	}
	if err := xmlDecoder.Decode(&features); err != nil {
		return errors.New("Read unexpected SASL XML element")
	} else if features.Mechanisms == nil {
		return errors.New("SASL mechanisms missing from handshake")
	}

	credential := base64.StdEncoding.EncodeToString([]byte("\x00" + user + "\x00" + accessToken))

	var auth struct {
		XMLName    xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
		Mechanism  string   `xml:"mechanism,attr"`
		Service    string   `xml:"auth:service,attr"`
		Allow      string   `xml:"auth:allow-generated-jid,attr"`
		FullBind   string   `xml:"auth:client-uses-full-bind-result,attr"`
		XMLNS      string   `xml:"xmlns:auth,attr"`
		Credential string   `xml:",chardata"`
	}
	auth.Mechanism = "X-OAUTH2"
	auth.Service = "chromiumsync"
	auth.Allow = "true"
	auth.FullBind = "true"
	auth.XMLNS = "http://www.google.com/talk/protocol/auth"
	auth.Credential = credential
	if err := xmlEncoder.Encode(auth); err != nil {
		return fmt.Errorf("Failed to write SASL credentials: %s", err)
	}

	var success struct {
		XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
	}
	if err := xmlDecoder.Decode(&success); err != nil {
		return errors.New("Failed to complete SASL handshake")
	}

	return nil
}

func xmppHandshake(xmlEncoder *xml.Encoder, xmlDecoder *xml.Decoder, domain, proxyName string) (string, error) {
	handshake := xml.StartElement{
		Name: xml.Name{"jabber:client", "stream:stream"},
		Attr: []xml.Attr{
			xml.Attr{xml.Name{Local: "to"}, domain},
			xml.Attr{xml.Name{Local: "xml:lang"}, "en"},
			xml.Attr{xml.Name{Local: "version"}, "1.0"},
			xml.Attr{xml.Name{Local: "xmlns:stream"}, "http://etherx.jabber.org/streams"},
		},
	}
	if err := xmlEncoder.EncodeToken(handshake); err != nil {
		return "", fmt.Errorf("Failed to write SASL handshake: %s", err)
	}
	if err := xmlEncoder.Flush(); err != nil {
		return "", fmt.Errorf("Failed to flush encoding stream: %s", err)
	}

	if startElement, err := readStartElement(xmlDecoder); err != nil {
		return "", err
	} else if startElement.Name.Space != "http://etherx.jabber.org/streams" ||
		startElement.Name.Local != "stream" {
		return "", errors.New("Read unexpected XMPP XML stanza")
	}

	var features struct {
		XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
		Bind    *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
		}
		Session *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
		}
	}
	if err := xmlDecoder.Decode(&features); err != nil {
		return "", errors.New("Read unexpected XMPP XML element")
	} else if features.Bind == nil || features.Session == nil {
		return "", errors.New("XMPP bind or session missing from handshake")
	}

	var resource struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		Type    string   `xml:"type,attr"`
		ID      string   `xml:"id,attr"`
		Bind    struct {
			XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
			Resource struct {
				XMLName      xml.Name `xml:"resource"`
				ResourceName string   `xml:",chardata"`
			}
		}
	}
	resource.Type = "set"
	resource.ID = "0"
	resource.Bind.Resource.ResourceName = proxyName
	if err := xmlEncoder.Encode(&resource); err != nil {
		return "", fmt.Errorf("Failed to set resource during XMPP handshake: %s", err)
	}

	var jid struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		Bind    *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
			JID     string   `xml:"jid"`
		}
	}
	if err := xmlDecoder.Decode(&jid); err != nil {
		return "", err
	} else if jid.Bind == nil || jid.Bind.JID == "" {
		return "", errors.New("Received unexpected XML element during XMPP handshake")
	}

	fullJID := jid.Bind.JID

	var session struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		Type    string   `xml:"type,attr"`
		ID      string   `xml:"id,attr"`
		Session struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
		}
	}
	session.Type = "set"
	session.ID = "1"
	if err := xmlEncoder.Encode(&session); err != nil {
		return "", fmt.Errorf("Failed to complete XMPP handshake: %s", err)
	}

	var xmppDone struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		ID      string   `xml:"id,attr"`
	}
	if err := xmlDecoder.Decode(&xmppDone); err != nil {
		return "", err
	} else if xmppDone.ID != "1" {
		return "", errors.New("Received unexpected result at end of XMPP handshake")
	}

	return fullJID, nil
}

func subscribe(xmlEncoder *xml.Encoder, xmlDecoder *xml.Decoder, fullJID string) error {
	var bareJID string
	if barePosition := strings.Index(fullJID, "/"); barePosition < 0 {
		return fmt.Errorf("Can't split JID %s", fullJID)
	} else {
		bareJID = fullJID[:barePosition]
	}

	var subscribe struct {
		XMLName   xml.Name `xml:"jabber:client iq"`
		Type      string   `xml:"type,attr"`
		To        string   `xml:"to,attr"`
		ID        string   `xml:"id,attr"`
		Subscribe struct {
			XMLName xml.Name `xml:"google:push subscribe"`
			Item    struct {
				XMLName xml.Name `xml:"item"`
				Channel string   `xml:"channel,attr"`
				From    string   `xml:"from,attr"`
			}
		}
	}
	subscribe.Type = "set"
	subscribe.To = bareJID
	subscribe.ID = "3"
	subscribe.Subscribe.Item.Channel = "cloudprint.google.com"
	subscribe.Subscribe.Item.From = "cloudprint.google.com"
	if err := xmlEncoder.Encode(&subscribe); err != nil {
		return fmt.Errorf("XMPP subscription request failed: %s", err)
	}

	var subscription struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		To      string   `xml:"to,attr"`
		From    string   `xml:"from,attr"`
	}
	if err := xmlDecoder.Decode(&subscription); err != nil {
		return fmt.Errorf("XMPP subscription response invalid: %s", err)
	} else if fullJID != subscription.To || bareJID != subscription.From {
		return errors.New("XMPP subscription failed")
	}

	return nil
}

func readStartElement(d *xml.Decoder) (xml.StartElement, error) {
	token, err := d.Token()
	if err != nil {
		return xml.StartElement{}, err
	}
	if startElement, ok := token.(xml.StartElement); ok {
		return startElement, nil
	} else {
		return xml.StartElement{}, errors.New("XML stream produced unexpected output")
	}
}

type tee struct {
	r io.Reader
	w io.Writer
}

func (t *tee) Read(p []byte) (int, error) {
	n, err := t.r.Read(p)
	fmt.Printf("read %d %s\n", n, p[0:n])
	return n, err
}

func (t *tee) Write(p []byte) (int, error) {
	n, err := t.w.Write(p)
	fmt.Printf("wrote %d %s\n", n, p[0:n])
	return n, err
}
<commit_msg>Better error reporting, more careful print job message parsing<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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 gcp

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"time"
)

// Dump XMPP XMP conversation to stdout.
const debug = false

// Compare this to err to detect a closed connection.
var Closed = errors.New("closed")

// Interface with XMPP server.
type gcpXMPP struct {
	conn       *tls.Conn
	xmlDecoder *xml.Decoder
}

type nextPrinterResponse struct {
	gcpID string
	err   error
}

func newXMPP(xmppJID, accessToken, proxyName string) (*gcpXMPP, error) {
	var user, domain string
	if parts := strings.SplitN(xmppJID, "@", 2); len(parts) != 2 {
		return nil, fmt.Errorf("Tried to use invalid XMPP JID: %s", xmppJID)
	} else {
		user = parts[0]
		domain = parts[1]
	}

	// Anyone home?
	conn, err := dial()
	if err != nil {
		return nil, fmt.Errorf("Failed to dial XMPP service: %s", err)
	}

	var xmlEncoder *xml.Encoder
	var xmlDecoder *xml.Decoder
	if debug {
		t := &tee{conn, conn}
		xmlEncoder = xml.NewEncoder(t)
		xmlDecoder = xml.NewDecoder(t)
	} else {
		xmlEncoder = xml.NewEncoder(conn)
		xmlDecoder = xml.NewDecoder(conn)
	}

	// SASL
	if err := saslHandshake(xmlEncoder, xmlDecoder, domain, user, accessToken); err != nil {
		return nil, fmt.Errorf("Failed to perform XMPP-SASL handshake: %s", err)
	}

	// XMPP
	fullJID, err := xmppHandshake(xmlEncoder, xmlDecoder, domain, proxyName)
	if err != nil {
		return nil, fmt.Errorf("Failed to perform final XMPP handshake: %s", err)
	}

	// Subscribe
	if err := subscribe(xmlEncoder, xmlDecoder, fullJID); err != nil {
		return nil, fmt.Errorf("Failed to subscribe: %s", err)
	}

	x := gcpXMPP{conn, xmlDecoder}

	return &x, nil
}

// Returns the GCPID of the next printer with waiting jobs.
func (x *gcpXMPP) nextWaitingPrinter() (string, error) {
	startElement, err := readStartElement(x.xmlDecoder)
	if err != nil {
		return "", fmt.Errorf("Failed to read the next start element: %s", err)
	}
	if startElement.Name.Local != "message" {
		return "", fmt.Errorf("Unexpected element while waiting for print message: %+v", startElement)
	}

	var message struct {
		XMLName xml.Name `xml:"message"`
		Data    string   `xml:"push>data"`
	}

	if err := x.xmlDecoder.DecodeElement(&message, startElement); err != nil {
		if strings.Contains(err.Error(), "use of closed network connection") {
			return "", Closed
		}
		return "", fmt.Errorf("Error while waiting for print jobs via XMPP: %s", err)
	} else {
		return message.Data, nil
	}
}

func (x *gcpXMPP) quit() {
	x.conn.Close()
}

func dial() (*tls.Conn, error) {
	tlsConfig := &tls.Config{
		ServerName: "talk.google.com",
	}
	netDialer := &net.Dialer{
		Timeout:   time.Second * 30,
		KeepAlive: time.Second * 60,
	}
	conn, err := tls.DialWithDialer(netDialer, "tcp", "talk.google.com:443", tlsConfig)
	if err != nil {
		return nil, fmt.Errorf("Failed to connect to XMPP server: %s", err)
	}
	if err = conn.VerifyHostname("talk.google.com"); err != nil {
		return nil, fmt.Errorf("Failed to verify hostname of XMPP server: %s", err)
	}

	return conn, nil
}

func saslHandshake(xmlEncoder *xml.Encoder, xmlDecoder *xml.Decoder, domain, user, accessToken string) error {
	handshake := xml.StartElement{
		Name: xml.Name{"jabber:client", "stream:stream"},
		Attr: []xml.Attr{
			xml.Attr{xml.Name{Local: "to"}, domain},
			xml.Attr{xml.Name{Local: "xml:lang"}, "en"},
			xml.Attr{xml.Name{Local: "version"}, "1.0"},
			xml.Attr{xml.Name{Local: "xmlns:stream"}, "http://etherx.jabber.org/streams"},
		},
	}
	if err := xmlEncoder.EncodeToken(handshake); err != nil {
		return fmt.Errorf("Failed to write SASL handshake: %s", err)
	}
	if err := xmlEncoder.Flush(); err != nil {
		return fmt.Errorf("Failed to flush encoding stream: %s", err)
	}

	if startElement, err := readStartElement(xmlDecoder); err != nil {
		return err
	} else if startElement.Name.Space != "http://etherx.jabber.org/streams" ||
		startElement.Name.Local != "stream" {
		return fmt.Errorf("Read unexpected SASL XML stanza: %s", startElement.Name.Local)
	}

	var features struct {
		XMLName    xml.Name `xml:"http://etherx.jabber.org/streams features"`
		Mechanisms *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
		}
	}
	if err := xmlDecoder.Decode(&features); err != nil {
		return fmt.Errorf("Read unexpected SASL XML element: %s", err)
	} else if features.Mechanisms == nil {
		return errors.New("SASL mechanisms missing from handshake")
	}

	credential := base64.StdEncoding.EncodeToString([]byte("\x00" + user + "\x00" + accessToken))

	var auth struct {
		XMLName    xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
		Mechanism  string   `xml:"mechanism,attr"`
		Service    string   `xml:"auth:service,attr"`
		Allow      string   `xml:"auth:allow-generated-jid,attr"`
		FullBind   string   `xml:"auth:client-uses-full-bind-result,attr"`
		XMLNS      string   `xml:"xmlns:auth,attr"`
		Credential string   `xml:",chardata"`
	}
	auth.Mechanism = "X-OAUTH2"
	auth.Service = "chromiumsync"
	auth.Allow = "true"
	auth.FullBind = "true"
	auth.XMLNS = "http://www.google.com/talk/protocol/auth"
	auth.Credential = credential
	if err := xmlEncoder.Encode(auth); err != nil {
		return fmt.Errorf("Failed to write SASL credentials: %s", err)
	}

	var success struct {
		XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
	}
	if err := xmlDecoder.Decode(&success); err != nil {
		return fmt.Errorf("Failed to complete SASL handshake: %s", err)
	}

	return nil
}

func xmppHandshake(xmlEncoder *xml.Encoder, xmlDecoder *xml.Decoder, domain, proxyName string) (string, error) {
	handshake := xml.StartElement{
		Name: xml.Name{"jabber:client", "stream:stream"},
		Attr: []xml.Attr{
			xml.Attr{xml.Name{Local: "to"}, domain},
			xml.Attr{xml.Name{Local: "xml:lang"}, "en"},
			xml.Attr{xml.Name{Local: "version"}, "1.0"},
			xml.Attr{xml.Name{Local: "xmlns:stream"}, "http://etherx.jabber.org/streams"},
		},
	}
	if err := xmlEncoder.EncodeToken(handshake); err != nil {
		return "", fmt.Errorf("Failed to write SASL handshake: %s", err)
	}
	if err := xmlEncoder.Flush(); err != nil {
		return "", fmt.Errorf("Failed to flush encoding stream: %s", err)
	}

	if startElement, err := readStartElement(xmlDecoder); err != nil {
		return "", err
	} else if startElement.Name.Space != "http://etherx.jabber.org/streams" ||
		startElement.Name.Local != "stream" {
		return "", fmt.Errorf("Read unexpected XMPP XML stanza: %s", startElement.Name.Local)
	}

	var features struct {
		XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
		Bind    *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
		}
		Session *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
		}
	}
	if err := xmlDecoder.Decode(&features); err != nil {
		return "", fmt.Errorf("Read unexpected XMPP XML element: %s", err)
	} else if features.Bind == nil || features.Session == nil {
		return "", errors.New("XMPP bind or session missing from handshake")
	}

	var resource struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		Type    string   `xml:"type,attr"`
		ID      string   `xml:"id,attr"`
		Bind    struct {
			XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
			Resource struct {
				XMLName      xml.Name `xml:"resource"`
				ResourceName string   `xml:",chardata"`
			}
		}
	}
	resource.Type = "set"
	resource.ID = "0"
	resource.Bind.Resource.ResourceName = proxyName
	if err := xmlEncoder.Encode(&resource); err != nil {
		return "", fmt.Errorf("Failed to set resource during XMPP handshake: %s", err)
	}

	var jid struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		Bind    *struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
			JID     string   `xml:"jid"`
		}
	}
	if err := xmlDecoder.Decode(&jid); err != nil {
		return "", err
	} else if jid.Bind == nil || jid.Bind.JID == "" {
		return "", errors.New("Received unexpected XML element during XMPP handshake")
	}

	fullJID := jid.Bind.JID

	var session struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		Type    string   `xml:"type,attr"`
		ID      string   `xml:"id,attr"`
		Session struct {
			XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
		}
	}
	session.Type = "set"
	session.ID = "1"
	if err := xmlEncoder.Encode(&session); err != nil {
		return "", fmt.Errorf("Failed to complete XMPP handshake: %s", err)
	}

	var xmppDone struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		ID      string   `xml:"id,attr"`
	}
	if err := xmlDecoder.Decode(&xmppDone); err != nil {
		return "", err
	} else if xmppDone.ID != "1" {
		return "", errors.New("Received unexpected result at end of XMPP handshake")
	}

	return fullJID, nil
}

func subscribe(xmlEncoder *xml.Encoder, xmlDecoder *xml.Decoder, fullJID string) error {
	var bareJID string
	if barePosition := strings.Index(fullJID, "/"); barePosition < 0 {
		return fmt.Errorf("Can't split JID %s", fullJID)
	} else {
		bareJID = fullJID[:barePosition]
	}

	var subscribe struct {
		XMLName   xml.Name `xml:"jabber:client iq"`
		Type      string   `xml:"type,attr"`
		To        string   `xml:"to,attr"`
		ID        string   `xml:"id,attr"`
		Subscribe struct {
			XMLName xml.Name `xml:"google:push subscribe"`
			Item    struct {
				XMLName xml.Name `xml:"item"`
				Channel string   `xml:"channel,attr"`
				From    string   `xml:"from,attr"`
			}
		}
	}
	subscribe.Type = "set"
	subscribe.To = bareJID
	subscribe.ID = "3"
	subscribe.Subscribe.Item.Channel = "cloudprint.google.com"
	subscribe.Subscribe.Item.From = "cloudprint.google.com"
	if err := xmlEncoder.Encode(&subscribe); err != nil {
		return fmt.Errorf("XMPP subscription request failed: %s", err)
	}

	var subscription struct {
		XMLName xml.Name `xml:"jabber:client iq"`
		To      string   `xml:"to,attr"`
		From    string   `xml:"from,attr"`
	}
	if err := xmlDecoder.Decode(&subscription); err != nil {
		return fmt.Errorf("XMPP subscription response invalid: %s", err)
	} else if fullJID != subscription.To || bareJID != subscription.From {
		return errors.New("XMPP subscription failed")
	}

	return nil
}

func readStartElement(d *xml.Decoder) (*xml.StartElement, error) {
	token, err := d.Token()
	if err != nil {
		return nil, err
	}
	if startElement, ok := token.(xml.StartElement); ok {
		return &startElement, nil
	} else {
		return nil, errors.New("XML stream produced unexpected output")
	}
}

type tee struct {
	r io.Reader
	w io.Writer
}

func (t *tee) Read(p []byte) (int, error) {
	n, err := t.r.Read(p)
	fmt.Printf("read %d %s\n", n, p[0:n])
	return n, err
}

func (t *tee) Write(p []byte) (int, error) {
	n, err := t.w.Write(p)
	fmt.Printf("wrote %d %s\n", n, p[0:n])
	return n, err
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/cli"
	"io/ioutil"
	"net/http"
	"os"
)

const (
	NAGIOS_OK       int = 0
	NAGIOS_WARNING  int = 1
	NAGIOS_CRITICAL int = 2
	NAGIOS_UNKNOWN  int = 3
)

var exitCode int

func main() {

	app := cli.NewApp()
	app.Name = "check_hypersearch"
	app.Version = "1.0.0"
	app.Author = "Paul Swanson"
	app.Usage = "Search for text on a web page"
	app.Flags = []cli.Flag{
		cli.StringFlag{"require,r", "all", "Require 'all' or 'some'"},
		cli.BoolFlag{"quiet, q", "Be quiet"},
		cli.BoolFlag{"verbose", "Be verbose; for debugging etc."},
	}

	cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}

USAGE:
{{.Name}} [global options] [arguments...]

For example, {{.Name}} --require some http://www.google.com/ "<title>Google</title>" "Privacy & Terms"

VERSION:
{{.Version}}

GLOBAL OPTIONS:
{{range .Flags}}{{.}}
{{end}}
`

	app.Action = func(c *cli.Context) {

		var requireAll, quiet, verbose bool

		if c.String("require") == "some" {
			requireAll = false
		} else {
			requireAll = true
		}

		if c.Bool("quiet") {
			quiet = true
		}

		if c.Bool("verbose") {
			verbose = true
		}

		args := c.Args()
		argCount := len(args)

		if argCount < 2 {
			cli.ShowAppHelp(c)
			exitCode = NAGIOS_UNKNOWN
			return
		}

		if verbose {
			println("Accessing", args[0])
		}
		resp, err := http.Get(args[0])
		if err != nil {
			println("Couldn't access that link!")
			exitCode = NAGIOS_UNKNOWN
			return
		}
		defer resp.Body.Close()

		if verbose {
			println("Reading page...")
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			println("Couldn't read that page!")
			exitCode = NAGIOS_UNKNOWN
		}

		var found int
		queryCount := argCount - 1

		for _, s := range args[1:] {
			if bytes.Contains(body, []byte(s)) {
				found++
				if verbose {
					println("Found:", s)
				}
			} else {
				if verbose {
					println("Not found:", s)
				}
			}
		}

		var statusMessage string

		switch {
		case queryCount == found:
			statusMessage = "OK."
			exitCode = NAGIOS_OK
		case found == 0 || requireAll:
			statusMessage = "FAIL."
			exitCode = NAGIOS_CRITICAL
		default:
			statusMessage = "Some OK."
			exitCode = NAGIOS_WARNING
		}

		if !quiet {
			fmt.Printf("Found %v of %v %v\n", found, queryCount, statusMessage)
		}
		if verbose {
			println("Nagios exit code:", exitCode)
		}

	}

	app.Run(os.Args)

	os.Exit(exitCode)
}
<commit_msg>Removed pointless '--quiet' flag.<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/cli"
	"io/ioutil"
	"net/http"
	"os"
)

const (
	NAGIOS_OK       int = 0
	NAGIOS_WARNING  int = 1
	NAGIOS_CRITICAL int = 2
	NAGIOS_UNKNOWN  int = 3
)

var exitCode int

func main() {

	app := cli.NewApp()
	app.Name = "check_hypersearch"
	app.Version = "1.0.0"
	app.Author = "Paul Swanson"
	app.Usage = "Search for text on a web page"
	app.Flags = []cli.Flag{
		cli.StringFlag{"require,r", "all", "Require 'all' or 'some'"},
		cli.BoolFlag{"verbose", "Be verbose; for debugging etc."},
	}

	cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}

USAGE:
{{.Name}} [global options] [arguments...]

For example, {{.Name}} --require some http://www.google.com/ "<title>Google</title>" "Privacy & Terms"

VERSION:
{{.Version}}

GLOBAL OPTIONS:
{{range .Flags}}{{.}}
{{end}}
`

	app.Action = func(c *cli.Context) {

		var requireAll, verbose bool

		if c.String("require") == "some" {
			requireAll = false
		} else {
			requireAll = true
		}

		if c.Bool("verbose") {
			verbose = true
		}

		args := c.Args()
		argCount := len(args)

		if argCount < 2 {
			cli.ShowAppHelp(c)
			exitCode = NAGIOS_UNKNOWN
			return
		}

		if verbose {
			println("Accessing", args[0])
		}
		resp, err := http.Get(args[0])
		if err != nil {
			println("Couldn't access that link!")
			exitCode = NAGIOS_UNKNOWN
			return
		}
		defer resp.Body.Close()

		if verbose {
			println("Reading page...")
		}
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			println("Couldn't read that page!")
			exitCode = NAGIOS_UNKNOWN
		}

		var found int
		queryCount := argCount - 1

		for _, s := range args[1:] {
			if bytes.Contains(body, []byte(s)) {
				found++
				if verbose {
					println("Found:", s)
				}
			} else {
				if verbose {
					println("Not found:", s)
				}
			}
		}

		var statusMessage string

		switch {
		case queryCount == found:
			statusMessage = "OK."
			exitCode = NAGIOS_OK
		case found == 0 || requireAll:
			statusMessage = "FAIL."
			exitCode = NAGIOS_CRITICAL
		default:
			statusMessage = "Some OK."
			exitCode = NAGIOS_WARNING
		}

		fmt.Printf("Found %v of %v %v\n", found, queryCount, statusMessage)

		if verbose {
			println("Nagios exit code:", exitCode)
		}

	}

	app.Run(os.Args)

	os.Exit(exitCode)
}
<|endoftext|>