<commit_before>package filestore

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"

	pb "github.com/ipfs/go-ipfs/filestore/pb"

	dshelp "gx/ipfs/QmNP2u7bofwUQptHQGPfabGWtTCbxhNLSZKqbf1uzsup9V/go-ipfs-ds-help"
	proto "gx/ipfs/QmT6n4mspWYEya864BhCUJEgyxiRfmiSY9ruQwTUNpRKaM/protobuf/proto"
	blocks "gx/ipfs/QmTRCUvZLiir12Qr6MV3HKfKMHX8Nf1Vddn6t2g5nsQSb9/go-block-format"
	posinfo "gx/ipfs/QmUWsXLvYYDAaoAt9TPZpFX4ffHHMg46AHrz1ZLTN5ABbe/go-ipfs-posinfo"
	cid "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid"
	blockstore "gx/ipfs/QmdpuJBPBZ6sLPj9BQpn3Rpi38BT2cF1QMiUfyzNWeySW4/go-ipfs-blockstore"
	ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
	dsns "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/namespace"
	dsq "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/query"
)

// FilestorePrefix identifies the key prefix for FileManager blocks.
var FilestorePrefix = ds.NewKey("filestore")

// FileManager is a blockstore implementation which stores special
// blocks FilestoreNode type. These nodes only contain a reference
// to the actual location of the block data in the filesystem
// (a path and an offset).
type FileManager struct {
	AllowFiles bool
	AllowUrls  bool
	ds         ds.Batching
	root       string
}

// CorruptReferenceError implements the error interface.
// It is used to indicate that the block contents pointed
// by the referencing blocks cannot be retrieved (i.e. the
// file is not found, or the data changed as it was being read).
type CorruptReferenceError struct {
	Code Status
	Err  error
}

// Error() returns the error message in the CorruptReferenceError
// as a string.
func (c CorruptReferenceError) Error() string {
	return c.Err.Error()
}

// NewFileManager initializes a new file manager with the given
// datastore and root. All FilestoreNodes paths are relative to the
// root path given here, which is prepended for any operations.
func NewFileManager(ds ds.Batching, root string) *FileManager {
	return &FileManager{ds: dsns.Wrap(ds, FilestorePrefix), root: root}
}

// AllKeysChan returns a channel from which to read the keys stored in
// the FileManager. If the given context is cancelled the channel will be
// closed.
func (f *FileManager) AllKeysChan(ctx context.Context) (<-chan *cid.Cid, error) {
	q := dsq.Query{KeysOnly: true}

	res, err := f.ds.Query(q)
	if err != nil {
		return nil, err
	}

	out := make(chan *cid.Cid, dsq.KeysOnlyBufSize)
	go func() {
		defer close(out)
		for {
			v, ok := res.NextSync()
			if !ok {
				return
			}

			k := ds.RawKey(v.Key)
			c, err := dshelp.DsKeyToCid(k)
			if err != nil {
				log.Errorf("decoding cid from filestore: %s", err)
				continue
			}

			select {
			case out <- c:
			case <-ctx.Done():
				return
			}
		}
	}()

	return out, nil
}

// DeleteBlock deletes the reference-block from the underlying
// datastore. It does not touch the referenced data.
func (f *FileManager) DeleteBlock(c *cid.Cid) error {
	err := f.ds.Delete(dshelp.CidToDsKey(c))
	if err == ds.ErrNotFound {
		return blockstore.ErrNotFound
	}
	return err
}

// Get reads a block from the datastore. Reading a block
// is done in two steps: the first step retrieves the reference
// block from the datastore. The second step uses the stored
// path and offsets to read the raw block data directly from disk.
func (f *FileManager) Get(c *cid.Cid) (blocks.Block, error) {
	dobj, err := f.getDataObj(c)
	if err != nil {
		return nil, err
	}
	out, err := f.readDataObj(c, dobj)
	if err != nil {
		return nil, err
	}

	return blocks.NewBlockWithCid(out, c)
}

func (f *FileManager) readDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	if !IsURL(d.GetFilePath()) {
		return f.readFileDataObj(c, d)
	} else {
		return f.readURLDataObj(c, d)
	}
}

func (f *FileManager) getDataObj(c *cid.Cid) (*pb.DataObj, error) {
	o, err := f.ds.Get(dshelp.CidToDsKey(c))
	switch err {
	case ds.ErrNotFound:
		return nil, blockstore.ErrNotFound
	default:
		return nil, err
	case nil:
		//
	}

	return unmarshalDataObj(o)
}

func unmarshalDataObj(o interface{}) (*pb.DataObj, error) {
	data, ok := o.([]byte)
	if !ok {
		return nil, fmt.Errorf("stored filestore dataobj was not a []byte")
	}

	var dobj pb.DataObj
	if err := proto.Unmarshal(data, &dobj); err != nil {
		return nil, err
	}

	return &dobj, nil
}

func (f *FileManager) readFileDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	if !f.AllowFiles {
		return nil, fmt.Errorf("filestore not enabled")
	}

	p := filepath.FromSlash(d.GetFilePath())
	abspath := filepath.Join(f.root, p)

	fi, err := os.Open(abspath)
	if os.IsNotExist(err) {
		return nil, &CorruptReferenceError{StatusFileNotFound, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}
	defer fi.Close()

	_, err = fi.Seek(int64(d.GetOffset()), io.SeekStart)
	if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}

	outbuf := make([]byte, d.GetSize_())
	_, err = io.ReadFull(fi, outbuf)
	if err == io.EOF || err == io.ErrUnexpectedEOF {
		return nil, &CorruptReferenceError{StatusFileChanged, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}

	outcid, err := c.Prefix().Sum(outbuf)
	if err != nil {
		return nil, err
	}

	if !c.Equals(outcid) {
		return nil, &CorruptReferenceError{StatusFileChanged,
			fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
	}

	return outbuf, nil
}

// reads and verifies the block from URL
func (f *FileManager) readURLDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	if !f.AllowUrls {
		return nil, fmt.Errorf("urlstore not enabled")
	}

	req, err := http.NewRequest("GET", d.GetFilePath(), nil)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", d.GetOffset(), d.GetOffset()+d.GetSize_()-1))

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	if res.StatusCode != http.StatusPartialContent {
		return nil, fmt.Errorf("expected HTTP 206 got %d", res.StatusCode)
	}

	outbuf := make([]byte, d.GetSize_())
	_, err = io.ReadFull(res.Body, outbuf)
	if err == io.EOF || err == io.ErrUnexpectedEOF {
		return nil, &CorruptReferenceError{StatusFileChanged, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}
	res.Body.Close()

	outcid, err := c.Prefix().Sum(outbuf)
	if err != nil {
		return nil, err
	}

	if !c.Equals(outcid) {
		return nil, &CorruptReferenceError{StatusFileChanged,
			fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
	}

	return outbuf, nil
}

// Has returns if the FileManager is storing a block reference. It does not
// validate the data, nor checks if the reference is valid.
func (f *FileManager) Has(c *cid.Cid) (bool, error) {
	// NOTE: interesting thing to consider. Has doesnt validate the data.
	// So the data on disk could be invalid, and we could think we have it.
	dsk := dshelp.CidToDsKey(c)
	return f.ds.Has(dsk)
}

type putter interface {
	Put(ds.Key, interface{}) error
}

// Put adds a new reference block to the FileManager. It does not check
// that the reference is valid.
func (f *FileManager) Put(b *posinfo.FilestoreNode) error {
	return f.putTo(b, f.ds)
}

func (f *FileManager) putTo(b *posinfo.FilestoreNode, to putter) error {
	var dobj pb.DataObj

	if !IsURL(b.PosInfo.FullPath) {
		if !f.AllowFiles {
			return fmt.Errorf("filestore not enabled")
		}
		if !filepath.HasPrefix(b.PosInfo.FullPath, f.root) {
			return fmt.Errorf("cannot add filestore references outside ipfs root (%s)", f.root)
		}

		p, err := filepath.Rel(f.root, b.PosInfo.FullPath)
		if err != nil {
			return err
		}

		dobj.FilePath = proto.String(filepath.ToSlash(p))
	} else {
		if !f.AllowUrls {
			return fmt.Errorf("urlstore not enabled")
		}
		dobj.FilePath = proto.String(b.PosInfo.FullPath)
	}
	dobj.Offset = proto.Uint64(b.PosInfo.Offset)
	dobj.Size_ = proto.Uint64(uint64(len(b.RawData())))

	data, err := proto.Marshal(&dobj)
	if err != nil {
		return err
	}

	return to.Put(dshelp.CidToDsKey(b.Cid()), data)
}

// PutMany is like Put() but takes a slice of blocks instead,
// allowing it to create a batch transaction.
func (f *FileManager) PutMany(bs []*posinfo.FilestoreNode) error {
	batch, err := f.ds.Batch()
	if err != nil {
		return err
	}

	for _, b := range bs {
		if err := f.putTo(b, batch); err != nil {
			return err
		}
	}

	return batch.Commit()
}

func IsURL(str string) bool {
	return (len(str) > 7 && str[0] == 'h' && str[1] == 't' && str[2] == 't' && str[3] == 'p') &&
		((len(str) > 8 && str[4] == 's' && str[5] == ':' && str[6] == '/' && str[7] == '/') ||
			(str[4] == ':' && str[5] == '/' && str[6] == '/'))
}
<commit_msg>Return better error code when an http request failed.<commit_after>package filestore

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"

	pb "github.com/ipfs/go-ipfs/filestore/pb"

	dshelp "gx/ipfs/QmNP2u7bofwUQptHQGPfabGWtTCbxhNLSZKqbf1uzsup9V/go-ipfs-ds-help"
	proto "gx/ipfs/QmT6n4mspWYEya864BhCUJEgyxiRfmiSY9ruQwTUNpRKaM/protobuf/proto"
	blocks "gx/ipfs/QmTRCUvZLiir12Qr6MV3HKfKMHX8Nf1Vddn6t2g5nsQSb9/go-block-format"
	posinfo "gx/ipfs/QmUWsXLvYYDAaoAt9TPZpFX4ffHHMg46AHrz1ZLTN5ABbe/go-ipfs-posinfo"
	cid "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid"
	blockstore "gx/ipfs/QmdpuJBPBZ6sLPj9BQpn3Rpi38BT2cF1QMiUfyzNWeySW4/go-ipfs-blockstore"
	ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
	dsns "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/namespace"
	dsq "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/query"
)

// FilestorePrefix identifies the key prefix for FileManager blocks.
var FilestorePrefix = ds.NewKey("filestore")

// FileManager is a blockstore implementation which stores special
// blocks FilestoreNode type. These nodes only contain a reference
// to the actual location of the block data in the filesystem
// (a path and an offset).
type FileManager struct {
	AllowFiles bool
	AllowUrls  bool
	ds         ds.Batching
	root       string
}

// CorruptReferenceError implements the error interface.
// It is used to indicate that the block contents pointed
// by the referencing blocks cannot be retrieved (i.e. the
// file is not found, or the data changed as it was being read).
type CorruptReferenceError struct {
	Code Status
	Err  error
}

// Error() returns the error message in the CorruptReferenceError
// as a string.
func (c CorruptReferenceError) Error() string {
	return c.Err.Error()
}

// NewFileManager initializes a new file manager with the given
// datastore and root. All FilestoreNodes paths are relative to the
// root path given here, which is prepended for any operations.
func NewFileManager(ds ds.Batching, root string) *FileManager {
	return &FileManager{ds: dsns.Wrap(ds, FilestorePrefix), root: root}
}

// AllKeysChan returns a channel from which to read the keys stored in
// the FileManager. If the given context is cancelled the channel will be
// closed.
func (f *FileManager) AllKeysChan(ctx context.Context) (<-chan *cid.Cid, error) {
	q := dsq.Query{KeysOnly: true}

	res, err := f.ds.Query(q)
	if err != nil {
		return nil, err
	}

	out := make(chan *cid.Cid, dsq.KeysOnlyBufSize)
	go func() {
		defer close(out)
		for {
			v, ok := res.NextSync()
			if !ok {
				return
			}

			k := ds.RawKey(v.Key)
			c, err := dshelp.DsKeyToCid(k)
			if err != nil {
				log.Errorf("decoding cid from filestore: %s", err)
				continue
			}

			select {
			case out <- c:
			case <-ctx.Done():
				return
			}
		}
	}()

	return out, nil
}

// DeleteBlock deletes the reference-block from the underlying
// datastore. It does not touch the referenced data.
func (f *FileManager) DeleteBlock(c *cid.Cid) error {
	err := f.ds.Delete(dshelp.CidToDsKey(c))
	if err == ds.ErrNotFound {
		return blockstore.ErrNotFound
	}
	return err
}

// Get reads a block from the datastore. Reading a block
// is done in two steps: the first step retrieves the reference
// block from the datastore. The second step uses the stored
// path and offsets to read the raw block data directly from disk.
func (f *FileManager) Get(c *cid.Cid) (blocks.Block, error) {
	dobj, err := f.getDataObj(c)
	if err != nil {
		return nil, err
	}
	out, err := f.readDataObj(c, dobj)
	if err != nil {
		return nil, err
	}

	return blocks.NewBlockWithCid(out, c)
}

func (f *FileManager) readDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	if !IsURL(d.GetFilePath()) {
		return f.readFileDataObj(c, d)
	} else {
		return f.readURLDataObj(c, d)
	}
}

func (f *FileManager) getDataObj(c *cid.Cid) (*pb.DataObj, error) {
	o, err := f.ds.Get(dshelp.CidToDsKey(c))
	switch err {
	case ds.ErrNotFound:
		return nil, blockstore.ErrNotFound
	default:
		return nil, err
	case nil:
		//
	}

	return unmarshalDataObj(o)
}

func unmarshalDataObj(o interface{}) (*pb.DataObj, error) {
	data, ok := o.([]byte)
	if !ok {
		return nil, fmt.Errorf("stored filestore dataobj was not a []byte")
	}

	var dobj pb.DataObj
	if err := proto.Unmarshal(data, &dobj); err != nil {
		return nil, err
	}

	return &dobj, nil
}

func (f *FileManager) readFileDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	if !f.AllowFiles {
		return nil, fmt.Errorf("filestore not enabled")
	}

	p := filepath.FromSlash(d.GetFilePath())
	abspath := filepath.Join(f.root, p)

	fi, err := os.Open(abspath)
	if os.IsNotExist(err) {
		return nil, &CorruptReferenceError{StatusFileNotFound, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}
	defer fi.Close()

	_, err = fi.Seek(int64(d.GetOffset()), io.SeekStart)
	if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}

	outbuf := make([]byte, d.GetSize_())
	_, err = io.ReadFull(fi, outbuf)
	if err == io.EOF || err == io.ErrUnexpectedEOF {
		return nil, &CorruptReferenceError{StatusFileChanged, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}

	outcid, err := c.Prefix().Sum(outbuf)
	if err != nil {
		return nil, err
	}

	if !c.Equals(outcid) {
		return nil, &CorruptReferenceError{StatusFileChanged,
			fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
	}

	return outbuf, nil
}

// reads and verifies the block from URL
func (f *FileManager) readURLDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
	if !f.AllowUrls {
		return nil, fmt.Errorf("urlstore not enabled")
	}

	req, err := http.NewRequest("GET", d.GetFilePath(), nil)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", d.GetOffset(), d.GetOffset()+d.GetSize_()-1))

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}
	if res.StatusCode != http.StatusPartialContent {
		return nil, &CorruptReferenceError{StatusFileError,
			fmt.Errorf("expected HTTP 206 got %d", res.StatusCode)}
	}

	outbuf := make([]byte, d.GetSize_())
	_, err = io.ReadFull(res.Body, outbuf)
	if err == io.EOF || err == io.ErrUnexpectedEOF {
		return nil, &CorruptReferenceError{StatusFileChanged, err}
	} else if err != nil {
		return nil, &CorruptReferenceError{StatusFileError, err}
	}
	res.Body.Close()

	outcid, err := c.Prefix().Sum(outbuf)
	if err != nil {
		return nil, err
	}

	if !c.Equals(outcid) {
		return nil, &CorruptReferenceError{StatusFileChanged,
			fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
	}

	return outbuf, nil
}

// Has returns if the FileManager is storing a block reference. It does not
// validate the data, nor checks if the reference is valid.
func (f *FileManager) Has(c *cid.Cid) (bool, error) {
	// NOTE: interesting thing to consider. Has doesnt validate the data.
	// So the data on disk could be invalid, and we could think we have it.
	dsk := dshelp.CidToDsKey(c)
	return f.ds.Has(dsk)
}

type putter interface {
	Put(ds.Key, interface{}) error
}

// Put adds a new reference block to the FileManager. It does not check
// that the reference is valid.
func (f *FileManager) Put(b *posinfo.FilestoreNode) error {
	return f.putTo(b, f.ds)
}

func (f *FileManager) putTo(b *posinfo.FilestoreNode, to putter) error {
	var dobj pb.DataObj

	if !IsURL(b.PosInfo.FullPath) {
		if !f.AllowFiles {
			return fmt.Errorf("filestore not enabled")
		}
		if !filepath.HasPrefix(b.PosInfo.FullPath, f.root) {
			return fmt.Errorf("cannot add filestore references outside ipfs root (%s)", f.root)
		}

		p, err := filepath.Rel(f.root, b.PosInfo.FullPath)
		if err != nil {
			return err
		}

		dobj.FilePath = proto.String(filepath.ToSlash(p))
	} else {
		if !f.AllowUrls {
			return fmt.Errorf("urlstore not enabled")
		}
		dobj.FilePath = proto.String(b.PosInfo.FullPath)
	}
	dobj.Offset = proto.Uint64(b.PosInfo.Offset)
	dobj.Size_ = proto.Uint64(uint64(len(b.RawData())))

	data, err := proto.Marshal(&dobj)
	if err != nil {
		return err
	}

	return to.Put(dshelp.CidToDsKey(b.Cid()), data)
}

// PutMany is like Put() but takes a slice of blocks instead,
// allowing it to create a batch transaction.
func (f *FileManager) PutMany(bs []*posinfo.FilestoreNode) error {
	batch, err := f.ds.Batch()
	if err != nil {
		return err
	}

	for _, b := range bs {
		if err := f.putTo(b, batch); err != nil {
			return err
		}
	}

	return batch.Commit()
}

func IsURL(str string) bool {
	return (len(str) > 7 && str[0] == 'h' && str[1] == 't' && str[2] == 't' && str[3] == 'p') &&
		((len(str) > 8 && str[4] == 's' && str[5] == ':' && str[6] == '/' && str[7] == '/') ||
			(str[4] == ':' && str[5] == '/' && str[6] == '/'))
}
<|endoftext|>


<commit_before>package launcher

import (
	"os"
	"os/user"
	"path"
	"strings"

	"fmt"

	"github.com/ellcrys/util"
	"github.com/goware/urlx"
)

// Go defines a deployment helper for go cocoon.
type Go struct {
	name        string
	image       string
	userHome    string
	imgGoPath   string
	repoURL     string
	buildParams map[string]interface{}
}

// NewGo returns a new instance a golang
// cocoon code deployment helper.
func NewGo() *Go {
	g := &Go{
		name:      "go",
		image:     "ncodes/launch-go",
		userHome:  os.Getenv("HOME"),
		imgGoPath: "/go",
	}
	g.setUserHomeDir()
	return g
}

// GetName returns the name for the language
func (g *Go) GetName() string {
	return g.name
}

// GetImage returns the image name to deploy cocoon code on
func (g *Go) GetImage() string {
	return g.image
}

// setUserHomeDir sets the user home directory
func (g *Go) setUserHomeDir() error {
	usr, err := user.Current()
	if err != nil {
		return err
	}
	g.userHome = usr.HomeDir
	return nil
}

// GetDownloadDestination returns the location to save
// the downloaded go cocoon code to.
func (g *Go) GetDownloadDestination(url string) string {
	g.repoURL = url
	u, _ := urlx.Parse(url)
	repoID := strings.Trim(u.Path, "/")
	return path.Join(g.userHome, "/ccode/source", repoID)
}

// GetMountDestination returns the location in
// the container where the source code root will be mounted
func (g *Go) GetMountDestination(url string) string {
	u, _ := urlx.Parse(url)
	repoID := strings.Trim(u.Path, "/")
	return path.Join(g.imgGoPath, "src/github.com/", repoID)
}

// RequiresBuild returns true if cocoon codes written in
// go langugage requires a build process.
// During development, If RUN_ROOT_BIN env is set, it will return false as
// the run command will find and find the ccode binary in the repo root.
func (g *Go) RequiresBuild() bool {
	if len(os.Getenv("RUN_ROOT_BIN")) > 0 {
		return false
	}
	return true
}

// SetBuildParams sets and validates build parameters
func (g *Go) SetBuildParams(buildParams map[string]interface{}) error {
	g.buildParams = buildParams
	if pkgMgr := g.buildParams["pkg_mgr"]; pkgMgr != nil {
		if !util.InStringSlice([]string{"glide"}, pkgMgr.(string)) {
			return fmt.Errorf("invalid pkg_mgr value in build script")
		}
	}
	return nil
}

// GetBuildScript will return the script required
// to create an executable
func (g *Go) GetBuildScript() string {

	// run known package manager fetch routine
	pkgFetchCmd := ""
	if pkgMgr := g.buildParams["pkg_mgr"]; pkgMgr != nil {
		switch pkgMgr {
		case "glide":
			pkgFetchCmd = "glide install"
		}
	}

	return strings.Join(util.RemoveEmptyInStringSlice([]string{pkgFetchCmd, "go build -v -o /bin/ccode"}), " && ")
}

// GetRunScript returns the script required to start the
// cocoon code accodeording to the build and installation process
// of the language. If RUN_ROOT_BIN env is set, it will run the
// ccode binary located in the mount destination.
func (g *Go) GetRunScript() []string {
	script := []string{strings.Join([]string{"ccode"}, " && ")}

	// run ccode in the repo root if RUN_ROOT_BIN is set (for development only)
	if len(os.Getenv("RUN_ROOT_BIN")) > 0 {
		script = []string{"bash", "-c", fmt.Sprintf("cd %s && ./ccode", g.GetMountDestination(g.repoURL))}
	}

	return script
}
<commit_msg>Renamed `RUN_ROOT_BIN` to `DEV_RUN_ROOT_BIN`<commit_after>package launcher

import (
	"os"
	"os/user"
	"path"
	"strings"

	"fmt"

	"github.com/ellcrys/util"
	"github.com/goware/urlx"
)

// Go defines a deployment helper for go cocoon.
type Go struct {
	name        string
	image       string
	userHome    string
	imgGoPath   string
	repoURL     string
	buildParams map[string]interface{}
}

// NewGo returns a new instance a golang
// cocoon code deployment helper.
func NewGo() *Go {
	g := &Go{
		name:      "go",
		image:     "ncodes/launch-go",
		userHome:  os.Getenv("HOME"),
		imgGoPath: "/go",
	}
	g.setUserHomeDir()
	return g
}

// GetName returns the name for the language
func (g *Go) GetName() string {
	return g.name
}

// GetImage returns the image name to deploy cocoon code on
func (g *Go) GetImage() string {
	return g.image
}

// setUserHomeDir sets the user home directory
func (g *Go) setUserHomeDir() error {
	usr, err := user.Current()
	if err != nil {
		return err
	}
	g.userHome = usr.HomeDir
	return nil
}

// GetDownloadDestination returns the location to save
// the downloaded go cocoon code to.
func (g *Go) GetDownloadDestination(url string) string {
	g.repoURL = url
	u, _ := urlx.Parse(url)
	repoID := strings.Trim(u.Path, "/")
	return path.Join(g.userHome, "/ccode/source", repoID)
}

// GetMountDestination returns the location in
// the container where the source code root will be mounted
func (g *Go) GetMountDestination(url string) string {
	u, _ := urlx.Parse(url)
	repoID := strings.Trim(u.Path, "/")
	return path.Join(g.imgGoPath, "src/github.com/", repoID)
}

// RequiresBuild returns true if cocoon codes written in
// go langugage requires a build process.
// During development, If DEV_RUN_ROOT_BIN env is set, it will return false as
// the run command will find and find the ccode binary in the repo root.
func (g *Go) RequiresBuild() bool {
	if len(os.Getenv("DEV_RUN_ROOT_BIN")) > 0 {
		return false
	}
	return true
}

// SetBuildParams sets and validates build parameters
func (g *Go) SetBuildParams(buildParams map[string]interface{}) error {
	g.buildParams = buildParams
	if pkgMgr := g.buildParams["pkg_mgr"]; pkgMgr != nil {
		if !util.InStringSlice([]string{"glide"}, pkgMgr.(string)) {
			return fmt.Errorf("invalid pkg_mgr value in build script")
		}
	}
	return nil
}

// GetBuildScript will return the script required
// to create an executable
func (g *Go) GetBuildScript() string {

	// run known package manager fetch routine
	pkgFetchCmd := ""
	if pkgMgr := g.buildParams["pkg_mgr"]; pkgMgr != nil {
		switch pkgMgr {
		case "glide":
			pkgFetchCmd = "glide install"
		}
	}

	return strings.Join(util.RemoveEmptyInStringSlice([]string{pkgFetchCmd, "go build -v -o /bin/ccode"}), " && ")
}

// GetRunScript returns the script required to start the
// cocoon code accodeording to the build and installation process
// of the language. If DEV_RUN_ROOT_BIN env is set, it will run the
// ccode binary located in the mount destination.
func (g *Go) GetRunScript() []string {
	script := []string{strings.Join([]string{"ccode"}, " && ")}

	// run ccode in the repo root if DEV_RUN_ROOT_BIN is set (for development only)
	if len(os.Getenv("DEV_RUN_ROOT_BIN")) > 0 {
		script = []string{"bash", "-c", fmt.Sprintf("cd %s && ./ccode", g.GetMountDestination(g.repoURL))}
	}

	return script
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/bwmarrin/discordgo"
	"github.com/spf13/viper"
)

const commandPrefix = `$`
const nameSplit = `#`
const regelChan = `regeln`

var globalCommands = map[string]Command{
	"help": &Help{},
	"name": &Name{},
}
var ruleCommands = map[string]Command{
	"accept": &Accept{},
	"update": &UpdateRules{},
}

/**
Command represents the available chat commands
*/
type Command interface {
	Exec(s *discordgo.Session, m *discordgo.MessageCreate)
	Description() string
}

func messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {

	user, _ := s.User("@me")
	if m.Author.ID != user.ID {
		ch, _ := s.Channel(m.ChannelID)
		if ch.Name == regelChan && !strings.HasPrefix(m.Content, commandPrefix) {
			s.ChannelMessageDelete(ch.ID, m.ID)
			return
		}
		if !strings.HasPrefix(m.Content, commandPrefix) {
			return
		}

		strAr := strings.Split(m.Content, " ")
		str := strings.Replace(strAr[0], commandPrefix, "", 1)

		comm, ok := globalCommands[str]
		if !ok {
			if ch.Name != regelChan {
				return
			}
			comm, ok = ruleCommands[str]
			if !ok {
				s.ChannelMessageDelete(ch.ID, m.ID)
				return
			}
		}
		comm.Exec(s, m)

	}
}

func checkRuleEntry(s *discordgo.Session) (err error) {
	rules := viper.GetString("channel.rules")

	msgs, err := s.ChannelMessagesPinned(rules)
	if err != nil {
		return err
	}

	file, err := ioutil.ReadFile(viper.GetString("regeln"))
	if err != nil {
		return err
	}

	if len(msgs) == 0 {
		max := 0
		for i := 0; i < len(file); i += max {
			max = i + 2000
			if len(file) <= max {
				max = len(file)
			} else {
				max = strings.LastIndex(string(file[:max]), "\n")
			}
			_, err := s.ChannelMessageSend(rules, string(file[i:max]))
			if err != nil {
				return err
			}
		}
	}

	return nil

}

/**
Accept is the command to accept the rules
*/
type Accept struct {
	Command
}

func (a *Accept) Description() string {
	return `Akzeptiert die Regeln und weist dem User die 'Mitglied'-Rolle zu.`
}

func (a *Accept) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	ch, _ := s.Channel(m.ChannelID)
	if ch.Name != regelChan {
		return
	}
	err := s.GuildMemberRoleAdd(ch.GuildID, m.Author.ID, viper.GetString("mitgliedRollenId"))
	if err != nil {
		fmt.Println(err)
	}
	s.ChannelMessageDelete(ch.ID, m.ID)

}

/**
UpdateRules removes all messages from the channel
and updates the rules */
type UpdateRules struct {
	Command
}

func (u *UpdateRules) Description() string {
	return `Aktualisiert die Regeln.`
}

func (u *UpdateRules) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	ch, _ := s.Channel(m.ChannelID)
	if ch.Name != regelChan {
		return
	}
	msgs, err := s.ChannelMessages(ch.ID, 100, "999999999999999999", "000000000000000000")
	if err != nil {
		fmt.Println(err)
	}
	for _, msg := range msgs {
		s.ChannelMessageDelete(ch.ID, msg.ID)
	}
	checkRuleEntry(s)
}

type Help struct {
	Command
}

func (h *Help) Description() string {
	return `Gibt die verfügbaren Kommandos aus.`
}

func (h *Help) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	printer := func(title string, cmdMap map[string]Command) string {
		m := fmt.Sprintf("**%s**\n", title)
		for k, v := range cmdMap {
			m += fmt.Sprintf(" - `$%s`: %s\n", k, v.Description())
		}
		return m
	}

	msg := printer("Globale Kommandos", globalCommands)
	msg += "\n"
	msg += printer("Kommandos für den Regelchannel", ruleCommands)
	s.ChannelMessageSend(m.ChannelID, msg)
}

type Name struct {
	Command
}

func (n *Name) Description() string {
	return `Registriere deinen GW2 Namen.`
}

func (n *Name) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	ch, _ := s.Channel(m.ChannelID)
	fmt.Println(m.Content)
	if !strings.Contains(m.Content, " ") {
		s.ChannelMessageSend(m.ChannelID, "Kein Name angegeben.")
		return
	}
	newname := strings.SplitN(string(m.Content), " ", 2)[1]

	err := s.GuildMemberNickname(ch.GuildID, m.Author.ID, m.Author.Username+nameSplit+newname)
	if err != nil {
		fmt.Println(err)
	}
}
<commit_msg>Remove all messages from rule channel<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/bwmarrin/discordgo"
	"github.com/spf13/viper"
)

const commandPrefix = `$`
const nameSplit = `#`
const regelChan = `regeln`

var globalCommands = map[string]Command{
	"help": &Help{},
	"name": &Name{},
}
var ruleCommands = map[string]Command{
	"accept": &Accept{},
	"update": &UpdateRules{},
}

/**
Command represents the available chat commands
*/
type Command interface {
	Exec(s *discordgo.Session, m *discordgo.MessageCreate)
	Description() string
}

func messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {

	user, _ := s.User("@me")
	if m.Author.ID != user.ID {
		ch, _ := s.Channel(m.ChannelID)
		if ch.Name == regelChan {
			s.ChannelMessageDelete(ch.ID, m.ID)
			return
		}
		if !strings.HasPrefix(m.Content, commandPrefix) {
			return
		}

		strAr := strings.Split(m.Content, " ")
		str := strings.Replace(strAr[0], commandPrefix, "", 1)

		comm, ok := globalCommands[str]
		if !ok {
			if ch.Name != regelChan {
				return
			}
			comm, ok = ruleCommands[str]
			if !ok {
				return
			}
		}
		comm.Exec(s, m)

	}
}

func checkRuleEntry(s *discordgo.Session) (err error) {
	rules := viper.GetString("channel.rules")

	msgs, err := s.ChannelMessagesPinned(rules)
	if err != nil {
		return err
	}

	file, err := ioutil.ReadFile(viper.GetString("regeln"))
	if err != nil {
		return err
	}

	if len(msgs) == 0 {
		max := 0
		for i := 0; i < len(file); i += max {
			max = i + 2000
			if len(file) <= max {
				max = len(file)
			} else {
				max = strings.LastIndex(string(file[:max]), "\n")
			}
			_, err := s.ChannelMessageSend(rules, string(file[i:max]))
			if err != nil {
				return err
			}
		}
	}

	return nil

}

/**
Accept is the command to accept the rules
*/
type Accept struct {
	Command
}

func (a *Accept) Description() string {
	return `Akzeptiert die Regeln und weist dem User die 'Mitglied'-Rolle zu.`
}

func (a *Accept) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	ch, _ := s.Channel(m.ChannelID)
	if ch.Name != regelChan {
		return
	}
	err := s.GuildMemberRoleAdd(ch.GuildID, m.Author.ID, viper.GetString("mitgliedRollenId"))
	if err != nil {
		fmt.Println(err)
	}
	s.ChannelMessageDelete(ch.ID, m.ID)

}

/**
UpdateRules removes all messages from the channel
and updates the rules */
type UpdateRules struct {
	Command
}

func (u *UpdateRules) Description() string {
	return `Aktualisiert die Regeln.`
}

func (u *UpdateRules) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	ch, _ := s.Channel(m.ChannelID)
	if ch.Name != regelChan {
		return
	}
	msgs, err := s.ChannelMessages(ch.ID, 100, "999999999999999999", "000000000000000000")
	if err != nil {
		fmt.Println(err)
	}
	for _, msg := range msgs {
		s.ChannelMessageDelete(ch.ID, msg.ID)
	}
	checkRuleEntry(s)
}

type Help struct {
	Command
}

func (h *Help) Description() string {
	return `Gibt die verfügbaren Kommandos aus.`
}

func (h *Help) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	printer := func(title string, cmdMap map[string]Command) string {
		m := fmt.Sprintf("**%s**\n", title)
		for k, v := range cmdMap {
			m += fmt.Sprintf(" - `$%s`: %s\n", k, v.Description())
		}
		return m
	}

	msg := printer("Globale Kommandos", globalCommands)
	msg += "\n"
	msg += printer("Kommandos für den Regelchannel", ruleCommands)
	s.ChannelMessageSend(m.ChannelID, msg)
}

type Name struct {
	Command
}

func (n *Name) Description() string {
	return `Registriere deinen GW2 Namen.`
}

func (n *Name) Exec(s *discordgo.Session, m *discordgo.MessageCreate) {
	ch, _ := s.Channel(m.ChannelID)
	fmt.Println(m.Content)
	if !strings.Contains(m.Content, " ") {
		s.ChannelMessageSend(m.ChannelID, "Kein Name angegeben.")
		return
	}
	newname := strings.SplitN(string(m.Content), " ", 2)[1]

	err := s.GuildMemberNickname(ch.GuildID, m.Author.ID, m.Author.Username+nameSplit+newname)
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/cbednarski/hostess/hostess"
)

type Options struct {
	Preview bool
}

// PrintErrLn will print to stderr followed by a newline
func PrintErrLn(err error) {
	os.Stderr.WriteString(fmt.Sprintf("%s\n", err))
}

// LoadHostfile will try to load, parse, and return a Hostfile. If we
// encounter errors we will terminate, unless -f is passed.
func LoadHostfile() (*hostess.Hostfile, error) {
	hosts, errs := hostess.LoadHostfile()

	if len(errs) > 0 {
		for _, err := range errs {
			PrintErrLn(err)
		}
		return nil, errors.New("Errors while parsing hostsfile. Please fix any dupes or conflicts and try again.")
	}

	return hosts, nil
}

// SaveOrPreview will display or write the Hostfile
func SaveOrPreview(options *Options, hostfile *hostess.Hostfile) error {
	// If -n is passed, no-op and output the resultant hosts file to stdout.
	// Otherwise it's for real and we're going to write it.
	if options.Preview {
		fmt.Printf("%s", hostfile.Format())
		return nil
	}

	if err := hostfile.Save(); err != nil {
		return fmt.Errorf("Unable to write to %s. (error: %s)", hostess.GetHostsPath(), err)
	}

	return nil
}

// StrPadRight adds spaces to the right of a string until it reaches length.
// If the input string is already that long, do nothing.
func StrPadRight(input string, length int) string {
	minimum := len(input)
	if length <= minimum {
		return input
	}
	return input + strings.Repeat(" ", length-minimum)
}

// Add command parses <hostname> <ip> and adds or updates a hostname in the
// hosts file
func Add(options *Options, hostname, ip string) error {
	hostsfile, err := LoadHostfile()

	newHostname, err := hostess.NewHostname(hostname, ip, true)
	if err != nil {
		return err
	}

	replaced := hostsfile.Hosts.ContainsDomain(newHostname.Domain)
	// Note that Add() may return an error, but they are informational only. We
	// don't actually care what the error is -- we just want to add the
	// hostname and save the file. This way the behavior is idempotent.
	hostsfile.Hosts.Add(newHostname)

	// If the user passes -n then we'll Add and show the new hosts file, but
	// not save it.
	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}
	// We'll give a little bit of information about whether we added or
	// updated, but if the user wants to know they can use has or ls to
	// show the file before they run the operation. Maybe later we can add
	// a verbose flag to show more information.
	if replaced {
		fmt.Printf("Updated %s\n", newHostname.FormatHuman())
	} else {
		fmt.Printf("Added %s\n", newHostname.FormatHuman())
	}

	return nil
}

// Remove command removes any hostname(s) matching <domain> from the hosts file
func Remove(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	found := hostsfile.Hosts.ContainsDomain(hostname)
	if !found {
		fmt.Printf("%s not found in %s", hostname, hostess.GetHostsPath())
	}

	hostsfile.Hosts.RemoveDomain(hostname)
	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}
	fmt.Printf("Deleted %s\n", hostname)

	return nil
}

// Has command indicates whether a hostname is present in the hosts file
func Has(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	found := hostsfile.Hosts.ContainsDomain(hostname)
	if found {
		fmt.Printf("Found %s in %s\n", hostname, hostess.GetHostsPath())
	} else {
		fmt.Printf("%s not found in %s\n", hostname, hostess.GetHostsPath())
		// Exit 1 for bash scripts to use this as a check
		os.Exit(1)
	}
	return nil
}

func Enable(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if err := hostsfile.Hosts.Enable(hostname); err != nil {
		return err
	}

	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}

	fmt.Printf("Enabled %s\n", hostname)

	return nil
}

func Disable(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if err := hostsfile.Hosts.Disable(hostname); err != nil {
		if err == hostess.ErrHostnameNotFound {
			// If the hostname does not exist then we have still achieved the
			// desired result, so we will not exit with an error here. We'll
			// handle the error by displaying it to the user.
			PrintErrLn(err)
			return nil
		}
		return err
	}

	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}

	fmt.Printf("Disabled %s\n", hostname)

	return nil
}

// List command shows a list of hostnames in the hosts file
func List(options *Options) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	widestHostname := 0
	widestIP := 0

	for _, hostname := range hostsfile.Hosts {
		dlen := len(hostname.Domain)
		if dlen > widestHostname {
			widestHostname = dlen
		}
		ilen := len(hostname.IP)
		if ilen > widestIP {
			widestIP = ilen
		}
	}

	for _, hostname := range hostsfile.Hosts {
		fmt.Printf("%s -> %s %s\n",
			StrPadRight(hostname.Domain, widestHostname),
			StrPadRight(hostname.IP.String(), widestIP),
			hostname.FormatEnabled())
	}

	return nil
}

// Format command removes duplicates and conflicts from the hosts file
func Format(options *Options) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if bytes.Equal(hostsfile.GetData(), hostsfile.Format()) {
		fmt.Printf("%s is already formatted and contains no dupes or conflicts; nothing to do\n", hostess.GetHostsPath())
		return nil
	}

	return SaveOrPreview(options, hostsfile)
}

// Dump command outputs hosts file contents as JSON
func Dump(options *Options) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	jsonbytes, err := hostsfile.Hosts.Dump()
	if err != nil {
		return err
	}

	fmt.Println(fmt.Sprintf("%s", jsonbytes))
	return nil
}

// Apply command adds hostnames to the hosts file from JSON
func Apply(options *Options, filename string) error {
	jsonbytes, err := ioutil.ReadFile(filename)
	if err != nil {
		return fmt.Errorf("Unable to read JSON from %s: %s", filename, err)
	}

	hostfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if err := hostfile.Hosts.Apply(jsonbytes); err != nil {
		return fmt.Errorf("Error applying changes to hosts file: %s", err)
	}

	if err := SaveOrPreview(options, hostfile); err != nil {
		return err
	}

	fmt.Printf("%s applied\n", filename)
	return nil
}
<commit_msg>Don't fail when a duplicate is encountered (fixes #39)<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/cbednarski/hostess/hostess"
)

type Options struct {
	Preview bool
}

// PrintErrLn will print to stderr followed by a newline
func PrintErrLn(err error) {
	os.Stderr.WriteString(fmt.Sprintf("%s\n", err))
}

// LoadHostfile will try to load, parse, and return a Hostfile. If we
// encounter errors we will terminate, unless -f is passed.
func LoadHostfile() (*hostess.Hostfile, error) {
	hosts, errs := hostess.LoadHostfile()

	fatal := false

	if len(errs) > 0 {
		for _, err := range errs {
			PrintErrLn(err)
			// If we find a duplicate we'll notify the user and continue. For
			// other errors we'll bail out.
			if !strings.Contains(err.Error(), "duplicate hostname entry") {
				fatal = true
			}
		}
	}

	if fatal {
		return nil, errors.New("Errors while parsing hostsfile. Please resolve any conflicts and try again.")
	}

	return hosts, nil
}

// SaveOrPreview will display or write the Hostfile
func SaveOrPreview(options *Options, hostfile *hostess.Hostfile) error {
	// If -n is passed, no-op and output the resultant hosts file to stdout.
	// Otherwise it's for real and we're going to write it.
	if options.Preview {
		fmt.Printf("%s", hostfile.Format())
		return nil
	}

	if err := hostfile.Save(); err != nil {
		return fmt.Errorf("Unable to write to %s. (error: %s)", hostess.GetHostsPath(), err)
	}

	return nil
}

// StrPadRight adds spaces to the right of a string until it reaches length.
// If the input string is already that long, do nothing.
func StrPadRight(input string, length int) string {
	minimum := len(input)
	if length <= minimum {
		return input
	}
	return input + strings.Repeat(" ", length-minimum)
}

// Add command parses <hostname> <ip> and adds or updates a hostname in the
// hosts file
func Add(options *Options, hostname, ip string) error {
	hostsfile, err := LoadHostfile()

	newHostname, err := hostess.NewHostname(hostname, ip, true)
	if err != nil {
		return err
	}

	replaced := hostsfile.Hosts.ContainsDomain(newHostname.Domain)
	// Note that Add() may return an error, but they are informational only. We
	// don't actually care what the error is -- we just want to add the
	// hostname and save the file. This way the behavior is idempotent.
	hostsfile.Hosts.Add(newHostname)

	// If the user passes -n then we'll Add and show the new hosts file, but
	// not save it.
	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}
	// We'll give a little bit of information about whether we added or
	// updated, but if the user wants to know they can use has or ls to
	// show the file before they run the operation. Maybe later we can add
	// a verbose flag to show more information.
	if replaced {
		fmt.Printf("Updated %s\n", newHostname.FormatHuman())
	} else {
		fmt.Printf("Added %s\n", newHostname.FormatHuman())
	}

	return nil
}

// Remove command removes any hostname(s) matching <domain> from the hosts file
func Remove(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	found := hostsfile.Hosts.ContainsDomain(hostname)
	if !found {
		fmt.Printf("%s not found in %s", hostname, hostess.GetHostsPath())
	}

	hostsfile.Hosts.RemoveDomain(hostname)
	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}
	fmt.Printf("Deleted %s\n", hostname)

	return nil
}

// Has command indicates whether a hostname is present in the hosts file
func Has(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	found := hostsfile.Hosts.ContainsDomain(hostname)
	if found {
		fmt.Printf("Found %s in %s\n", hostname, hostess.GetHostsPath())
	} else {
		fmt.Printf("%s not found in %s\n", hostname, hostess.GetHostsPath())
		// Exit 1 for bash scripts to use this as a check
		os.Exit(1)
	}
	return nil
}

func Enable(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if err := hostsfile.Hosts.Enable(hostname); err != nil {
		return err
	}

	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}

	fmt.Printf("Enabled %s\n", hostname)

	return nil
}

func Disable(options *Options, hostname string) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if err := hostsfile.Hosts.Disable(hostname); err != nil {
		if err == hostess.ErrHostnameNotFound {
			// If the hostname does not exist then we have still achieved the
			// desired result, so we will not exit with an error here. We'll
			// handle the error by displaying it to the user.
			PrintErrLn(err)
			return nil
		}
		return err
	}

	if err := SaveOrPreview(options, hostsfile); err != nil {
		return err
	}

	fmt.Printf("Disabled %s\n", hostname)

	return nil
}

// List command shows a list of hostnames in the hosts file
func List(options *Options) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	widestHostname := 0
	widestIP := 0

	for _, hostname := range hostsfile.Hosts {
		dlen := len(hostname.Domain)
		if dlen > widestHostname {
			widestHostname = dlen
		}
		ilen := len(hostname.IP)
		if ilen > widestIP {
			widestIP = ilen
		}
	}

	for _, hostname := range hostsfile.Hosts {
		fmt.Printf("%s -> %s %s\n",
			StrPadRight(hostname.Domain, widestHostname),
			StrPadRight(hostname.IP.String(), widestIP),
			hostname.FormatEnabled())
	}

	return nil
}

// Format command removes duplicates from the hosts file
func Format(options *Options) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if bytes.Equal(hostsfile.GetData(), hostsfile.Format()) {
		fmt.Printf("%s is already formatted and contains no dupes or conflicts; nothing to do\n", hostess.GetHostsPath())
		return nil
	}

	return SaveOrPreview(options, hostsfile)
}

// Dump command outputs hosts file contents as JSON
func Dump(options *Options) error {
	hostsfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	jsonbytes, err := hostsfile.Hosts.Dump()
	if err != nil {
		return err
	}

	fmt.Println(fmt.Sprintf("%s", jsonbytes))
	return nil
}

// Apply command adds hostnames to the hosts file from JSON
func Apply(options *Options, filename string) error {
	jsonbytes, err := ioutil.ReadFile(filename)
	if err != nil {
		return fmt.Errorf("Unable to read JSON from %s: %s", filename, err)
	}

	hostfile, err := LoadHostfile()
	if err != nil {
		return err
	}

	if err := hostfile.Hosts.Apply(jsonbytes); err != nil {
		return fmt.Errorf("Error applying changes to hosts file: %s", err)
	}

	if err := SaveOrPreview(options, hostfile); err != nil {
		return err
	}

	fmt.Printf("%s applied\n", filename)
	return nil
}
<|endoftext|>


<commit_before>//go:generate go run _tools/gen_commands.go

package main

import (
	"flag"
	"fmt"
	"os"
	"runtime"
	"time"

	"github.com/Songmu/prompter"
	"github.com/Songmu/retry"
	"github.com/mackerelio/mackerel-agent/command"
	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mackerel-agent/mackerel"
	"github.com/mackerelio/mackerel-agent/version"
)

/* +main - mackerel-agent

	mackerel-agent [options]

main process of mackerel-agent
*/
func doMain(fs *flag.FlagSet, argv []string) error {
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		return fmt.Errorf("failed to load config: %s", err)
	}
	return start(conf, make(chan struct{}))
}

/* +command init - initialize mackerel-agent.conf with apikey

	init -apikey=xxxxxxxxxxx [-conf=mackerel-agent.conf]

Initialize mackerel-agent.conf with api key.

- The conf file doesn't exist:
    create new file and set the apikey.
- The conf file exists and apikey is unset:
    set the apikey.
- The conf file exists and apikey already set:
    skip initializing. Don't overwrite apikey and exit normally.
- The conf file exists, but the contents of it is invalid toml:
    exit with error.
*/
func doInit(fs *flag.FlagSet, argv []string) error {
	err := doInitialize(fs, argv)
	if _, ok := err.(apikeyAlreadySetError); ok {
		logger.Infof("%s", err)
		return nil
	}
	return err
}

/* +command supervise - supervise mode

	supervise -conf mackerel-agent.conf ...

run as supervise mode enabling configuration reloading and crash recovery
*/
func doSupervise(fs *flag.FlagSet, argv []string) error {
	if runtime.GOOS == "windows" {
		return fmt.Errorf("supervise mode is not supported on windows")
	}
	copiedArgv := make([]string, len(argv))
	copy(copiedArgv, argv)
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		return err
	}
	setLogLevel(conf.Silent, conf.Verbose)
	err = createPidFile(conf.Pidfile)
	if err != nil {
		return err
	}
	defer removePidFile(conf.Pidfile)

	return (&supervisor{
		prog: os.Args[0],
		argv: copiedArgv,
	}).supervise(nil)
}

/* +command version - display version of mackerel-agent

	version

display the version of mackerel-agent
*/
func doVersion(_ *flag.FlagSet, _ []string) error {
	fmt.Printf("mackerel-agent version %s (rev %s) [%s %s %s] \n",
		version.VERSION, version.GITCOMMIT, runtime.GOOS, runtime.GOARCH, runtime.Version())
	return nil
}

/* +command configtest - configtest

	configtest

do configtest
*/
func doConfigtest(fs *flag.FlagSet, argv []string) error {
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		return fmt.Errorf("failed to test config: %s", err)
	}
	fmt.Fprintf(os.Stderr, "%s Syntax OK\n", conf.Conffile)
	return nil
}

/* +command retire - retire the host

	retire [-force]

retire the host
*/
func doRetire(fs *flag.FlagSet, argv []string) error {
	conf, force, err := resolveConfigForRetire(fs, argv)
	if err != nil {
		return fmt.Errorf("failed to load config: %s", err)
	}

	hostID, err := conf.LoadHostID()
	if err != nil {
		return fmt.Errorf("hostID file is not found")
	}

	api, err := mackerel.NewAPI(conf.Apibase, conf.Apikey, conf.Verbose)
	if err != nil {
		return fmt.Errorf("faild to create api client: %s", err)
	}

	if !force && !prompter.YN(fmt.Sprintf("retire this host? (hostID: %s)", hostID), false) {
		return fmt.Errorf("retirement is canceled")
	}

	err = retry.Retry(10, 3*time.Second, func() error {
		return api.RetireHost(hostID)
	})
	if err != nil {
		return fmt.Errorf("faild to retire the host: %s", err)
	}
	logger.Infof("This host (hostID: %s) has been retired.", hostID)
	// just to try to remove hostID file.
	err = conf.DeleteSavedHostID()
	if err != nil {
		logger.Warningf("Failed to remove HostID file: %s", err)
	}
	return nil
}

/* +command once - output onetime

	once

output metrics and meta data of the host one time.
These data are only displayed and not posted to Mackerel.
*/
func doOnce(fs *flag.FlagSet, argv []string) error {
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		logger.Warningf("failed to load config (but `once` must not required conf): %s", err)
		conf = &config.Config{}
	}
	command.RunOnce(conf)
	return nil
}
<commit_msg>fix comment<commit_after>//go:generate go run _tools/gen_commands.go

package main

import (
	"flag"
	"fmt"
	"os"
	"runtime"
	"time"

	"github.com/Songmu/prompter"
	"github.com/Songmu/retry"
	"github.com/mackerelio/mackerel-agent/command"
	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mackerel-agent/mackerel"
	"github.com/mackerelio/mackerel-agent/version"
)

/* +main - mackerel-agent

	mackerel-agent [options]

main process of mackerel-agent
*/
func doMain(fs *flag.FlagSet, argv []string) error {
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		return fmt.Errorf("failed to load config: %s", err)
	}
	return start(conf, make(chan struct{}))
}

/* +command init - initialize mackerel-agent.conf with apikey

	init -apikey=xxxxxxxxxxx [-conf=mackerel-agent.conf]

Initialize mackerel-agent.conf with api key.

- The conf file doesn't exist:
    create new file and set the apikey.
- The conf file exists and apikey is unset:
    set the apikey.
- The conf file exists and apikey already set:
    skip initializing. Don't overwrite apikey and exit normally.
- The conf file exists, but the contents of it is invalid toml:
    exit with error.
*/
func doInit(fs *flag.FlagSet, argv []string) error {
	err := doInitialize(fs, argv)
	if _, ok := err.(apikeyAlreadySetError); ok {
		logger.Infof("%s", err)
		return nil
	}
	return err
}

/* +command supervise - supervisor mode

	supervise -conf mackerel-agent.conf ...

run as supervisor mode enabling configuration reloading and crash recovery
*/
func doSupervise(fs *flag.FlagSet, argv []string) error {
	if runtime.GOOS == "windows" {
		return fmt.Errorf("supervise mode is not supported on windows")
	}
	copiedArgv := make([]string, len(argv))
	copy(copiedArgv, argv)
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		return err
	}
	setLogLevel(conf.Silent, conf.Verbose)
	err = createPidFile(conf.Pidfile)
	if err != nil {
		return err
	}
	defer removePidFile(conf.Pidfile)

	return (&supervisor{
		prog: os.Args[0],
		argv: copiedArgv,
	}).supervise(nil)
}

/* +command version - display version of mackerel-agent

	version

display the version of mackerel-agent
*/
func doVersion(_ *flag.FlagSet, _ []string) error {
	fmt.Printf("mackerel-agent version %s (rev %s) [%s %s %s] \n",
		version.VERSION, version.GITCOMMIT, runtime.GOOS, runtime.GOARCH, runtime.Version())
	return nil
}

/* +command configtest - configtest

	configtest

do configtest
*/
func doConfigtest(fs *flag.FlagSet, argv []string) error {
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		return fmt.Errorf("failed to test config: %s", err)
	}
	fmt.Fprintf(os.Stderr, "%s Syntax OK\n", conf.Conffile)
	return nil
}

/* +command retire - retire the host

	retire [-force]

retire the host
*/
func doRetire(fs *flag.FlagSet, argv []string) error {
	conf, force, err := resolveConfigForRetire(fs, argv)
	if err != nil {
		return fmt.Errorf("failed to load config: %s", err)
	}

	hostID, err := conf.LoadHostID()
	if err != nil {
		return fmt.Errorf("hostID file is not found")
	}

	api, err := mackerel.NewAPI(conf.Apibase, conf.Apikey, conf.Verbose)
	if err != nil {
		return fmt.Errorf("faild to create api client: %s", err)
	}

	if !force && !prompter.YN(fmt.Sprintf("retire this host? (hostID: %s)", hostID), false) {
		return fmt.Errorf("retirement is canceled")
	}

	err = retry.Retry(10, 3*time.Second, func() error {
		return api.RetireHost(hostID)
	})
	if err != nil {
		return fmt.Errorf("faild to retire the host: %s", err)
	}
	logger.Infof("This host (hostID: %s) has been retired.", hostID)
	// just to try to remove hostID file.
	err = conf.DeleteSavedHostID()
	if err != nil {
		logger.Warningf("Failed to remove HostID file: %s", err)
	}
	return nil
}

/* +command once - output onetime

	once

output metrics and meta data of the host one time.
These data are only displayed and not posted to Mackerel.
*/
func doOnce(fs *flag.FlagSet, argv []string) error {
	conf, err := resolveConfig(fs, argv)
	if err != nil {
		logger.Warningf("failed to load config (but `once` must not required conf): %s", err)
		conf = &config.Config{}
	}
	command.RunOnce(conf)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"math"
	"os"
	"os/signal"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/bjeanes/go-lifx/client"
	"github.com/ninjasphere/go-ninja"
)

type Light struct {
	Id            string
	Name          string
	Bus           *ninja.DeviceBus
	OnOffBus      *ninja.ChannelBus
	colorBus      *ninja.ChannelBus
	brightnessBus *ninja.ChannelBus
	Batch         bool
	batchBus      *ninja.ChannelBus
	LightState    *LightState
	lightClient   *client.Light
}

// https://github.com/LIFX/lifx-gem/blob/master/lib/lifx/protocol/light.rb
type LightState struct {
	On             *bool
	Brightness     *uint16
	Hue            *uint16
	Saturation     *uint16
	TransitionTime *uint16 //?
	XY             []float64
	ColorTemp      *uint16
}

func (l Light) GetJsonLightState() *simplejson.Json {
	st := l.LightState
	js := simplejson.New()
	js.Set("on", st.On)
	js.Set("bri", st.Brightness)
	js.Set("sat", st.Saturation)
	js.Set("hue", st.Hue)
	js.Set("ct", st.ColorTemp)
	js.Set("transitionTime", st.TransitionTime)
	js.Set("xy", st.XY)
	return js
}

func getOnOffBus(light *Light) *ninja.ChannelBus {
	methods := []string{"turnOn", "turnOff", "set"}
	events := []string{"state"}
	onOffBus, _ := light.Bus.AnnounceChannel("on-off", "on-off", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "turnOn":
			light.turnOnOff(true)
		case "turnOff":
			light.refreshLightState()
			light.turnOnOff(false)
		case "set":
			state, _ := payload.GetIndex(0).Bool()
			light.turnOnOff(state)
		default:
			log.Criticalf("On-off got an unknown method %s", method)
			return
		}
	})

	return onOffBus
}

func getBrightBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	brightnessBus, _ := light.Bus.AnnounceChannel("brightness", "brightness", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}

		switch method {
		case "set":
			brightness, _ := payload.GetIndex(0).Float64()
			light.setBrightness(brightness)
		default:
			log.Criticalf("Brightness got an unknown method %s", method)
			return
		}

	})

	return brightnessBus

}

func getColorBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	colorBus, _ := light.Bus.AnnounceChannel("color", "color", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "set":
			mode, err := payload.Get("mode").String()
			if err != nil {
				log.Criticalf("No mode sent to color bus: %s", err)
			}
			light.setColor(payload, mode)
		default:
			log.Criticalf("Color got an unknown method %s", method)
		}
	})

	return colorBus
}

func NewLight(bus *ninja.DriverBus, client *client.Light) (*Light, error) { //TODO cut this down!

	lightID := "1"
	lightState := createLightState()

	light := &Light{ //TODO fix when lib gets updated
		Id:         lightID,
		Name:       "lifx bulb",
		LightState: &lightState,
		Batch:      false,
		lightClient:			client,
	}

	sigs, _ := simplejson.NewJson([]byte(`{
      "ninja:manufacturer": "Lifx",
      "ninja:productName": "Lifx",
      "manufacturer:productModelId": "Lifx",
      "ninja:productType": "Light",
      "ninja:thingType": "light"
  }`))

	deviceBus, _ := bus.AnnounceDevice(lightID, "lifx", "lifx", sigs) //TODO fix when lib gets updated
	light.Bus = deviceBus
	light.OnOffBus = getOnOffBus(light)
	light.brightnessBus = getBrightBus(light)
	light.colorBus = getColorBus(light)
	light.batchBus = getBatchBus(light)

	return light, nil
}

func (l Light) StartBatch() {
	l.Batch = true
}

func (l Light) EndBatch() {
	l.Batch = false
	// l.User.SetLightState(l.Id, l.LightState) //TODO send actual state
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) turnOnOff(state bool) {

}

func (l Light) setBrightness(fbrightness float64) {

}

func (l Light) setColor(payload *simplejson.Json, mode string) {
	l.refreshLightState()
	switch mode {
	case "hue": //TODO less verbose plz
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		fhue, _ := payload.Get("hue").Float64()
		hue := uint16(fhue * math.MaxUint16)
		fsaturation, _ := payload.Get("saturation").Float64()
		saturation := uint16(fsaturation * math.MaxUint16)
		on := bool(true)
		l.LightState.Hue = &hue
		l.LightState.Saturation = &saturation
		l.LightState.On = &on
	case "xy":
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		x, _ := payload.Get("x").Float64()
		y, _ := payload.Get("y").Float64()
		xy := []float64{x, y}
		l.LightState.XY = xy
		l.LightState.Hue = nil
		l.LightState.Saturation = nil
		l.LightState.ColorTemp = nil
	case "temperature":
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		temp, _ := payload.Get("temperature").Float64()
		utemp := uint16(math.Floor(1000000 / temp))
		l.LightState.ColorTemp = &utemp
		l.LightState.XY = nil
		l.LightState.Hue = nil
		l.LightState.Saturation = nil
	default:
		log.Criticalf("Bad color mode: %s", mode)
		return
	}

	if !l.Batch {
		// l.User.SetLightState(l.Id, l.LightState) //TODO
		l.colorBus.SendEvent("state", l.GetJsonLightState())
	}

}

func (l Light) setTransition(transTime int) {
	transTime = transTime / 1000 //LIFX transition time in seconds
	utranstime := uint16(transTime)
	l.LightState.TransitionTime = &utranstime
}

func (l Light) setBatchColor(payload *simplejson.Json) {
	l.StartBatch()

	color := payload.Get("color")
	if color != nil {
		l.setColor(color, "hue")
	}

	if brightness, err := payload.Get("brightness").Float64(); err == nil {
		l.setBrightness(brightness)
	}

	if onoff, err := payload.Get("on-off").Bool(); err == nil {
		l.turnOnOff(onoff)
	}

	if transition, err := payload.Get("transition").Int(); err == nil {
		l.setTransition(transition)
	}

	l.EndBatch()
}

// type LightState struct {
//   On             *bool
//   Brightness     *uint16
//   Hue            *uint16
//   Saturation     *uint16
//   TransitionTime *uint16 //?
// }

func createLightState() LightState {

	on := bool(false)
	brightness := uint16(0)
	saturation := uint16(0)
	hueVal := uint16(0)
	transitionTime := uint16(0)

	lightState := LightState{
		On:             &on,
		Brightness:     &brightness,
		Saturation:     &saturation,
		Hue:            &hueVal,
		TransitionTime: &transitionTime,
	}

	return lightState
}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func (l Light) sendLightState() {

	// l.User.SetLightState(l.Id, l.LightState)
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) refreshLightState() { //TODO

}

func blinkAllLights() {
	c := client.New()
	c.Discover()

	for i:=0; i<3; i++ {
		for _, l := range c.Lights {
			l.TurnOff()
		}
		time.Sleep(1 * time.Second)
		for _, l := range c.Lights {
			l.TurnOn()
		}
		time.Sleep(1 * time.Second)
	}
}

func getBatchBus(light *Light) *ninja.ChannelBus {
	methods := []string{"setBatch"}
	events := []string{"state"}
	batchBus, _ := light.Bus.AnnounceChannel("core.batching", "core.batching", methods, events, func(method string, payload *simplejson.Json) {
		switch method {
		case "setBatch":
			light.setBatchColor(payload.GetIndex(0))
		default:
			log.Criticalf("Color got an unknown method %s", method)
			return
		}
	})

	return batchBus
}

func run() int {
	log.Infof("Starting " + drivername)

	conn, err := ninja.Connect("com.ninjablocks.lifx")
	bus, err := conn.AnnounceDriver("com.ninjablocks.hue", drivername, getCurDir())
	if err != nil {
		log.HandleError(err, "Could not get driver bus")
	}

	lightClient := client.New()
	lightClient.Discover()

	for _,l := range lightClient.Lights {
		_, err := NewLight(bus,&l)
		if err != nil {
			log.Criticalf("Error creating light instance",err)
		}

	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

	return 0
}
<commit_msg>added turn on/off methods<commit_after>package main

import (
	"fmt"

	"math"
	"os"
	"os/signal"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/bjeanes/go-lifx/client"
	"github.com/ninjasphere/go-ninja"
)

type Light struct {
	Id            string
	Name          string
	Bus           *ninja.DeviceBus
	OnOffBus      *ninja.ChannelBus
	colorBus      *ninja.ChannelBus
	brightnessBus *ninja.ChannelBus
	Batch         bool
	batchBus      *ninja.ChannelBus
	LightState    *LightState
	lightClient   *client.Light
}

// https://github.com/LIFX/lifx-gem/blob/master/lib/lifx/protocol/light.rb
type LightState struct {
	On             *bool
	Brightness     *uint16
	Hue            *uint16
	Saturation     *uint16
	TransitionTime *uint16 //?
	XY             []float64
	ColorTemp      *uint16
}

func (l Light) GetJsonLightState() *simplejson.Json {
	st := l.LightState
	js := simplejson.New()
	js.Set("on", st.On)
	js.Set("bri", st.Brightness)
	js.Set("sat", st.Saturation)
	js.Set("hue", st.Hue)
	js.Set("ct", st.ColorTemp)
	js.Set("transitionTime", st.TransitionTime)
	js.Set("xy", st.XY)
	return js
}

func getOnOffBus(light *Light) *ninja.ChannelBus {
	methods := []string{"turnOn", "turnOff", "set"}
	events := []string{"state"}
	onOffBus, _ := light.Bus.AnnounceChannel("on-off", "on-off", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "turnOn":
			light.turnOnOff(true)
		case "turnOff":
			light.refreshLightState()
			light.turnOnOff(false)
		case "set":
			state, _ := payload.GetIndex(0).Bool()
			light.turnOnOff(state)
		default:
			log.Criticalf("On-off got an unknown method %s", method)
			return
		}
	})

	return onOffBus
}

func getBrightBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	brightnessBus, _ := light.Bus.AnnounceChannel("brightness", "brightness", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}

		switch method {
		case "set":
			brightness, _ := payload.GetIndex(0).Float64()
			light.setBrightness(brightness)
		default:
			log.Criticalf("Brightness got an unknown method %s", method)
			return
		}

	})

	return brightnessBus

}

func getColorBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	colorBus, _ := light.Bus.AnnounceChannel("color", "color", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "set":
			mode, err := payload.Get("mode").String()
			if err != nil {
				log.Criticalf("No mode sent to color bus: %s", err)
			}
			light.setColor(payload, mode)
		default:
			log.Criticalf("Color got an unknown method %s", method)
		}
	})

	return colorBus
}

func NewLight(bus *ninja.DriverBus, client *client.Light) (*Light, error) { //TODO cut this down!

	lightID := "1"
	lightState := createLightState()

	light := &Light{ //TODO fix when lib gets updated
		Id:         lightID,
		Name:       "LiFX Bulb",
		LightState: &lightState,
		Batch:      false,
		lightClient:			client,
	}

	sigs, _ := simplejson.NewJson([]byte(`{
      "ninja:manufacturer": "Lifx",
      "ninja:productName": "Lifx",
      "manufacturer:productModelId": "Lifx",
      "ninja:productType": "Light",
      "ninja:thingType": "light"
  }`))

	deviceBus, _ := bus.AnnounceDevice(lightID, "light", "LiFX Bulb", sigs) //TODO fix when lib gets updated
	light.Bus = deviceBus
	light.OnOffBus = getOnOffBus(light)
	light.brightnessBus = getBrightBus(light)
	light.colorBus = getColorBus(light)
	light.batchBus = getBatchBus(light)

	return light, nil
}

func (l Light) StartBatch() {
	l.Batch = true
}

func (l Light) EndBatch() {
	l.Batch = false
	// l.User.SetLightState(l.Id, l.LightState) //TODO send actual state
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) turnOnOff(state bool) {
	if(state == true) {
		l.lightClient.TurnOn()
	} else {
		l.lightClient.TurnOff()
	}

}

func (l Light) setBrightness(fbrightness float64) {

}

func (l Light) setColor(payload *simplejson.Json, mode string) {
	l.refreshLightState()
	switch mode {
	case "hue": //TODO less verbose plz
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		fhue, _ := payload.Get("hue").Float64()
		hue := uint16(fhue * math.MaxUint16)
		fsaturation, _ := payload.Get("saturation").Float64()
		saturation := uint16(fsaturation * math.MaxUint16)
		on := bool(true)
		l.LightState.Hue = &hue
		l.LightState.Saturation = &saturation
		l.LightState.On = &on
	case "xy":
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		x, _ := payload.Get("x").Float64()
		y, _ := payload.Get("y").Float64()
		xy := []float64{x, y}
		l.LightState.XY = xy
		l.LightState.Hue = nil
		l.LightState.Saturation = nil
		l.LightState.ColorTemp = nil
	case "temperature":
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		temp, _ := payload.Get("temperature").Float64()
		utemp := uint16(math.Floor(1000000 / temp))
		l.LightState.ColorTemp = &utemp
		l.LightState.XY = nil
		l.LightState.Hue = nil
		l.LightState.Saturation = nil
	default:
		log.Criticalf("Bad color mode: %s", mode)
		return
	}

	if !l.Batch {
		// l.User.SetLightState(l.Id, l.LightState) //TODO
		l.colorBus.SendEvent("state", l.GetJsonLightState())
	}

}

func (l Light) setTransition(transTime int) {
	transTime = transTime / 1000 //LIFX transition time in seconds
	utranstime := uint16(transTime)
	l.LightState.TransitionTime = &utranstime
}

func (l Light) setBatchColor(payload *simplejson.Json) {
	l.StartBatch()

	color := payload.Get("color")
	if color != nil {
		l.setColor(color, "hue")
	}

	if brightness, err := payload.Get("brightness").Float64(); err == nil {
		l.setBrightness(brightness)
	}

	if onoff, err := payload.Get("on-off").Bool(); err == nil {
		l.turnOnOff(onoff)
	}

	if transition, err := payload.Get("transition").Int(); err == nil {
		l.setTransition(transition)
	}

	l.EndBatch()
}

func createLightState() LightState {

	on := bool(false)
	brightness := uint16(0)
	saturation := uint16(0)
	hueVal := uint16(0)
	transitionTime := uint16(0)

	lightState := LightState{
		On:             &on,
		Brightness:     &brightness,
		Saturation:     &saturation,
		Hue:            &hueVal,
		TransitionTime: &transitionTime,
	}

	return lightState
}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func (l Light) sendLightState() {

	// l.User.SetLightState(l.Id, l.LightState)
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) refreshLightState() { //TODO

}

func blinkAllLights() {
	c := client.New()
	c.Discover()

	for i:=0; i<3; i++ {
		for _, l := range c.Lights {
			l.TurnOff()
		}
		time.Sleep(1 * time.Second)
		for _, l := range c.Lights {
			l.TurnOn()
		}
		time.Sleep(1 * time.Second)
	}
}

func getBatchBus(light *Light) *ninja.ChannelBus {
	methods := []string{"setBatch"}
	events := []string{"state"}
	batchBus, _ := light.Bus.AnnounceChannel("core.batching", "core.batching", methods, events, func(method string, payload *simplejson.Json) {
		switch method {
		case "setBatch":
			light.setBatchColor(payload.GetIndex(0))
		default:
			log.Criticalf("Color got an unknown method %s", method)
			return
		}
	})

	return batchBus
}

func run() int {
	log.Infof("Starting " + drivername)

	conn, err := ninja.Connect("com.ninjablocks.lifx")
	bus, err := conn.AnnounceDriver("com.ninjablocks.hue", drivername, getCurDir())
	if err != nil {
		log.HandleError(err, "Could not get driver bus")
	}

	lightClient := client.New()
	lightClient.Discover()

	for _,l := range lightClient.Lights {
		_, err := NewLight(bus,&l)
		if err != nil {
			log.Criticalf("Error creating light instance",err)
		}

	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

	return 0
}
<|endoftext|>


<commit_before>package cli

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
)

/*
Cmd represents a command (or sub command) in a CLI application. It should be constructed
by calling Command() on an app to create a top level command or by calling Command() on another
command to create a sub command
*/
type Cmd struct {
	// The code to execute when this command is matched
	Action func()
	// The command options and arguments
	Spec string
	// The command error handling strategy
	ErrorHandling flag.ErrorHandling

	init CmdInitializer
	name string
	desc string

	commands   []*Cmd
	options    []*opt
	optionsIdx map[string]*opt
	args       []*arg
	argsIdx    map[string]*arg

	parents []string

	fsm *state
}

/*
BoolParam represents a Bool option or argument
*/
type BoolParam interface{}

/*
StringParam represents a String option or argument
*/
type StringParam interface{}

/*
IntParam represents an Int option or argument
*/
type IntParam interface{}

/*
StringsParam represents a string slice option or argument
*/
type StringsParam interface{}

/*
IntsParam represents an int slice option or argument
*/
type IntsParam interface{}

/*
CmdInitializer is a function that configures a command by adding options, arguments, a spec, sub commands and the code
to execute when the command is called
*/
type CmdInitializer func(*Cmd)

/*
Command adds a new (sub) command to c where name is the command name (what you type in the console),
description is what would be shown in the help messages, e.g.:

	Usage: git [OPTIONS] COMMAND [arg...]

	Commands:
	  $name	$desc

the last argument, init, is a function that will be called by mow.cli to further configure the created
(sub) command, e.g. to add options, arguments and the code to execute
*/
func (c *Cmd) Command(name, desc string, init CmdInitializer) {
	c.commands = append(c.commands, &Cmd{
		ErrorHandling: c.ErrorHandling,
		name:          name,
		desc:          desc,
		init:          init,
		commands:      []*Cmd{},
		options:       []*opt{},
		optionsIdx:    map[string]*opt{},
		args:          []*arg{},
		argsIdx:       map[string]*arg{},
	})
}

/*
Bool can be used to add a bool option or argument to a command.
It accepts either a BoolOpt or a BoolArg struct.

The result should be stored in a variable (a pointer to a bool) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Bool(p BoolParam) *bool {
	switch x := p.(type) {
	case BoolOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*bool)
	case BoolArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*bool)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
String can be used to add a string option or argument to a command.
It accepts either a StringOpt or a StringArg struct.

The result should be stored in a variable (a pointer to a string) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) String(p StringParam) *string {
	switch x := p.(type) {
	case StringOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*string)
	case StringArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*string)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
Int can be used to add an int option or argument to a command.
It accepts either a IntOpt or a IntArg struct.

The result should be stored in a variable (a pointer to an int) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Int(p IntParam) *int {
	switch x := p.(type) {
	case IntOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*int)
	case IntArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*int)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
Strings can be used to add a string slice option or argument to a command.
It accepts either a StringsOpt or a StringsArg struct.

The result should be stored in a variable (a pointer to a string slice) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Strings(p StringsParam) *[]string {
	switch x := p.(type) {
	case StringsOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]string)
	case StringsArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]string)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
Ints can be used to add an int slice option or argument to a command.
It accepts either a IntsOpt or a IntsArg struct.

The result should be stored in a variable (a pointer to an int slice) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Ints(p IntsParam) *[]int {
	switch x := p.(type) {
	case IntsOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]int)
	case IntsArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]int)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

func (c *Cmd) doInit() error {
	if c.init != nil {
		c.init(c)
	}

	parents := append(c.parents, c.name)

	for _, sub := range c.commands {
		sub.parents = parents
	}

	if len(c.Spec) == 0 {
		if len(c.options) > 0 {
			c.Spec = "[OPTIONS] "
		}
		for _, arg := range c.args {
			c.Spec += arg.name + " "
		}
	}
	fsm, err := uParse(c)
	if err != nil {
		return err
	}
	c.fsm = fsm
	return nil
}

func (c *Cmd) onError(err error) {
	if err != nil {
		switch c.ErrorHandling {
		case flag.ExitOnError:
			os.Exit(2)
		case flag.PanicOnError:
			panic(err)
		}
	} else {
		if c.ErrorHandling == flag.ExitOnError {
			os.Exit(2)
		}
	}
}

/*
PrintHelp prints the command's help message.
In most cases the library users won't need to call this method, unless
a more complex validation is needed
*/
func (c *Cmd) PrintHelp() {
	out := os.Stderr

	full := append(c.parents, c.name)
	sub := ""
	if len(c.commands) > 0 {
		sub = "COMMAND [arg...]"
	}
	path := strings.Join(full, " ")
	fmt.Fprintf(out, "\nUsage: %s %s %s\n\n", path, c.Spec, sub)
	if len(c.desc) > 0 {
		fmt.Fprintf(out, "%s\n", c.desc)
	}

	w := tabwriter.NewWriter(out, 15, 1, 3, ' ', 0)

	if len(c.args) > 0 {
		fmt.Fprintf(out, "\nArguments:\n")

		for _, arg := range c.args {
			fmt.Fprintf(w, "  %s=%#v\t%s\n", arg.name, arg.get(), arg.desc)
		}
		w.Flush()
	}

	if len(c.options) > 0 {
		fmt.Fprintf(out, "\nOptions:\n")

		for _, opt := range c.options {
			fmt.Fprintf(w, "  %s=%#v\t%s\n", strings.Join(opt.names, ", "), opt.get(), opt.desc)
		}
		w.Flush()
	}

	if len(c.commands) > 0 {
		fmt.Fprintf(out, "\nCommands:\n")

		for _, c := range c.commands {
			fmt.Fprintf(w, "  %s\t%s\n", c.name, c.desc)
		}
		w.Flush()
	}

	if len(c.commands) > 0 {
		fmt.Fprintf(out, "\nRun '%s COMMAND --help' for more information on a command.\n", path)
	}
}

func (c *Cmd) parse(args []string) error {
	if c.helpRequested(args) {
		c.PrintHelp()
		c.onError(nil)
		return nil
	}
	nargs, nargsLen, err := c.normalize(args)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
		c.PrintHelp()
		c.onError(err)
		return err
	}

	if err := c.fsm.parse(nargs); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
		c.PrintHelp()
		c.onError(err)
		return err
	}

	args = args[nargsLen:]
	if len(args) == 0 {
		if c.Action != nil {
			c.Action()
			return nil
		}
		c.PrintHelp()
		c.onError(nil)
		return nil
	}

	arg := args[0]
	for _, sub := range c.commands {
		if arg == sub.name {
			if err := sub.doInit(); err != nil {
				panic(err)
			}
			return sub.parse(args[1:])
		}
	}

	switch {
	case strings.HasPrefix(arg, "-"):
		fmt.Fprintf(os.Stderr, "Error: illegal option %s\n", arg)
	default:
		fmt.Fprintf(os.Stderr, "Error: illegal input %s\n", arg)
	}
	c.PrintHelp()
	c.onError(err)
	return err

}

func (c *Cmd) helpRequested(args []string) bool {
	for _, arg := range args {
		for _, sub := range c.commands {
			if arg == sub.name {
				return false
			}
		}
		if arg == "-h" || arg == "--help" {
			return true
		}
	}
	return false
}

func (c *Cmd) normalize(args []string) (res []string, consumed int, err error) {
	err = nil
	res = []string{}
	consumed = 0
	for _, arg := range args {
		for _, sub := range c.commands {
			if arg == sub.name {
				return
			}
		}
		switch {
		case strings.HasPrefix(arg, "--"):
			var parts []string
			parts, err = c.normalizeLongOpt(arg)
			if err != nil {
				return
			}
			res = append(res, parts...)

		case strings.HasPrefix(arg, "-"):
			var parts []string
			parts, err = c.normalizeShortOpt(arg)
			if err != nil {
				return
			}
			res = append(res, parts...)
		default:
			res = append(res, arg)
		}
		consumed++
	}
	return
}

func (c *Cmd) normalizeLongOpt(arg string) ([]string, error) {
	res := []string{}
	kv := strings.Split(arg, "=")
	name := arg
	if len(kv) == 2 {
		name = kv[0]
		res = append(res, name, kv[1])
	} else {
		res = append(res, name)
	}
	if _, declared := c.optionsIdx[name]; !declared {
		return nil, fmt.Errorf("Illegal option %s", name)
	}
	return res, nil
}

func (c *Cmd) normalizeShortOpt(arg string) ([]string, error) {
	res := []string{}
	if len(arg) > 1 {
		name := arg[0:2]
		opt, declared := c.optionsIdx[name]
		if !declared {
			return nil, fmt.Errorf("Illegal option %s", name)
		}
		switch {
		case opt.isBool():
			res = append(res, name)

			parts, err := c.normalizeShortOpt("-" + arg[2:])
			if err != nil {
				return nil, err
			}
			res = append(res, parts...)
		default:
			res = append(res, name)
			if len(arg) > 2 {
				res = append(res, arg[2:])
			}
		}
	}
	return res, nil
}
<commit_msg>Handle -f=X syntax for short options<commit_after>package cli

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
)

/*
Cmd represents a command (or sub command) in a CLI application. It should be constructed
by calling Command() on an app to create a top level command or by calling Command() on another
command to create a sub command
*/
type Cmd struct {
	// The code to execute when this command is matched
	Action func()
	// The command options and arguments
	Spec string
	// The command error handling strategy
	ErrorHandling flag.ErrorHandling

	init CmdInitializer
	name string
	desc string

	commands   []*Cmd
	options    []*opt
	optionsIdx map[string]*opt
	args       []*arg
	argsIdx    map[string]*arg

	parents []string

	fsm *state
}

/*
BoolParam represents a Bool option or argument
*/
type BoolParam interface{}

/*
StringParam represents a String option or argument
*/
type StringParam interface{}

/*
IntParam represents an Int option or argument
*/
type IntParam interface{}

/*
StringsParam represents a string slice option or argument
*/
type StringsParam interface{}

/*
IntsParam represents an int slice option or argument
*/
type IntsParam interface{}

/*
CmdInitializer is a function that configures a command by adding options, arguments, a spec, sub commands and the code
to execute when the command is called
*/
type CmdInitializer func(*Cmd)

/*
Command adds a new (sub) command to c where name is the command name (what you type in the console),
description is what would be shown in the help messages, e.g.:

	Usage: git [OPTIONS] COMMAND [arg...]

	Commands:
	  $name	$desc

the last argument, init, is a function that will be called by mow.cli to further configure the created
(sub) command, e.g. to add options, arguments and the code to execute
*/
func (c *Cmd) Command(name, desc string, init CmdInitializer) {
	c.commands = append(c.commands, &Cmd{
		ErrorHandling: c.ErrorHandling,
		name:          name,
		desc:          desc,
		init:          init,
		commands:      []*Cmd{},
		options:       []*opt{},
		optionsIdx:    map[string]*opt{},
		args:          []*arg{},
		argsIdx:       map[string]*arg{},
	})
}

/*
Bool can be used to add a bool option or argument to a command.
It accepts either a BoolOpt or a BoolArg struct.

The result should be stored in a variable (a pointer to a bool) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Bool(p BoolParam) *bool {
	switch x := p.(type) {
	case BoolOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*bool)
	case BoolArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*bool)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
String can be used to add a string option or argument to a command.
It accepts either a StringOpt or a StringArg struct.

The result should be stored in a variable (a pointer to a string) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) String(p StringParam) *string {
	switch x := p.(type) {
	case StringOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*string)
	case StringArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*string)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
Int can be used to add an int option or argument to a command.
It accepts either a IntOpt or a IntArg struct.

The result should be stored in a variable (a pointer to an int) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Int(p IntParam) *int {
	switch x := p.(type) {
	case IntOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*int)
	case IntArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*int)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
Strings can be used to add a string slice option or argument to a command.
It accepts either a StringsOpt or a StringsArg struct.

The result should be stored in a variable (a pointer to a string slice) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Strings(p StringsParam) *[]string {
	switch x := p.(type) {
	case StringsOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]string)
	case StringsArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]string)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

/*
Ints can be used to add an int slice option or argument to a command.
It accepts either a IntsOpt or a IntsArg struct.

The result should be stored in a variable (a pointer to an int slice) which will be populated when the app is run and the call arguments get parsed
*/
func (c *Cmd) Ints(p IntsParam) *[]int {
	switch x := p.(type) {
	case IntsOpt:
		return c.mkOpt(opt{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]int)
	case IntsArg:
		return c.mkArg(arg{name: x.Name, desc: x.Desc, envVar: x.EnvVar}, x.Value).(*[]int)
	default:
		panic(fmt.Sprintf("Unhandled param %v", p))
	}
}

func (c *Cmd) doInit() error {
	if c.init != nil {
		c.init(c)
	}

	parents := append(c.parents, c.name)

	for _, sub := range c.commands {
		sub.parents = parents
	}

	if len(c.Spec) == 0 {
		if len(c.options) > 0 {
			c.Spec = "[OPTIONS] "
		}
		for _, arg := range c.args {
			c.Spec += arg.name + " "
		}
	}
	fsm, err := uParse(c)
	if err != nil {
		return err
	}
	c.fsm = fsm
	return nil
}

func (c *Cmd) onError(err error) {
	if err != nil {
		switch c.ErrorHandling {
		case flag.ExitOnError:
			os.Exit(2)
		case flag.PanicOnError:
			panic(err)
		}
	} else {
		if c.ErrorHandling == flag.ExitOnError {
			os.Exit(2)
		}
	}
}

/*
PrintHelp prints the command's help message.
In most cases the library users won't need to call this method, unless
a more complex validation is needed
*/
func (c *Cmd) PrintHelp() {
	out := os.Stderr

	full := append(c.parents, c.name)
	sub := ""
	if len(c.commands) > 0 {
		sub = "COMMAND [arg...]"
	}
	path := strings.Join(full, " ")
	fmt.Fprintf(out, "\nUsage: %s %s %s\n\n", path, c.Spec, sub)
	if len(c.desc) > 0 {
		fmt.Fprintf(out, "%s\n", c.desc)
	}

	w := tabwriter.NewWriter(out, 15, 1, 3, ' ', 0)

	if len(c.args) > 0 {
		fmt.Fprintf(out, "\nArguments:\n")

		for _, arg := range c.args {
			fmt.Fprintf(w, "  %s=%#v\t%s\n", arg.name, arg.get(), arg.desc)
		}
		w.Flush()
	}

	if len(c.options) > 0 {
		fmt.Fprintf(out, "\nOptions:\n")

		for _, opt := range c.options {
			fmt.Fprintf(w, "  %s=%#v\t%s\n", strings.Join(opt.names, ", "), opt.get(), opt.desc)
		}
		w.Flush()
	}

	if len(c.commands) > 0 {
		fmt.Fprintf(out, "\nCommands:\n")

		for _, c := range c.commands {
			fmt.Fprintf(w, "  %s\t%s\n", c.name, c.desc)
		}
		w.Flush()
	}

	if len(c.commands) > 0 {
		fmt.Fprintf(out, "\nRun '%s COMMAND --help' for more information on a command.\n", path)
	}
}

func (c *Cmd) parse(args []string) error {
	if c.helpRequested(args) {
		c.PrintHelp()
		c.onError(nil)
		return nil
	}
	nargs, nargsLen, err := c.normalize(args)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
		c.PrintHelp()
		c.onError(err)
		return err
	}

	if err := c.fsm.parse(nargs); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
		c.PrintHelp()
		c.onError(err)
		return err
	}

	args = args[nargsLen:]
	if len(args) == 0 {
		if c.Action != nil {
			c.Action()
			return nil
		}
		c.PrintHelp()
		c.onError(nil)
		return nil
	}

	arg := args[0]
	for _, sub := range c.commands {
		if arg == sub.name {
			if err := sub.doInit(); err != nil {
				panic(err)
			}
			return sub.parse(args[1:])
		}
	}

	switch {
	case strings.HasPrefix(arg, "-"):
		fmt.Fprintf(os.Stderr, "Error: illegal option %s\n", arg)
	default:
		fmt.Fprintf(os.Stderr, "Error: illegal input %s\n", arg)
	}
	c.PrintHelp()
	c.onError(err)
	return err

}

func (c *Cmd) helpRequested(args []string) bool {
	for _, arg := range args {
		for _, sub := range c.commands {
			if arg == sub.name {
				return false
			}
		}
		if arg == "-h" || arg == "--help" {
			return true
		}
	}
	return false
}

func (c *Cmd) normalize(args []string) (res []string, consumed int, err error) {
	err = nil
	res = []string{}
	consumed = 0
	for _, arg := range args {
		for _, sub := range c.commands {
			if arg == sub.name {
				return
			}
		}
		switch {
		case strings.HasPrefix(arg, "--"):
			var parts []string
			parts, err = c.normalizeLongOpt(arg)
			if err != nil {
				return
			}
			res = append(res, parts...)

		case strings.HasPrefix(arg, "-"):
			var parts []string
			parts, err = c.normalizeShortOpt(arg)
			if err != nil {
				return
			}
			res = append(res, parts...)
		default:
			res = append(res, arg)
		}
		consumed++
	}
	return
}

func (c *Cmd) normalizeLongOpt(arg string) ([]string, error) {
	res := []string{}
	kv := strings.Split(arg, "=")
	name := arg
	if len(kv) == 2 {
		name = kv[0]
		res = append(res, name, kv[1])
	} else {
		res = append(res, name)
	}
	if _, declared := c.optionsIdx[name]; !declared {
		return nil, fmt.Errorf("Illegal option %s", name)
	}
	return res, nil
}

func (c *Cmd) normalizeShortOpt(arg string) ([]string, error) {
	res := []string{}
	if len(arg) > 1 {
		name := arg[0:2]
		opt, declared := c.optionsIdx[name]
		if !declared {
			return nil, fmt.Errorf("Illegal option %s", name)
		}
		switch {
		case opt.isBool():
			res = append(res, name)

			parts, err := c.normalizeShortOpt("-" + arg[2:])
			if err != nil {
				return nil, err
			}
			res = append(res, parts...)
		default:
			res = append(res, name)
			if len(arg) > 2 {
				val := arg[2:]
				if strings.HasPrefix(val, "=") {
					val = arg[3:]
				}
				res = append(res, val)
			}
		}
	}
	return res, nil
}
<|endoftext|>


<commit_before>// Package httpfake provides a simple wrapper for httptest
// with a handful chainable API for setting up handlers to a fake server.
// This package is aimed to be used in tests where the original external server
// must not be reached. Instead is used in its place a fake server
// which can be configured to handle any request as desired.
package httpfake

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	netURL "net/url"
	"strings"
)

// HTTPFake is the root struct for the fake server
type HTTPFake struct {
	Server          *httptest.Server
	RequestHandlers []*Request
}

// New starts a httptest.Server as the fake server
// and sets up the initial configuration to this server's request handlers
func New() *HTTPFake {
	fake := &HTTPFake{
		RequestHandlers: []*Request{},
	}

	fake.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rh := fake.findHandler(r)
		if rh == nil {
			errMsg := fmt.Sprintf(
				"not found request handler for [%s: %s]; registered handlers are:\n",
				r.Method, r.URL,
			)
			for _, frh := range fake.RequestHandlers {
				errMsg += fmt.Sprintf("* [%s: %s]\n", frh.Method, frh.URL.Path)
			}
			printError(errMsg)
			w.WriteHeader(http.StatusNotFound)
			return
		}
		if rh.CustomHandle != nil {
			rh.CustomHandle(w, r, rh)
			return
		}
		Respond(w, r, rh)
	}))

	return fake
}

// NewHandler initializes the configuration for a new request handler
func (f *HTTPFake) NewHandler() *Request {
	rh := NewRequest()
	f.RequestHandlers = append(f.RequestHandlers, rh)
	return rh
}

// ResolveURL resolves the full URL to the fake server for a given path
func (f *HTTPFake) ResolveURL(path string, args ...interface{}) string {
	format := f.Server.URL + path
	return fmt.Sprintf(format, args...)
}

// Reset wipes the request handlers definitions
func (f *HTTPFake) Reset() *HTTPFake {
	f.RequestHandlers = []*Request{}
	return f
}

func (f *HTTPFake) findHandler(r *http.Request) *Request {
	founds := []*Request{}
	url := r.URL.String()
	path := getURLPath(url)
	for _, rh := range f.RequestHandlers {
		if rh.Method != r.Method {
			continue
		}

		rhURL, _ := netURL.QueryUnescape(rh.URL.String())
		if rhURL == url {
			return rh
		}

		// fallback if the income request has query strings
		// and there is handlers only for the path
		if getURLPath(rhURL) == path {
			founds = append(founds, rh)
		}
	}
	// only use the fallback if could find only one match
	if len(founds) == 1 {
		return founds[0]
	}
	return nil
}

func getURLPath(url string) string {
	return strings.Split(url, "?")[0]
}

func printError(msg string) {
	fmt.Println("\033[0;31mhttpfake: " + msg + "\033[0m")
}
<commit_msg>Fix linter error<commit_after>// Package httpfake provides a simple wrapper for httptest
// with a handful chainable API for setting up handlers to a fake server.
// This package is aimed to be used in tests where the original external server
// must not be reached. Instead is used in its place a fake server
// which can be configured to handle any request as desired.
package httpfake

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	netURL "net/url"
	"strings"
)

// HTTPFake is the root struct for the fake server
type HTTPFake struct {
	Server          *httptest.Server
	RequestHandlers []*Request
}

// New starts a httptest.Server as the fake server
// and sets up the initial configuration to this server's request handlers
func New() *HTTPFake {
	fake := &HTTPFake{
		RequestHandlers: []*Request{},
	}

	fake.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rh, err := fake.findHandler(r)
		if err != nil {
			printError(fmt.Sprintf("error finding handler: %v", err))
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		if rh == nil {
			errMsg := fmt.Sprintf(
				"not found request handler for [%s: %s]; registered handlers are:\n",
				r.Method, r.URL,
			)
			for _, frh := range fake.RequestHandlers {
				errMsg += fmt.Sprintf("* [%s: %s]\n", frh.Method, frh.URL.Path)
			}
			printError(errMsg)
			w.WriteHeader(http.StatusNotFound)
			return
		}

		if rh.CustomHandle != nil {
			rh.CustomHandle(w, r, rh)
			return
		}

		Respond(w, r, rh)
	}))

	return fake
}

// NewHandler initializes the configuration for a new request handler
func (f *HTTPFake) NewHandler() *Request {
	rh := NewRequest()
	f.RequestHandlers = append(f.RequestHandlers, rh)
	return rh
}

// ResolveURL resolves the full URL to the fake server for a given path
func (f *HTTPFake) ResolveURL(path string, args ...interface{}) string {
	format := f.Server.URL + path
	return fmt.Sprintf(format, args...)
}

// Reset wipes the request handlers definitions
func (f *HTTPFake) Reset() *HTTPFake {
	f.RequestHandlers = []*Request{}
	return f
}

func (f *HTTPFake) findHandler(r *http.Request) (*Request, error) {
	founds := []*Request{}
	url := r.URL.String()
	path := getURLPath(url)
	for _, rh := range f.RequestHandlers {
		if rh.Method != r.Method {
			continue
		}

		rhURL, err := netURL.QueryUnescape(rh.URL.String())
		if err != nil {
			return nil, err
		}

		if rhURL == url {
			return rh, nil
		}

		// fallback if the income request has query strings
		// and there is handlers only for the path
		if getURLPath(rhURL) == path {
			founds = append(founds, rh)
		}
	}
	// only use the fallback if could find only one match
	if len(founds) == 1 {
		return founds[0], nil
	}

	return nil, nil
}

func getURLPath(url string) string {
	return strings.Split(url, "?")[0]
}

func printError(msg string) {
	fmt.Println("\033[0;31mhttpfake: " + msg + "\033[0m")
}
<|endoftext|>


<commit_before>package models

/*
TODO

import (
  r "github.com/dancannon/gorethink"
	"time"
)

type Application struct {
	Id        string `gorethink:"id,omitempty"`
	CreatedAt time.Time
}

func NewApplication() *Application {
	app := new(Application)
	app.CreatedAt = time.Now()
}
*/
<commit_msg>Delete old application.go<commit_after><|endoftext|>