<commit_before>package cli

import (
	"fmt"
	"io"

	"github.com/codegangsta/cli"
	"github.com/minodisk/qiitactl/api"
	"github.com/minodisk/qiitactl/command"
	"github.com/minodisk/qiitactl/info"
)

func GenerateApp(client api.Client, outWriter io.Writer, errWriter io.Writer) (app *cli.App) {
	app = cli.NewApp()
	app.Writer = outWriter
	app.Name = info.Name
	app.Version = info.Version
	app.Author = info.Author
	app.Usage = "Controls the Qiita posts"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "Panic when error occurs",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:  "generate",
			Usage: "Generate something in your local",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "file",
					Usage:  "Generate a new markdown file for a new post",
					Action: partialize(command.GenerateFile, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "title, t",
							Usage: "The title of a new post",
						},
						cli.StringFlag{
							Name:  "team, T",
							Usage: "The name of a team, when you post to the team",
						},
					},
				},
			},
		},
		{
			Name:  "create",
			Usage: "Create resources from current working directory to Qiita",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Create a post",
					Action: partialize(command.CreatePost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be created",
						},
						cli.BoolFlag{
							Name:  "tweet, t",
							Usage: "Tweet the created post in Twitter",
						},
						cli.BoolFlag{
							Name:  "gist, g",
							Usage: "Post codes in the created post to GitHub Gist",
						},
					},
				},
			},
		},
		{
			Name:  "show",
			Usage: "Display resources",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Print detail of a post in Qitta",
					Action: partializeWithWriter(command.ShowPost, client, outWriter, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "id, i",
							Usage: "The ID of the post to be printed detail",
						},
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be created",
						},
					},
				},
				{
					Name:   "posts",
					Usage:  "Print a list of posts in Qiita",
					Action: partializeWithWriter(command.ShowPosts, client, outWriter, errWriter),
					Flags:  []cli.Flag{},
				},
			},
		},
		{
			Name:  "fetch",
			Usage: "Download resources from Qiita to current working directory",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Download a post as a file",
					Action: partialize(command.FetchPost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "id, i",
							Usage: "The ID of the post to be downloaded",
						},
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be created",
						},
					},
				},
				{
					Name:   "posts",
					Usage:  "Download posts as files",
					Action: partialize(command.FetchPosts, client, errWriter),
					Flags:  []cli.Flag{},
				},
			},
		},
		{
			Name:  "update",
			Usage: "Update resources from current working directory to Qiita",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Update a post",
					Action: partialize(command.UpdatePost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be updated",
						},
					},
				},
			},
		},
		{
			Name:  "delete",
			Usage: "Delete resources from Qiita",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Delete a post",
					Action: partialize(command.DeletePost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be updated",
						},
					},
				},
			},
		},
	}
	app.CommandNotFound = func(c *cli.Context, command string) {
		fmt.Fprintf(errWriter, "%s: '%s' is not a %s command. See '%s --help'.", c.App.Name, command, c.App.Name, c.App.Name)
		// os.Exit(2)
	}
	return
}

func partialize(cmdFunc func(*cli.Context, api.Client) error, client api.Client, errWriter io.Writer) func(ctx *cli.Context) {
	return func(ctx *cli.Context) {
		client.SetDebug(ctx.GlobalBool("debug"))
		err := cmdFunc(ctx, client)
		if err != nil {
			printError(ctx, errWriter, err)
		}
	}
}

func partializeWithWriter(cmdFunc func(*cli.Context, api.Client, io.Writer) error, client api.Client, outWriter io.Writer, errWriter io.Writer) func(ctx *cli.Context) {
	return func(ctx *cli.Context) {
		client.SetDebug(ctx.GlobalBool("debug"))
		err := cmdFunc(ctx, client, outWriter)
		if err != nil {
			printError(ctx, errWriter, err)
		}
	}
}

func printError(ctx *cli.Context, w io.Writer, err error) {
	// if ctx.GlobalBool("debug") {
	// 	panic(err)
	// } else {
	fmt.Fprint(w, err)
	// }
}
<commit_msg>Add comments to cli<commit_after>package cli

import (
	"fmt"
	"io"

	"github.com/codegangsta/cli"
	"github.com/minodisk/qiitactl/api"
	"github.com/minodisk/qiitactl/command"
	"github.com/minodisk/qiitactl/info"
)

// GenerateApp generates an application of command line interface.
func GenerateApp(client api.Client, outWriter io.Writer, errWriter io.Writer) (app *cli.App) {
	app = cli.NewApp()
	app.Writer = outWriter
	app.Name = info.Name
	app.Version = info.Version
	app.Author = info.Author
	app.Usage = "Controls the Qiita posts"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "Panic when error occurs",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:  "generate",
			Usage: "Generate something in your local",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "file",
					Usage:  "Generate a new markdown file for a new post",
					Action: partialize(command.GenerateFile, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "title, t",
							Usage: "The title of a new post",
						},
						cli.StringFlag{
							Name:  "team, T",
							Usage: "The name of a team, when you post to the team",
						},
					},
				},
			},
		},
		{
			Name:  "create",
			Usage: "Create resources from current working directory to Qiita",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Create a post",
					Action: partialize(command.CreatePost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be created",
						},
						cli.BoolFlag{
							Name:  "tweet, t",
							Usage: "Tweet the created post in Twitter",
						},
						cli.BoolFlag{
							Name:  "gist, g",
							Usage: "Post codes in the created post to GitHub Gist",
						},
					},
				},
			},
		},
		{
			Name:  "show",
			Usage: "Display resources",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Print detail of a post in Qitta",
					Action: partializeWithWriter(command.ShowPost, client, outWriter, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "id, i",
							Usage: "The ID of the post to be printed detail",
						},
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be created",
						},
					},
				},
				{
					Name:   "posts",
					Usage:  "Print a list of posts in Qiita",
					Action: partializeWithWriter(command.ShowPosts, client, outWriter, errWriter),
					Flags:  []cli.Flag{},
				},
			},
		},
		{
			Name:  "fetch",
			Usage: "Download resources from Qiita to current working directory",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Download a post as a file",
					Action: partialize(command.FetchPost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "id, i",
							Usage: "The ID of the post to be downloaded",
						},
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be created",
						},
					},
				},
				{
					Name:   "posts",
					Usage:  "Download posts as files",
					Action: partialize(command.FetchPosts, client, errWriter),
					Flags:  []cli.Flag{},
				},
			},
		},
		{
			Name:  "update",
			Usage: "Update resources from current working directory to Qiita",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Update a post",
					Action: partialize(command.UpdatePost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be updated",
						},
					},
				},
			},
		},
		{
			Name:  "delete",
			Usage: "Delete resources from Qiita",
			Flags: []cli.Flag{},
			Subcommands: []cli.Command{
				{
					Name:   "post",
					Usage:  "Delete a post",
					Action: partialize(command.DeletePost, client, errWriter),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "filename, f",
							Usage: "The filename of the post to be updated",
						},
					},
				},
			},
		},
	}
	app.CommandNotFound = func(c *cli.Context, command string) {
		fmt.Fprintf(errWriter, "%s: '%s' is not a %s command. See '%s --help'.", c.App.Name, command, c.App.Name, c.App.Name)
		// os.Exit(2)
	}
	return
}

func partialize(cmdFunc func(*cli.Context, api.Client) error, client api.Client, errWriter io.Writer) func(ctx *cli.Context) {
	return func(ctx *cli.Context) {
		client.SetDebugMode(ctx.GlobalBool("debug"))
		err := cmdFunc(ctx, client)
		if err != nil {
			printError(ctx, errWriter, err)
		}
	}
}

func partializeWithWriter(cmdFunc func(*cli.Context, api.Client, io.Writer) error, client api.Client, outWriter io.Writer, errWriter io.Writer) func(ctx *cli.Context) {
	return func(ctx *cli.Context) {
		client.SetDebugMode(ctx.GlobalBool("debug"))
		err := cmdFunc(ctx, client, outWriter)
		if err != nil {
			printError(ctx, errWriter, err)
		}
	}
}

func printError(ctx *cli.Context, w io.Writer, err error) {
	// if ctx.GlobalBool("debug") {
	// 	panic(err)
	// } else {
	fmt.Fprint(w, err)
	// }
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"io"
	"os"

	"github.com/vektra/cypress/commands"
	"github.com/vektra/cypress/plugin"
)

type CatCommand struct {
	Kv     bool `short:"k" description:"display in key=value format"`
	Human  bool `short:"H" description:"display in easy to read format"`
	Json   bool `short:"j" description:"display in json"`
	Native bool `short:"n" description:"output as native binary"`
}

func (c *CatCommand) Execute(args []string) error {
	var cnt int

	if c.Kv {
		cnt++
	}

	if c.Human {
		cnt++
	}

	if c.Json {
		cnt++
	}

	if c.Native {
		cnt++
	}

	var format commands.Format

	switch {
	case cnt == 0:
		format = commands.KV
	case cnt > 1:
		return fmt.Errorf("multiple display types requested, only use one")
	case c.Kv:
		format = commands.KV
	case c.Human:
		format = commands.HUMAN
	case c.Json:
		format = commands.JSON
	case c.Native:
		format = commands.NATIVE
	}

	dir := args[0]
	if dir == "" {
		return fmt.Errorf("no source specified")
	}

	spool, err := plugin.NewSpool(dir)
	if err != nil {
		return err
	}

	gen, err := spool.Generator()
	if err != nil {
		return err
	}

	cat, err := commands.NewCat(os.Stdout, gen, format)
	if err != nil {
		return err
	}

	err = cat.Run()
	if err != nil {
		if err == io.EOF {
			return nil
		}

		return err
	}

	return nil
}

func init() {
	long := `Given a source of messages, the cat command will read those messages in and print them out. Commonly, users point cat at a spool directory to read the message contained within.`

	addCommand("cat", "display messages", long, &CatCommand{})
}
<commit_msg>Change cat to decode the stream on stdin<commit_after>package cli

import (
	"fmt"
	"io"
	"os"

	"github.com/vektra/cypress"
	"github.com/vektra/cypress/commands"
)

type CatCommand struct {
	Kv     bool `short:"k" description:"display in key=value format"`
	Human  bool `short:"H" description:"display in easy to read format"`
	Json   bool `short:"j" description:"display in json"`
	Native bool `short:"n" description:"output as native binary"`
}

func (c *CatCommand) Execute(args []string) error {
	var cnt int

	if c.Kv {
		cnt++
	}

	if c.Human {
		cnt++
	}

	if c.Json {
		cnt++
	}

	if c.Native {
		cnt++
	}

	var format commands.Format

	switch {
	case cnt == 0:
		format = commands.KV
	case cnt > 1:
		return fmt.Errorf("multiple display types requested, only use one")
	case c.Kv:
		format = commands.KV
	case c.Human:
		format = commands.HUMAN
	case c.Json:
		format = commands.JSON
	case c.Native:
		format = commands.NATIVE
	}

	dec, err := cypress.NewStreamDecoder(os.Stdin)
	if err != nil {
		return err
	}

	cat, err := commands.NewCat(os.Stdout, dec, format)
	if err != nil {
		return err
	}

	err = cat.Run()
	if err != nil {
		if err == io.EOF {
			return nil
		}

		return err
	}

	return nil
}

func init() {
	long := `Given a stream on stdin, the cat command will read those messages in and print them out.`

	addCommand("cat", "display messages", long, &CatCommand{})
}
<|endoftext|>


<commit_before>package cli

import (
    "fmt"
    "os"
    "io"
    "path/filepath"
    "strings"
    "code.google.com/p/google-api-go-client/drive/v2"
    "../util"
    "../gdrive"
)

func List(d *gdrive.Drive, query, titleFilter string, maxResults int, sharedStatus bool) {
    caller := d.Files.List()

    if maxResults > 0 {
        caller.MaxResults(int64(maxResults))
    }

    if titleFilter != "" {
        q := fmt.Sprintf("title contains '%s'", titleFilter)
        caller.Q(q)
    }

    if query != "" {
        caller.Q(query)
    }

    list, err := caller.Do()
    if err != nil {
        fmt.Println(err)
        return
    }

    items := make([]map[string]string, 0, 0)

    for _, f := range list.Items {
        // Skip files that dont have a download url (they are not stored on google drive)
        if f.DownloadUrl == "" {
            continue
        }

        items = append(items, map[string]string{
            "Id": f.Id,
            "Title": util.TruncateString(f.Title, 40),
            "Size": util.FileSizeFormat(f.FileSize),
            "Created": util.ISODateToLocal(f.CreatedDate),
        })
    }

    columnOrder := []string{"Id", "Title", "Size", "Created"}

    if sharedStatus {
        addSharedStatus(d, items)
        columnOrder = append(columnOrder, "Shared")
    }

    util.PrintColumns(items, columnOrder, 3)
}

// Adds the key-value-pair 'Shared: True/False' to the map
func addSharedStatus(d *gdrive.Drive, items []map[string]string) {
    // Limit to 10 simultaneous requests
    active := make(chan bool, 10)
    done := make(chan bool)

    // Closure that performs the check
    checkStatus := func(item map[string]string) {
        // Wait for an empty spot in the active queue
        active <- true

        // Perform request
        shared := isShared(d, item["Id"])
        item["Shared"] = util.FormatBool(shared)

        // Decrement the active queue and notify that we are done
        <-active
        done <- true
    }

    // Go, go, go!
    for _, item := range items {
        go checkStatus(item)
    }

    // Wait for all goroutines to finish
    for i := 0; i < len(items); i++ {
        <-done
    }
}

func Info(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }
    printInfo(d, info)
}

func printInfo(d *gdrive.Drive, f *drive.File) {
    fields := map[string]string{
        "Id": f.Id,
        "Title": f.Title,
        "Description": f.Description,
        "Size": util.FileSizeFormat(f.FileSize),
        "Created": util.ISODateToLocal(f.CreatedDate),
        "Modified": util.ISODateToLocal(f.ModifiedDate),
        "Owner": strings.Join(f.OwnerNames, ", "),
        "Md5sum": f.Md5Checksum,
        "Shared": util.FormatBool(isShared(d, f.Id)),
    }

    order := []string{"Id", "Title", "Description", "Size", "Created", "Modified", "Owner", "Md5sum", "Shared"}
    util.Print(fields, order)
}

// Upload file to drive
func Upload(d *gdrive.Drive, input io.ReadCloser, title string, share bool) {
    // Use filename or 'untitled' as title if no title is specified
    if title == "" {
        if f, ok := input.(*os.File); ok && input != os.Stdin {
            title = filepath.Base(f.Name())
        } else {
            title = "untitled"
        }
    }

    metadata := &drive.File{Title: title}
    getRate := util.MeasureTransferRate()

    info, err := d.Files.Insert(metadata).Media(input).Do()
    if err != nil {
        fmt.Printf("An error occurred uploading the document: %v\n", err)
        return
    }

    // Total bytes transferred
    bytes := info.FileSize

    // Print information about uploaded file
    printInfo(d, info)
    fmt.Printf("Uploaded '%s' at %s, total %s\n", info.Title, getRate(bytes), util.FileSizeFormat(bytes))

    // Share file if the share flag was provided
    if share {
        Share(d, info.Id)
    }
}

func DownloadLatest(d *gdrive.Drive, stdout bool) {
    list, err := d.Files.List().Do()

    if err != nil {
        fmt.Println(err)
        return
    }

    if len(list.Items) == 0 {
        fmt.Println("No files found")
        return
    }

    latestId := list.Items[0].Id
    Download(d, latestId, stdout, true)
}

// Download file from drive
func Download(d *gdrive.Drive, fileId string, stdout, deleteAfterDownload bool) {
    // Get file info
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    if info.DownloadUrl == "" {
        // If there is no DownloadUrl, there is no body
        fmt.Println("An error occurred: File is not downloadable")
        return
    }

    // Measure transfer rate
    getRate := util.MeasureTransferRate()

    // GET the download url
    res, err := d.Client().Get(info.DownloadUrl)
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    // Close body on function exit
    defer res.Body.Close()

    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    // Write file content to stdout
    if stdout {
        io.Copy(os.Stdout, res.Body)
        return
    }

    // Check if file exists
    if util.FileExists(info.Title) {
        fmt.Printf("An error occurred: '%s' already exists\n", info.Title)
        return
    }

    // Create a new file
    outFile, err := os.Create(info.Title)
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    // Close file on function exit
    defer outFile.Close()

    // Save file to disk
    bytes, err := io.Copy(outFile, res.Body)
    if err != nil {
        fmt.Printf("An error occurred: %v")
        return
    }

    fmt.Printf("Downloaded '%s' at %s, total %s\n", info.Title, getRate(bytes), util.FileSizeFormat(bytes))

    if deleteAfterDownload {
        Delete(d, fileId)
    }
}

// Delete file with given file id
func Delete(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    if err = d.Files.Delete(fileId).Do(); err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    fmt.Printf("Removed file '%s'\n", info.Title)
}

// Make given file id readable by anyone -- auth not required to view/download file
func Share(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    perm := &drive.Permission{
        Value: "me",
        Type: "anyone",
        Role: "reader",
    }

    _, err = d.Permissions.Insert(fileId, perm).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    fmt.Printf("File '%s' is now readable by everyone @ %s\n", info.Title, util.PreviewUrl(fileId))
}

// Removes the 'anyone' permission -- auth will be required to view/download file
func Unshare(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    err = d.Permissions.Delete(fileId, "anyone").Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    fmt.Printf("File '%s' is now longer shared to 'anyone'\n", info.Title)
}

func isShared(d *gdrive.Drive, fileId string) bool {
    r, err := d.Permissions.List(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return false
    }

    for _, perm := range r.Items {
        if perm.Type == "anyone" {
            return true
        }
    }
    return false
}
<commit_msg>Fix typo<commit_after>package cli

import (
    "fmt"
    "os"
    "io"
    "path/filepath"
    "strings"
    "code.google.com/p/google-api-go-client/drive/v2"
    "../util"
    "../gdrive"
)

func List(d *gdrive.Drive, query, titleFilter string, maxResults int, sharedStatus bool) {
    caller := d.Files.List()

    if maxResults > 0 {
        caller.MaxResults(int64(maxResults))
    }

    if titleFilter != "" {
        q := fmt.Sprintf("title contains '%s'", titleFilter)
        caller.Q(q)
    }

    if query != "" {
        caller.Q(query)
    }

    list, err := caller.Do()
    if err != nil {
        fmt.Println(err)
        return
    }

    items := make([]map[string]string, 0, 0)

    for _, f := range list.Items {
        // Skip files that dont have a download url (they are not stored on google drive)
        if f.DownloadUrl == "" {
            continue
        }

        items = append(items, map[string]string{
            "Id": f.Id,
            "Title": util.TruncateString(f.Title, 40),
            "Size": util.FileSizeFormat(f.FileSize),
            "Created": util.ISODateToLocal(f.CreatedDate),
        })
    }

    columnOrder := []string{"Id", "Title", "Size", "Created"}

    if sharedStatus {
        addSharedStatus(d, items)
        columnOrder = append(columnOrder, "Shared")
    }

    util.PrintColumns(items, columnOrder, 3)
}

// Adds the key-value-pair 'Shared: True/False' to the map
func addSharedStatus(d *gdrive.Drive, items []map[string]string) {
    // Limit to 10 simultaneous requests
    active := make(chan bool, 10)
    done := make(chan bool)

    // Closure that performs the check
    checkStatus := func(item map[string]string) {
        // Wait for an empty spot in the active queue
        active <- true

        // Perform request
        shared := isShared(d, item["Id"])
        item["Shared"] = util.FormatBool(shared)

        // Decrement the active queue and notify that we are done
        <-active
        done <- true
    }

    // Go, go, go!
    for _, item := range items {
        go checkStatus(item)
    }

    // Wait for all goroutines to finish
    for i := 0; i < len(items); i++ {
        <-done
    }
}

func Info(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }
    printInfo(d, info)
}

func printInfo(d *gdrive.Drive, f *drive.File) {
    fields := map[string]string{
        "Id": f.Id,
        "Title": f.Title,
        "Description": f.Description,
        "Size": util.FileSizeFormat(f.FileSize),
        "Created": util.ISODateToLocal(f.CreatedDate),
        "Modified": util.ISODateToLocal(f.ModifiedDate),
        "Owner": strings.Join(f.OwnerNames, ", "),
        "Md5sum": f.Md5Checksum,
        "Shared": util.FormatBool(isShared(d, f.Id)),
    }

    order := []string{"Id", "Title", "Description", "Size", "Created", "Modified", "Owner", "Md5sum", "Shared"}
    util.Print(fields, order)
}

// Upload file to drive
func Upload(d *gdrive.Drive, input io.ReadCloser, title string, share bool) {
    // Use filename or 'untitled' as title if no title is specified
    if title == "" {
        if f, ok := input.(*os.File); ok && input != os.Stdin {
            title = filepath.Base(f.Name())
        } else {
            title = "untitled"
        }
    }

    metadata := &drive.File{Title: title}
    getRate := util.MeasureTransferRate()

    info, err := d.Files.Insert(metadata).Media(input).Do()
    if err != nil {
        fmt.Printf("An error occurred uploading the document: %v\n", err)
        return
    }

    // Total bytes transferred
    bytes := info.FileSize

    // Print information about uploaded file
    printInfo(d, info)
    fmt.Printf("Uploaded '%s' at %s, total %s\n", info.Title, getRate(bytes), util.FileSizeFormat(bytes))

    // Share file if the share flag was provided
    if share {
        Share(d, info.Id)
    }
}

func DownloadLatest(d *gdrive.Drive, stdout bool) {
    list, err := d.Files.List().Do()

    if err != nil {
        fmt.Println(err)
        return
    }

    if len(list.Items) == 0 {
        fmt.Println("No files found")
        return
    }

    latestId := list.Items[0].Id
    Download(d, latestId, stdout, true)
}

// Download file from drive
func Download(d *gdrive.Drive, fileId string, stdout, deleteAfterDownload bool) {
    // Get file info
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    if info.DownloadUrl == "" {
        // If there is no DownloadUrl, there is no body
        fmt.Println("An error occurred: File is not downloadable")
        return
    }

    // Measure transfer rate
    getRate := util.MeasureTransferRate()

    // GET the download url
    res, err := d.Client().Get(info.DownloadUrl)
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    // Close body on function exit
    defer res.Body.Close()

    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    // Write file content to stdout
    if stdout {
        io.Copy(os.Stdout, res.Body)
        return
    }

    // Check if file exists
    if util.FileExists(info.Title) {
        fmt.Printf("An error occurred: '%s' already exists\n", info.Title)
        return
    }

    // Create a new file
    outFile, err := os.Create(info.Title)
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    // Close file on function exit
    defer outFile.Close()

    // Save file to disk
    bytes, err := io.Copy(outFile, res.Body)
    if err != nil {
        fmt.Printf("An error occurred: %v")
        return
    }

    fmt.Printf("Downloaded '%s' at %s, total %s\n", info.Title, getRate(bytes), util.FileSizeFormat(bytes))

    if deleteAfterDownload {
        Delete(d, fileId)
    }
}

// Delete file with given file id
func Delete(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    if err = d.Files.Delete(fileId).Do(); err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    fmt.Printf("Removed file '%s'\n", info.Title)
}

// Make given file id readable by anyone -- auth not required to view/download file
func Share(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    perm := &drive.Permission{
        Value: "me",
        Type: "anyone",
        Role: "reader",
    }

    _, err = d.Permissions.Insert(fileId, perm).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    fmt.Printf("File '%s' is now readable by everyone @ %s\n", info.Title, util.PreviewUrl(fileId))
}

// Removes the 'anyone' permission -- auth will be required to view/download file
func Unshare(d *gdrive.Drive, fileId string) {
    info, err := d.Files.Get(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    err = d.Permissions.Delete(fileId, "anyone").Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return
    }

    fmt.Printf("File '%s' is no longer shared to 'anyone'\n", info.Title)
}

func isShared(d *gdrive.Drive, fileId string) bool {
    r, err := d.Permissions.List(fileId).Do()
    if err != nil {
        fmt.Printf("An error occurred: %v\n", err)
        return false
    }

    for _, perm := range r.Items {
        if perm.Type == "anyone" {
            return true
        }
    }
    return false
}
<|endoftext|>


<commit_before>package cli

import (
	"context"
	"flag"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/elpinal/coco3/config"
	"github.com/elpinal/coco3/eval"
	"github.com/elpinal/coco3/gate"
	"github.com/elpinal/coco3/parser"
)

type CLI struct {
	In  io.Reader
	Out io.Writer
	Err io.Writer

	config.Config

	exitCh chan int
	doneCh chan struct{}
}

func (c *CLI) Run(args []string) int {
	c.exitCh = make(chan int)
	c.doneCh = make(chan struct{})
	f := flag.NewFlagSet("coco3", flag.ContinueOnError)
	f.SetOutput(c.Err)
	f.Usage = func() {
		c.Err.Write([]byte("coco3 is a shell.\n"))
		c.Err.Write([]byte("Usage:\n"))
		f.PrintDefaults()
	}

	flagC := f.String("c", "", "take first argument as a command to execute")
	if err := f.Parse(args); err != nil {
		return 2
	}

	if len(c.Config.StartUpCommand) > 0 {
		done := make(chan struct{})
		go func() {
			if err := c.execute(c.Config.StartUpCommand); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			close(done)
		}()
		select {
		case code := <-c.exitCh:
			return code
		case <-done:
		}
	}

	if *flagC != "" {
		go func() {
			if err := c.execute([]byte(*flagC)); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			c.exitCh <- 0
		}()
		return <-c.exitCh
	}

	if len(f.Args()) > 0 {
		defer func() {
			close(c.doneCh)
		}()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		go c.runFiles(ctx, f.Args())
		return <-c.exitCh
	}

	conf := &c.Config
	conf.Init()
	g := gate.New(conf, c.In, c.Out, c.Err)
	go func() {
		for {
			if err := c.interact(g); err != nil {
				fmt.Fprintln(c.Err, err)
				g.Clear()
			}
		}
	}()
	return <-c.exitCh
}

func (c *CLI) interact(g gate.Gate) error {
	for {
		old, err := enterRowMode()
		if err != nil {
			return err
		}
		r, err := g.Read()
		if err != nil {
			return err
		}
		c.Out.Write([]byte{'\n'})
		if err := exitRowMode(old); err != nil {
			return err
		}
		if err := c.execute([]byte(string(r))); err != nil {
			return err
		}
		g.Clear()
	}
}

func (c *CLI) execute(b []byte) error {
	f, err := parser.ParseSrc(b)
	if err != nil {
		return err
	}
	e := eval.New(c.In, c.Out, c.Err)
	err = e.Eval(f.Lines)
	select {
	case code := <-e.ExitCh:
		c.exitCh <- code
		<-c.doneCh
	default:
	}
	return err
}

func (c *CLI) runFiles(ctx context.Context, files []string) {
	for _, file := range files {
		b, err := ioutil.ReadFile(file)
		if err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		if err := c.execute(b); err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		select {
		case <-ctx.Done():
			return
		default:
		}
	}
	c.exitCh <- 0
}
<commit_msg>Add comment<commit_after>package cli

import (
	"context"
	"flag"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/elpinal/coco3/config"
	"github.com/elpinal/coco3/eval"
	"github.com/elpinal/coco3/gate"
	"github.com/elpinal/coco3/parser"
)

type CLI struct {
	In  io.Reader
	Out io.Writer
	Err io.Writer

	config.Config

	exitCh chan int
	doneCh chan struct{} // to ensure exiting just after exitCh received
}

func (c *CLI) Run(args []string) int {
	c.exitCh = make(chan int)
	c.doneCh = make(chan struct{})
	f := flag.NewFlagSet("coco3", flag.ContinueOnError)
	f.SetOutput(c.Err)
	f.Usage = func() {
		c.Err.Write([]byte("coco3 is a shell.\n"))
		c.Err.Write([]byte("Usage:\n"))
		f.PrintDefaults()
	}

	flagC := f.String("c", "", "take first argument as a command to execute")
	if err := f.Parse(args); err != nil {
		return 2
	}

	if len(c.Config.StartUpCommand) > 0 {
		done := make(chan struct{})
		go func() {
			if err := c.execute(c.Config.StartUpCommand); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			close(done)
		}()
		select {
		case code := <-c.exitCh:
			return code
		case <-done:
		}
	}

	if *flagC != "" {
		go func() {
			if err := c.execute([]byte(*flagC)); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			c.exitCh <- 0
		}()
		return <-c.exitCh
	}

	if len(f.Args()) > 0 {
		defer func() {
			close(c.doneCh)
		}()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		go c.runFiles(ctx, f.Args())
		return <-c.exitCh
	}

	conf := &c.Config
	conf.Init()
	g := gate.New(conf, c.In, c.Out, c.Err)
	go func() {
		for {
			if err := c.interact(g); err != nil {
				fmt.Fprintln(c.Err, err)
				g.Clear()
			}
		}
	}()
	return <-c.exitCh
}

func (c *CLI) interact(g gate.Gate) error {
	for {
		old, err := enterRowMode()
		if err != nil {
			return err
		}
		r, err := g.Read()
		if err != nil {
			return err
		}
		c.Out.Write([]byte{'\n'})
		if err := exitRowMode(old); err != nil {
			return err
		}
		if err := c.execute([]byte(string(r))); err != nil {
			return err
		}
		g.Clear()
	}
}

func (c *CLI) execute(b []byte) error {
	f, err := parser.ParseSrc(b)
	if err != nil {
		return err
	}
	e := eval.New(c.In, c.Out, c.Err)
	err = e.Eval(f.Lines)
	select {
	case code := <-e.ExitCh:
		c.exitCh <- code
		<-c.doneCh
	default:
	}
	return err
}

func (c *CLI) runFiles(ctx context.Context, files []string) {
	for _, file := range files {
		b, err := ioutil.ReadFile(file)
		if err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		if err := c.execute(b); err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		select {
		case <-ctx.Done():
			return
		default:
		}
	}
	c.exitCh <- 0
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"sort"
	"time"

	"github.com/gophergala2016/goad"
	"github.com/gophergala2016/goad/Godeps/_workspace/src/github.com/nsf/termbox-go"
	"github.com/gophergala2016/goad/queue"
)

var (
	url         string
	concurrency uint
	requests    uint
	timeout     uint
	region      string
)

const coldef = termbox.ColorDefault
const nano = 1000000000

func main() {
	flag.UintVar(&concurrency, "c", 10, "number of concurrent requests")
	flag.UintVar(&requests, "n", 1000, "number of total requests to make")
	flag.UintVar(&timeout, "t", 15, "request timeout in seconds")
	flag.StringVar(&region, "r", "us-east-1", "AWS region")
	flag.Parse()

	if len(flag.Args()) < 1 {
		fmt.Println("You must specify a URL")
		os.Exit(1)
	}

	url = flag.Args()[0]

	test := goad.NewTest(&goad.TestConfig{
		URL:            url,
		Concurrency:    concurrency,
		TotalRequests:  requests,
		RequestTimeout: time.Duration(timeout) * time.Second,
		Region:         region,
	})

	var finalResult queue.RegionsAggData
	defer printSummary(&finalResult)

	start(test, &finalResult)
}

func start(test *goad.Test, finalResult *queue.RegionsAggData) {
	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	resultChan := test.Start()
	termbox.Sync()
	for result := range resultChan {
		result.Regions["eu-west-1"] = result.Regions["us-east-1"]
		// sort so that regions always appear in the same order
		var regions []string
		for key := range result.Regions {
			regions = append(regions, key)
		}
		sort.Strings(regions)
		y := 0
		for _, region := range regions {
			data := result.Regions[region]
			y = renderRegion(data, y)
			y++
		}
		termbox.Flush()
		finalResult.Regions = result.Regions
	}
}

// renderRegion returns the y for the next empty line
func renderRegion(data queue.AggData, y int) int {
	x := 0
	renderString(x, y, "Region: ", termbox.ColorWhite, termbox.ColorBlue)
	x += 8
	regionStr := fmt.Sprintf("%-10s", data.Region)
	renderString(x, y, regionStr, termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlue)
	x = 0
	y++
	headingStr := "   TotReqs   TotBytes  AveTime   AveReq/s Ave1stByte"
	renderString(x, y, headingStr, coldef|termbox.AttrBold, coldef)
	y++
	resultStr := fmt.Sprintf("%10d %10d %7.2fs %10.2f   %7.2fs", data.TotalReqs, data.TotBytesRead, float64(data.AveTimeForReq)/nano, data.AveReqPerSec, float64(data.AveTimeToFirst)/nano)
	x = 0
	renderString(x, y, resultStr, coldef, coldef)
	y++
	return y
}

func renderString(x int, y int, str string, f termbox.Attribute, b termbox.Attribute) {
	for i, c := range str {
		termbox.SetCell(x+i, y, c, f, b)
	}
}

func printSummary(result *queue.RegionsAggData) {
	fmt.Println("Summary")
	fmt.Println("")
	for region, data := range result.Regions {
		fmt.Println("Region: " + region)
		fmt.Println("   TotReqs   TotBytes  AveTime   AveReq/s Ave1stByte")
		fmt.Printf("%10d %10d %7.2fs %10.2f   %7.2fs\n", data.TotalReqs, data.TotBytesRead, float64(data.AveTimeForReq)/nano, data.AveReqPerSec, float64(data.AveTimeToFirst)/nano)
		fmt.Println("")
	}
}
<commit_msg>Bold the headings<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"sort"
	"time"

	"github.com/gophergala2016/goad"
	"github.com/gophergala2016/goad/Godeps/_workspace/src/github.com/nsf/termbox-go"
	"github.com/gophergala2016/goad/queue"
)

var (
	url         string
	concurrency uint
	requests    uint
	timeout     uint
	region      string
)

const coldef = termbox.ColorDefault
const nano = 1000000000

func main() {
	flag.UintVar(&concurrency, "c", 10, "number of concurrent requests")
	flag.UintVar(&requests, "n", 1000, "number of total requests to make")
	flag.UintVar(&timeout, "t", 15, "request timeout in seconds")
	flag.StringVar(&region, "r", "us-east-1", "AWS region")
	flag.Parse()

	if len(flag.Args()) < 1 {
		fmt.Println("You must specify a URL")
		os.Exit(1)
	}

	url = flag.Args()[0]

	test := goad.NewTest(&goad.TestConfig{
		URL:            url,
		Concurrency:    concurrency,
		TotalRequests:  requests,
		RequestTimeout: time.Duration(timeout) * time.Second,
		Region:         region,
	})

	var finalResult queue.RegionsAggData
	defer printSummary(&finalResult)

	start(test, &finalResult)
}

func start(test *goad.Test, finalResult *queue.RegionsAggData) {
	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	resultChan := test.Start()
	termbox.Sync()
	for result := range resultChan {
		result.Regions["eu-west-1"] = result.Regions["us-east-1"]
		// sort so that regions always appear in the same order
		var regions []string
		for key := range result.Regions {
			regions = append(regions, key)
		}
		sort.Strings(regions)
		y := 0
		for _, region := range regions {
			data := result.Regions[region]
			y = renderRegion(data, y)
			y++
		}
		termbox.Flush()
		finalResult.Regions = result.Regions
	}
}

// renderRegion returns the y for the next empty line
func renderRegion(data queue.AggData, y int) int {
	x := 0
	renderString(x, y, "Region: ", termbox.ColorWhite, termbox.ColorBlue)
	x += 8
	regionStr := fmt.Sprintf("%-10s", data.Region)
	renderString(x, y, regionStr, termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlue)
	x = 0
	y++
	headingStr := "   TotReqs   TotBytes  AveTime   AveReq/s Ave1stByte"
	renderString(x, y, headingStr, coldef|termbox.AttrBold, coldef)
	y++
	resultStr := fmt.Sprintf("%10d %10d %7.2fs %10.2f   %7.2fs", data.TotalReqs, data.TotBytesRead, float64(data.AveTimeForReq)/nano, data.AveReqPerSec, float64(data.AveTimeToFirst)/nano)
	x = 0
	renderString(x, y, resultStr, coldef, coldef)
	y++
	return y
}

func renderString(x int, y int, str string, f termbox.Attribute, b termbox.Attribute) {
	for i, c := range str {
		termbox.SetCell(x+i, y, c, f, b)
	}
}

func boldPrintln(msg string) {
	fmt.Printf("\033[1m%s\033[0m\n", msg)
}

func printSummary(result *queue.RegionsAggData) {
	boldPrintln("Summary")
	fmt.Println("")
	for region, data := range result.Regions {
		fmt.Println("Region: " + region)
		boldPrintln("   TotReqs   TotBytes  AveTime   AveReq/s Ave1stByte")
		fmt.Printf("%10d %10d %7.2fs %10.2f   %7.2fs\n", data.TotalReqs, data.TotBytesRead, float64(data.AveTimeForReq)/nano, data.AveReqPerSec, float64(data.AveTimeToFirst)/nano)
		fmt.Println("")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"sort"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/goadapp/goad"
	"github.com/goadapp/goad/helpers"
	"github.com/goadapp/goad/queue"
	"github.com/goadapp/goad/version"
	"github.com/nsf/termbox-go"
	"github.com/zeph/ini"
)

var (
	url         string
	concurrency uint
	requests    uint
	timeout     uint
	regions     string
	method      string
	body        string
	headers     helpers.StringsliceFlag
	awsProfile  string
	outputFile  string
)

const coldef = termbox.ColorDefault
const nano = 1000000000

func main() {
	var printVersion bool

	flag.StringVar(&url, "u", "", "URL to load test (required)")
	flag.StringVar(&method, "m", "GET", "HTTP method")
	flag.StringVar(&body, "b", "", "HTTP request body")
	flag.UintVar(&concurrency, "c", 10, "number of concurrent requests")
	flag.UintVar(&requests, "n", 1000, "number of total requests to make")
	flag.UintVar(&timeout, "t", 15, "request timeout in seconds")
	flag.StringVar(&regions, "r", "us-east-1,eu-west-1,ap-northeast-1", "AWS regions to run in (comma separated, no spaces)")
	flag.StringVar(&awsProfile, "p", "", "AWS named profile to use")
	flag.StringVar(&outputFile, "o", "", "Optional path to JSON file for result storage")
	flag.Var(&headers, "H", "List of headers")
	flag.BoolVar(&printVersion, "version", false, "print the current Goad version")
	flag.Parse()

	if printVersion {
		fmt.Println(version.Version)
		os.Exit(0)
	}

	regionsList := strings.Split(regions, ",")

	cfg, err := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true}, "default.ini")
	if err == nil {
		url = cfg.Section("general").Key("url").String()
		concurrency, _ = cfg.Section("general").Key("concurrency").Uint()
		requests, _ = cfg.Section("general").Key("requests").Uint()
		/* it seems we have mismatch on how we read
		the timeout param on 2 different interfaces */
		timeoutArg := cfg.Section("general").Key("timeout").String()
		timeoutObj, _ := time.ParseDuration(timeoutArg)
		timeout = uint(timeoutObj.Seconds())
		//
		regionsList = cfg.Section("regions").KeyStrings()
	} else {
		log.Print(err)
	}

	if url == "" {
		flag.Usage()
		os.Exit(0)
	}

	test, testerr := goad.NewTest(&goad.TestConfig{
		URL:            url,
		Concurrency:    concurrency,
		TotalRequests:  requests,
		RequestTimeout: time.Duration(timeout) * time.Second,
		Regions:        regionsList,
		Method:         method,
		Body:           body,
		Headers:        headers,
		AwsProfile:     awsProfile,
	})
	if testerr != nil {
		fmt.Println(testerr)
		os.Exit(1)
	}

	var finalResult queue.RegionsAggData
	defer printSummary(&finalResult)

	if outputFile != "" {
		defer saveJSONSummary(outputFile, &finalResult)
	}

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // but interrupts from kbd are blocked by termbox

	start(test, &finalResult, sigChan)
}

func start(test *goad.Test, finalResult *queue.RegionsAggData, sigChan chan os.Signal) {
	err := termbox.Init()
	if err != nil {
		panic(err)
	}

	defer termbox.Close()
	termbox.Sync()
	renderString(0, 0, "Launching on AWS... (be patient)", coldef, coldef)
	renderLogo()
	termbox.Flush()

	resultChan := test.Start()

	_, h := termbox.Size()
	renderString(0, h-1, "Press ctrl-c to interrupt", coldef, coldef)
	termbox.Flush()

	go func() {
		for {
			event := termbox.PollEvent()
			if event.Key == 3 {
				sigChan <- syscall.SIGINT
			}
		}
	}()

	firstTime := true
outer:
	for {
		select {
		case result, ok := <-resultChan:
			if !ok {
				break outer
			}

			if firstTime {
				clearLogo()
				firstTime = false
			}

			// sort so that regions always appear in the same order
			var regions []string
			for key := range result.Regions {
				regions = append(regions, key)
			}
			sort.Strings(regions)
			y := 3
			totalReqs := 0
			for _, region := range regions {
				data := result.Regions[region]
				totalReqs += data.TotalReqs
				y = renderRegion(data, y)
				y++
			}

			y = 0
			percentDone := float64(totalReqs) / float64(result.TotalExpectedRequests)
			drawProgressBar(percentDone, y)

			termbox.Flush()
			finalResult.Regions = result.Regions

		case <-sigChan:
			break outer
		}
	}
}

func renderLogo() {
	s1 := `	  _____                 _`
	s2 := `  / ____|               | |`
	s3 := `	| |  __  ___   ____  __| |`
	s4 := `	| | |_ |/ _ \ / _  |/ _  |`
	s5 := `	| |__| | (_) | (_| | (_| |`
	s6 := `	 \_____|\___/ \__,_|\__,_|`
	s7 := " Global load testing with Go"
	arr := [...]string{s1, s2, s3, s4, s5, s6, s7}
	for i, str := range arr {
		renderString(0, i+1, str, coldef, coldef)
	}
}

// Also clears loading message
func clearLogo() {
	for i := 0; i < 8; i++ {
		renderString(0, i, "                                ", coldef, coldef)
	}
}

// renderRegion returns the y for the next empty line
func renderRegion(data queue.AggData, y int) int {
	x := 0
	renderString(x, y, "Region: ", termbox.ColorWhite, termbox.ColorBlue)
	x += 8
	regionStr := fmt.Sprintf("%s", data.Region)
	renderString(x, y, regionStr, termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlue)
	x = 0
	y++
	headingStr := "   TotReqs   TotBytes    AvgTime   AvgReq/s  AvgKbps/s"
	renderString(x, y, headingStr, coldef|termbox.AttrBold, coldef)
	y++
	resultStr := fmt.Sprintf("%10d %10s   %7.3fs %10.2f %10.2f", data.TotalReqs, humanize.Bytes(uint64(data.TotBytesRead)), float64(data.AveTimeForReq)/nano, data.AveReqPerSec, data.AveKBytesPerSec)
	renderString(x, y, resultStr, coldef, coldef)
	y++
	headingStr = "   Slowest    Fastest   Timeouts  TotErrors"
	renderString(x, y, headingStr, coldef|termbox.AttrBold, coldef)
	y++
	resultStr = fmt.Sprintf("  %7.3fs   %7.3fs %10d %10d", float64(data.Slowest)/nano, float64(data.Fastest)/nano, data.TotalTimedOut, totErrors(&data))
	renderString(x, y, resultStr, coldef, coldef)
	y++

	return y
}

func totErrors(data *queue.AggData) int {
	var okReqs int
	for statusStr, value := range data.Statuses {
		status, _ := strconv.Atoi(statusStr)
		if status < 400 {
			okReqs += value
		}
	}
	return data.TotalReqs - okReqs
}

func drawProgressBar(percent float64, y int) {
	x := 0
	width := 52
	percentStr := fmt.Sprintf("%5.1f%%            ", percent*100)
	renderString(x, y, percentStr, coldef, coldef)
	y++
	hashes := int(percent * float64(width))
	if percent > 0.99 {
		hashes = width
	}
	renderString(x, y, "[", coldef, coldef)

	for x++; x <= hashes; x++ {
		renderString(x, y, "#", coldef, coldef)
	}
	renderString(width+1, y, "]", coldef, coldef)
}

func renderString(x int, y int, str string, f termbox.Attribute, b termbox.Attribute) {
	for i, c := range str {
		termbox.SetCell(x+i, y, c, f, b)
	}
}

func boldPrintln(msg string) {
	fmt.Printf("\033[1m%s\033[0m\n", msg)
}

func printData(data *queue.AggData) {
	boldPrintln("   TotReqs   TotBytes    AvgTime   AvgReq/s  AvgKbps/s")
	fmt.Printf("%10d %10s   %7.3fs %10.2f %10.2f\n", data.TotalReqs, humanize.Bytes(uint64(data.TotBytesRead)), float64(data.AveTimeForReq)/nano, data.AveReqPerSec, data.AveKBytesPerSec)
	boldPrintln("   Slowest    Fastest   Timeouts  TotErrors")
	fmt.Printf("  %7.3fs   %7.3fs %10d %10d", float64(data.Slowest)/nano, float64(data.Fastest)/nano, data.TotalTimedOut, totErrors(data))
	fmt.Println("")
}

func printSummary(result *queue.RegionsAggData) {
	if len(result.Regions) == 0 {
		boldPrintln("No results received")
		return
	}
	boldPrintln("Regional results")
	fmt.Println("")

	for region, data := range result.Regions {
		fmt.Println("Region: " + region)
		printData(&data)
	}

	overall := queue.SumRegionResults(result)

	fmt.Println("")
	boldPrintln("Overall")
	fmt.Println("")
	printData(overall)

	boldPrintln("HTTPStatus   Requests")
	for statusStr, value := range overall.Statuses {
		fmt.Printf("%10s %10d\n", statusStr, value)
	}
	fmt.Println("")
	boldPrintln("Host(s) we did hit!")
	for targetStr, value := range overall.Targets {
		fmt.Printf("%10s %10d\n", targetStr, value)
	}
	fmt.Println("")
}

func saveJSONSummary(path string, result *queue.RegionsAggData) {
	if len(result.Regions) == 0 {
		return
	}
	results := make(map[string]queue.AggData)

	for region, data := range result.Regions {
		results[region] = data
	}

	overall := queue.SumRegionResults(result)

	results["overall"] = *overall
	b, err := json.MarshalIndent(results, "", "  ")
	if err != nil {
		fmt.Println(err)
		return
	}
	err = ioutil.WriteFile(path, b, 0644)
	if err != nil {
		fmt.Println(err)
		return
	}
}
<commit_msg>triggering Travis-CI...<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"sort"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/goadapp/goad"
	"github.com/goadapp/goad/helpers"
	"github.com/goadapp/goad/queue"
	"github.com/goadapp/goad/version"
	"github.com/nsf/termbox-go"
	"github.com/zeph/ini"
)

var (
	url         string
	concurrency uint
	requests    uint
	timeout     uint
	regions     string
	method      string
	body        string
	headers     helpers.StringsliceFlag
	awsProfile  string
	outputFile  string
)

const coldef = termbox.ColorDefault
const nano = 1000000000

func main() {
	var printVersion bool

	flag.StringVar(&url, "u", "", "URL to load test (required)")
	flag.StringVar(&method, "m", "GET", "HTTP method")
	flag.StringVar(&body, "b", "", "HTTP request body")
	flag.UintVar(&concurrency, "c", 10, "number of concurrent requests")
	flag.UintVar(&requests, "n", 1000, "number of total requests to make")
	flag.UintVar(&timeout, "t", 15, "request timeout in seconds")
	flag.StringVar(&regions, "r", "us-east-1,eu-west-1,ap-northeast-1", "AWS regions to run in (comma separated, no spaces)")
	flag.StringVar(&awsProfile, "p", "", "AWS named profile to use")
	flag.StringVar(&outputFile, "o", "", "Optional path to JSON file for result storage")
	flag.Var(&headers, "H", "List of headers")
	flag.BoolVar(&printVersion, "version", false, "print the current Goad version")
	flag.Parse()

	if printVersion {
		fmt.Println(version.Version)
		os.Exit(0)
	}

	regionsList := strings.Split(regions, ",")

	cfg, err := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true}, "default.ini")
	if err == nil {
		url = cfg.Section("general").Key("url").String()
		concurrency, _ = cfg.Section("general").Key("concurrency").Uint()
		requests, _ = cfg.Section("general").Key("requests").Uint()
		timeoutArg := cfg.Section("general").Key("timeout").String()
		timeoutObj, _ := time.ParseDuration(timeoutArg)
		timeout = uint(timeoutObj.Seconds())
		regionsList = cfg.Section("regions").KeyStrings()
	} else {
		log.Print(err)
	}

	if url == "" {
		flag.Usage()
		os.Exit(0)
	}

	test, testerr := goad.NewTest(&goad.TestConfig{
		URL:            url,
		Concurrency:    concurrency,
		TotalRequests:  requests,
		RequestTimeout: time.Duration(timeout) * time.Second,
		Regions:        regionsList,
		Method:         method,
		Body:           body,
		Headers:        headers,
		AwsProfile:     awsProfile,
	})
	if testerr != nil {
		fmt.Println(testerr)
		os.Exit(1)
	}

	var finalResult queue.RegionsAggData
	defer printSummary(&finalResult)

	if outputFile != "" {
		defer saveJSONSummary(outputFile, &finalResult)
	}

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // but interrupts from kbd are blocked by termbox

	start(test, &finalResult, sigChan)
}

func start(test *goad.Test, finalResult *queue.RegionsAggData, sigChan chan os.Signal) {
	err := termbox.Init()
	if err != nil {
		panic(err)
	}

	defer termbox.Close()
	termbox.Sync()
	renderString(0, 0, "Launching on AWS... (be patient)", coldef, coldef)
	renderLogo()
	termbox.Flush()

	resultChan := test.Start()

	_, h := termbox.Size()
	renderString(0, h-1, "Press ctrl-c to interrupt", coldef, coldef)
	termbox.Flush()

	go func() {
		for {
			event := termbox.PollEvent()
			if event.Key == 3 {
				sigChan <- syscall.SIGINT
			}
		}
	}()

	firstTime := true
outer:
	for {
		select {
		case result, ok := <-resultChan:
			if !ok {
				break outer
			}

			if firstTime {
				clearLogo()
				firstTime = false
			}

			// sort so that regions always appear in the same order
			var regions []string
			for key := range result.Regions {
				regions = append(regions, key)
			}
			sort.Strings(regions)
			y := 3
			totalReqs := 0
			for _, region := range regions {
				data := result.Regions[region]
				totalReqs += data.TotalReqs
				y = renderRegion(data, y)
				y++
			}

			y = 0
			percentDone := float64(totalReqs) / float64(result.TotalExpectedRequests)
			drawProgressBar(percentDone, y)

			termbox.Flush()
			finalResult.Regions = result.Regions

		case <-sigChan:
			break outer
		}
	}
}

func renderLogo() {
	s1 := `	  _____                 _`
	s2 := `  / ____|               | |`
	s3 := `	| |  __  ___   ____  __| |`
	s4 := `	| | |_ |/ _ \ / _  |/ _  |`
	s5 := `	| |__| | (_) | (_| | (_| |`
	s6 := `	 \_____|\___/ \__,_|\__,_|`
	s7 := " Global load testing with Go"
	arr := [...]string{s1, s2, s3, s4, s5, s6, s7}
	for i, str := range arr {
		renderString(0, i+1, str, coldef, coldef)
	}
}

// Also clears loading message
func clearLogo() {
	for i := 0; i < 8; i++ {
		renderString(0, i, "                                ", coldef, coldef)
	}
}

// renderRegion returns the y for the next empty line
func renderRegion(data queue.AggData, y int) int {
	x := 0
	renderString(x, y, "Region: ", termbox.ColorWhite, termbox.ColorBlue)
	x += 8
	regionStr := fmt.Sprintf("%s", data.Region)
	renderString(x, y, regionStr, termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlue)
	x = 0
	y++
	headingStr := "   TotReqs   TotBytes    AvgTime   AvgReq/s  AvgKbps/s"
	renderString(x, y, headingStr, coldef|termbox.AttrBold, coldef)
	y++
	resultStr := fmt.Sprintf("%10d %10s   %7.3fs %10.2f %10.2f", data.TotalReqs, humanize.Bytes(uint64(data.TotBytesRead)), float64(data.AveTimeForReq)/nano, data.AveReqPerSec, data.AveKBytesPerSec)
	renderString(x, y, resultStr, coldef, coldef)
	y++
	headingStr = "   Slowest    Fastest   Timeouts  TotErrors"
	renderString(x, y, headingStr, coldef|termbox.AttrBold, coldef)
	y++
	resultStr = fmt.Sprintf("  %7.3fs   %7.3fs %10d %10d", float64(data.Slowest)/nano, float64(data.Fastest)/nano, data.TotalTimedOut, totErrors(&data))
	renderString(x, y, resultStr, coldef, coldef)
	y++

	return y
}

func totErrors(data *queue.AggData) int {
	var okReqs int
	for statusStr, value := range data.Statuses {
		status, _ := strconv.Atoi(statusStr)
		if status < 400 {
			okReqs += value
		}
	}
	return data.TotalReqs - okReqs
}

func drawProgressBar(percent float64, y int) {
	x := 0
	width := 52
	percentStr := fmt.Sprintf("%5.1f%%            ", percent*100)
	renderString(x, y, percentStr, coldef, coldef)
	y++
	hashes := int(percent * float64(width))
	if percent > 0.99 {
		hashes = width
	}
	renderString(x, y, "[", coldef, coldef)

	for x++; x <= hashes; x++ {
		renderString(x, y, "#", coldef, coldef)
	}
	renderString(width+1, y, "]", coldef, coldef)
}

func renderString(x int, y int, str string, f termbox.Attribute, b termbox.Attribute) {
	for i, c := range str {
		termbox.SetCell(x+i, y, c, f, b)
	}
}

func boldPrintln(msg string) {
	fmt.Printf("\033[1m%s\033[0m\n", msg)
}

func printData(data *queue.AggData) {
	boldPrintln("   TotReqs   TotBytes    AvgTime   AvgReq/s  AvgKbps/s")
	fmt.Printf("%10d %10s   %7.3fs %10.2f %10.2f\n", data.TotalReqs, humanize.Bytes(uint64(data.TotBytesRead)), float64(data.AveTimeForReq)/nano, data.AveReqPerSec, data.AveKBytesPerSec)
	boldPrintln("   Slowest    Fastest   Timeouts  TotErrors")
	fmt.Printf("  %7.3fs   %7.3fs %10d %10d", float64(data.Slowest)/nano, float64(data.Fastest)/nano, data.TotalTimedOut, totErrors(data))
	fmt.Println("")
}

func printSummary(result *queue.RegionsAggData) {
	if len(result.Regions) == 0 {
		boldPrintln("No results received")
		return
	}
	boldPrintln("Regional results")
	fmt.Println("")

	for region, data := range result.Regions {
		fmt.Println("Region: " + region)
		printData(&data)
	}

	overall := queue.SumRegionResults(result)

	fmt.Println("")
	boldPrintln("Overall")
	fmt.Println("")
	printData(overall)

	boldPrintln("HTTPStatus   Requests")
	for statusStr, value := range overall.Statuses {
		fmt.Printf("%10s %10d\n", statusStr, value)
	}
	fmt.Println("")
	boldPrintln("Host(s) we did hit!")
	for targetStr, value := range overall.Targets {
		fmt.Printf("%10s %10d\n", targetStr, value)
	}
	fmt.Println("")
}

func saveJSONSummary(path string, result *queue.RegionsAggData) {
	if len(result.Regions) == 0 {
		return
	}
	results := make(map[string]queue.AggData)

	for region, data := range result.Regions {
		results[region] = data
	}

	overall := queue.SumRegionResults(result)

	results["overall"] = *overall
	b, err := json.MarshalIndent(results, "", "  ")
	if err != nil {
		fmt.Println(err)
		return
	}
	err = ioutil.WriteFile(path, b, 0644)
	if err != nil {
		fmt.Println(err)
		return
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"context"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/pkg/errors"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/elpinal/coco3/config"
	"github.com/elpinal/coco3/eval"
	"github.com/elpinal/coco3/gate"
	"github.com/elpinal/coco3/parser"

	"github.com/elpinal/coco3/extra"
	eparser "github.com/elpinal/coco3/extra/parser"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
)

type CLI struct {
	In  io.Reader
	Out io.Writer
	Err io.Writer

	*config.Config

	db *sqlx.DB

	execute1 func([]byte) (action, error)
}

func (c *CLI) init() {
	if c.Out == nil {
		c.Out = ioutil.Discard
	}
	if c.Err == nil {
		c.Err = ioutil.Discard
	}
	if c.Config == nil {
		c.Config = &config.Config{}
	}
	c.Config.Init()
}

func (c *CLI) Run(args []string) int {
	f := flag.NewFlagSet("coco3", flag.ContinueOnError)
	f.SetOutput(c.Err)
	f.Usage = func() {
		c.Err.Write([]byte("coco3 is a shell.\n"))
		c.Err.Write([]byte("Usage:\n"))
		f.PrintDefaults()
	}

	flagC := f.String("c", "", "take first argument as a command to execute")
	flagE := f.Bool("extra", c.Config.Extra, "switch to extra mode")
	if err := f.Parse(args); err != nil {
		return 2
	}
	return c.run(f.Args(), flagC, flagE)
}

func (c *CLI) run(args []string, flagC *string, flagE *bool) int {
	// Aliases, only available for non-extra mode.
	for _, alias := range c.Config.Alias {
		eval.DefAlias(alias[0], alias[1])
	}

	// Prepare environment.
	for k, v := range c.Config.Env {
		err := os.Setenv(k, v)
		if err != nil {
			c.errorln(err)
			return 1
		}
	}

	setpath(c.Config.Paths)

	if *flagE {
		// If -extra flag is on, enable extra mode on any command executions.
		c.execute1 = c.executeExtra
	} else {
		c.execute1 = c.execute
	}

	if len(c.Config.StartUpCommand) > 0 {
		a, err := c.execute1(c.Config.StartUpCommand)
		if err != nil {
			c.printExecError(err)
			return 1
		}
		if e, ok := a.(exit); ok {
			return e.code
		}
	}

	switch {
	case *flagC != "":
		// The -c flag.
		return c.fromArg(*flagC)
	case len(args) > 0:
		// Execute files.
		return c.executeFiles(args)
	default:
		// Interactive mode.
		return c.runInteractiveMode()
	}
}

func (c *CLI) fromArg(program string) int {
	a, err := c.execute1([]byte(program))
	if err != nil {
		c.printExecError(err)
		return 1
	}
	if e, ok := a.(exit); ok {
		return e.code
	}
	return 0
}

func (c *CLI) executeFiles(args []string) int {
	a, err := c.runFiles(args)
	if err != nil {
		c.printExecError(err)
		return 1
	}
	if e, ok := a.(exit); ok {
		return e.code
	}
	return 0
}

func (c *CLI) runInteractiveMode() int {
	// Inherit history.
	histRunes, err := c.getHistory(c.Config.HistFile)
	if err != nil {
		c.errorln(err)
		return 1
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	g := gate.NewContext(ctx, c.Config, c.In, c.Out, c.Err, histRunes)
	for {
		a, err := c.interact(g)
		if err != nil {
			c.printExecError(err)
		}
		if e, ok := a.(exit); ok {
			return e.code
		}
	}
}

func (c *CLI) errorf(s string, a ...interface{}) {
	fmt.Fprintf(c.Err, s, a...)
}

func (c *CLI) errorln(s ...interface{}) {
	fmt.Fprintln(c.Err, s...)
}

func (c *CLI) errorp(s ...interface{}) {
	fmt.Fprint(c.Err, s...)
}

func (c *CLI) getHistory(filename string) ([][]rune, error) {
	db, err := sqlx.Connect("sqlite3", filename)
	if err != nil {
		return nil, errors.Wrap(err, "connecting history file")
	}
	_, err = db.Exec(schema)
	if err != nil {
		return nil, errors.Wrap(err, "initializing history file")
	}
	var history []string
	err = db.Select(&history, "select line from command_info")
	if err != nil {
		return nil, errors.Wrap(err, "restoring history")
	}
	// TODO: Is this way proper?
	c.db = db
	return sanitizeHistory(history), nil
}

func (c *CLI) printExecError(err error) {
	if pe, ok := err.(*eparser.ParseError); ok {
		c.errorln(pe.Verbose())
	} else {
		c.errorln(err)
	}
}

// setpath sets the PATH environment variable.
func setpath(args []string) {
	if len(args) == 0 {
		return
	}
	paths := filepath.SplitList(os.Getenv("PATH"))
	var newPaths []string
	for _, path := range paths {
		if contains(args, path) {
			continue
		}
		newPaths = append(newPaths, path)
	}
	newPaths = append(args, newPaths...)
	os.Setenv("PATH", strings.Join(newPaths, string(filepath.ListSeparator)))
}

func contains(xs []string, s string) bool {
	for _, x := range xs {
		if x == s {
			return true
		}
	}
	return false
}

func sanitizeHistory(history []string) [][]rune {
	histRunes := make([][]rune, 0, len(history))
	for _, line := range history {
		if line == "" {
			continue
		}
		l := len(histRunes)
		s := []rune(line)
		if l > 0 && compareRunes(histRunes[l-1], s) {
			continue
		}
		histRunes = append(histRunes, s)
	}
	return histRunes
}

func compareRunes(r1, r2 []rune) bool {
	if len(r1) != len(r2) {
		return false
	}
	for i, r := range r1 {
		if r2[i] != r {
			return false
		}
	}
	return true
}

func (c *CLI) interact(g gate.Gate) (action, error) {
	r, end, err := c.read(g)
	if err != nil {
		return nil, err
	}
	if end {
		return exitSuccess, nil
	}
	ch := c.writeHistory(r)
	a, err := c.execute1([]byte(string(r)))
	if err != nil {
		return a, err
	}
	return a, <-ch
}

func (c *CLI) read(g gate.Gate) ([]rune, bool, error) {
	defer c.Out.Write([]byte{'\n'})
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		return nil, false, err
	}
	defer func() {
		if err := terminal.Restore(0, oldState); err != nil {
			c.errorln(err)
		}
	}()
	r, end, err := g.Read()
	if err != nil {
		return nil, false, err
	}
	return r, end, nil
}

func (c *CLI) writeHistory(r []rune) <-chan error {
	startTime := time.Now()
	ch := make(chan error)
	go func() {
		_, err := c.db.Exec("insert into command_info (time, line) values ($1, $2)", startTime, string(r))
		if err != nil {
			ch <- errors.Wrap(err, "saving history")
			return
		}
		ch <- nil
	}()
	return ch
}

const schema = `
create table if not exists command_info (
    time datetime,
    line text
)`

func (c *CLI) execute(b []byte) (action, error) {
	f, err := parser.ParseSrc(b)
	if err != nil {
		return nil, err
	}
	e := eval.New(c.In, c.Out, c.Err, c.db)
	err = e.Eval(f.Lines)
	select {
	case code := <-e.ExitCh:
		return exit{code}, nil
	default:
	}
	return nil, err
}

func (c *CLI) executeExtra(b []byte) (action, error) {
	cmd, err := eparser.Parse(b)
	if err != nil {
		return nil, err
	}
	e := extra.New(extra.Option{DB: c.db})
	err = e.Eval(cmd)
	if err == nil {
		return nil, nil
	}
	if pe, ok := err.(*eparser.ParseError); ok {
		pe.Src = string(b)
	}
	return nil, err
}

func (c *CLI) runFiles(files []string) (action, error) {
	for _, file := range files {
		b, err := ioutil.ReadFile(file)
		if err != nil {
			return nil, err
		}
		a, err := c.execute1(b)
		if err != nil {
			return nil, err
		}
		if a != nil {
			return a, nil
		}
	}
	return nil, nil
}

type action interface {
	act()
}

type exit struct {
	code int
}

func (e exit) act() {}

var exitSuccess = exit{0}
<commit_msg>Move c.init to top<commit_after>package cli

import (
	"context"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/pkg/errors"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/elpinal/coco3/config"
	"github.com/elpinal/coco3/eval"
	"github.com/elpinal/coco3/gate"
	"github.com/elpinal/coco3/parser"

	"github.com/elpinal/coco3/extra"
	eparser "github.com/elpinal/coco3/extra/parser"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
)

type CLI struct {
	In  io.Reader
	Out io.Writer
	Err io.Writer

	*config.Config

	db *sqlx.DB

	execute1 func([]byte) (action, error)
}

func (c *CLI) init() {
	if c.Out == nil {
		c.Out = ioutil.Discard
	}
	if c.Err == nil {
		c.Err = ioutil.Discard
	}
	if c.Config == nil {
		c.Config = &config.Config{}
	}
	c.Config.Init()
}

func (c *CLI) Run(args []string) int {
	c.init()

	f := flag.NewFlagSet("coco3", flag.ContinueOnError)
	f.SetOutput(c.Err)
	f.Usage = func() {
		c.Err.Write([]byte("coco3 is a shell.\n"))
		c.Err.Write([]byte("Usage:\n"))
		f.PrintDefaults()
	}

	flagC := f.String("c", "", "take first argument as a command to execute")
	flagE := f.Bool("extra", c.Config.Extra, "switch to extra mode")
	if err := f.Parse(args); err != nil {
		return 2
	}
	return c.run(f.Args(), flagC, flagE)
}

func (c *CLI) run(args []string, flagC *string, flagE *bool) int {
	// Aliases, only available for non-extra mode.
	for _, alias := range c.Config.Alias {
		eval.DefAlias(alias[0], alias[1])
	}

	// Prepare environment.
	for k, v := range c.Config.Env {
		err := os.Setenv(k, v)
		if err != nil {
			c.errorln(err)
			return 1
		}
	}

	setpath(c.Config.Paths)

	if *flagE {
		// If -extra flag is on, enable extra mode on any command executions.
		c.execute1 = c.executeExtra
	} else {
		c.execute1 = c.execute
	}

	if len(c.Config.StartUpCommand) > 0 {
		a, err := c.execute1(c.Config.StartUpCommand)
		if err != nil {
			c.printExecError(err)
			return 1
		}
		if e, ok := a.(exit); ok {
			return e.code
		}
	}

	switch {
	case *flagC != "":
		// The -c flag.
		return c.fromArg(*flagC)
	case len(args) > 0:
		// Execute files.
		return c.executeFiles(args)
	default:
		// Interactive mode.
		return c.runInteractiveMode()
	}
}

func (c *CLI) fromArg(program string) int {
	a, err := c.execute1([]byte(program))
	if err != nil {
		c.printExecError(err)
		return 1
	}
	if e, ok := a.(exit); ok {
		return e.code
	}
	return 0
}

func (c *CLI) executeFiles(args []string) int {
	a, err := c.runFiles(args)
	if err != nil {
		c.printExecError(err)
		return 1
	}
	if e, ok := a.(exit); ok {
		return e.code
	}
	return 0
}

func (c *CLI) runInteractiveMode() int {
	// Inherit history.
	histRunes, err := c.getHistory(c.Config.HistFile)
	if err != nil {
		c.errorln(err)
		return 1
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	g := gate.NewContext(ctx, c.Config, c.In, c.Out, c.Err, histRunes)
	for {
		a, err := c.interact(g)
		if err != nil {
			c.printExecError(err)
		}
		if e, ok := a.(exit); ok {
			return e.code
		}
	}
}

func (c *CLI) errorf(s string, a ...interface{}) {
	fmt.Fprintf(c.Err, s, a...)
}

func (c *CLI) errorln(s ...interface{}) {
	fmt.Fprintln(c.Err, s...)
}

func (c *CLI) errorp(s ...interface{}) {
	fmt.Fprint(c.Err, s...)
}

func (c *CLI) getHistory(filename string) ([][]rune, error) {
	db, err := sqlx.Connect("sqlite3", filename)
	if err != nil {
		return nil, errors.Wrap(err, "connecting history file")
	}
	_, err = db.Exec(schema)
	if err != nil {
		return nil, errors.Wrap(err, "initializing history file")
	}
	var history []string
	err = db.Select(&history, "select line from command_info")
	if err != nil {
		return nil, errors.Wrap(err, "restoring history")
	}
	// TODO: Is this way proper?
	c.db = db
	return sanitizeHistory(history), nil
}

func (c *CLI) printExecError(err error) {
	if pe, ok := err.(*eparser.ParseError); ok {
		c.errorln(pe.Verbose())
	} else {
		c.errorln(err)
	}
}

// setpath sets the PATH environment variable.
func setpath(args []string) {
	if len(args) == 0 {
		return
	}
	paths := filepath.SplitList(os.Getenv("PATH"))
	var newPaths []string
	for _, path := range paths {
		if contains(args, path) {
			continue
		}
		newPaths = append(newPaths, path)
	}
	newPaths = append(args, newPaths...)
	os.Setenv("PATH", strings.Join(newPaths, string(filepath.ListSeparator)))
}

func contains(xs []string, s string) bool {
	for _, x := range xs {
		if x == s {
			return true
		}
	}
	return false
}

func sanitizeHistory(history []string) [][]rune {
	histRunes := make([][]rune, 0, len(history))
	for _, line := range history {
		if line == "" {
			continue
		}
		l := len(histRunes)
		s := []rune(line)
		if l > 0 && compareRunes(histRunes[l-1], s) {
			continue
		}
		histRunes = append(histRunes, s)
	}
	return histRunes
}

func compareRunes(r1, r2 []rune) bool {
	if len(r1) != len(r2) {
		return false
	}
	for i, r := range r1 {
		if r2[i] != r {
			return false
		}
	}
	return true
}

func (c *CLI) interact(g gate.Gate) (action, error) {
	r, end, err := c.read(g)
	if err != nil {
		return nil, err
	}
	if end {
		return exitSuccess, nil
	}
	ch := c.writeHistory(r)
	a, err := c.execute1([]byte(string(r)))
	if err != nil {
		return a, err
	}
	return a, <-ch
}

func (c *CLI) read(g gate.Gate) ([]rune, bool, error) {
	defer c.Out.Write([]byte{'\n'})
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		return nil, false, err
	}
	defer func() {
		if err := terminal.Restore(0, oldState); err != nil {
			c.errorln(err)
		}
	}()
	r, end, err := g.Read()
	if err != nil {
		return nil, false, err
	}
	return r, end, nil
}

func (c *CLI) writeHistory(r []rune) <-chan error {
	startTime := time.Now()
	ch := make(chan error)
	go func() {
		_, err := c.db.Exec("insert into command_info (time, line) values ($1, $2)", startTime, string(r))
		if err != nil {
			ch <- errors.Wrap(err, "saving history")
			return
		}
		ch <- nil
	}()
	return ch
}

const schema = `
create table if not exists command_info (
    time datetime,
    line text
)`

func (c *CLI) execute(b []byte) (action, error) {
	f, err := parser.ParseSrc(b)
	if err != nil {
		return nil, err
	}
	e := eval.New(c.In, c.Out, c.Err, c.db)
	err = e.Eval(f.Lines)
	select {
	case code := <-e.ExitCh:
		return exit{code}, nil
	default:
	}
	return nil, err
}

func (c *CLI) executeExtra(b []byte) (action, error) {
	cmd, err := eparser.Parse(b)
	if err != nil {
		return nil, err
	}
	e := extra.New(extra.Option{DB: c.db})
	err = e.Eval(cmd)
	if err == nil {
		return nil, nil
	}
	if pe, ok := err.(*eparser.ParseError); ok {
		pe.Src = string(b)
	}
	return nil, err
}

func (c *CLI) runFiles(files []string) (action, error) {
	for _, file := range files {
		b, err := ioutil.ReadFile(file)
		if err != nil {
			return nil, err
		}
		a, err := c.execute1(b)
		if err != nil {
			return nil, err
		}
		if a != nil {
			return a, nil
		}
	}
	return nil, nil
}

type action interface {
	act()
}

type exit struct {
	code int
}

func (e exit) act() {}

var exitSuccess = exit{0}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
)

const commandMessage = "Specify: n-clients n-requests ip:port n-replicas[this, just for logging]"

const requestBody = "BODY"

var (
	wg sync.WaitGroup

	mutex  sync.Mutex
	file   *os.File
	writer *bufio.Writer

	nClients, nRequests, nReplicas int

	targetIP string
)

func main() {

	var err error

	if len(os.Args) < 5 {
		fmt.Println(commandMessage)
		os.Exit(1)
	}

	nClients, err = strconv.Atoi(os.Args[1])

	if err != nil {
		fmt.Println(commandMessage, ": ", err.Error())
		os.Exit(1)
	}

	nRequests, err = strconv.Atoi(os.Args[2])

	if err != nil {
		fmt.Println(commandMessage, ": ", err.Error())
		os.Exit(1)
	}

	targetIP = "http://" + os.Args[3]

	nReplicas := os.Args[4]

	file, err = os.Create(fmt.Sprintf("raft_test_c%dreq%drep%s.txt", nClients, nRequests, nReplicas))

	if err != nil {
		fmt.Println("Can't create file")
		os.Exit(1)
	}

	writer = bufio.NewWriter(file)

	wg.Add(nClients)

	for c := 0; c < nClients; c++ {
		go client(c)
	}

	wg.Wait()
	file.Sync()
}

func client(clientID int) {
	var t0, t1 time.Time

	for r := 0; r < nRequests; r++ {

		requestID := 1000*clientID + r

		target := fmt.Sprintf("%s/command/%d/%s", targetIP, requestID, requestBody)

		t0 = time.Now()

		resp, err := http.Get(target)

		if err != nil {
			go writeToFile(fmt.Sprintf("Error on HTTP/GET! Client: %d, Request %d", clientID, requestID))
			continue
		}

		defer resp.Body.Close()

		if resp.StatusCode == 299 || resp.StatusCode == 200 { // request accepted
			t1 = time.Now()

			diff := t1.Sub(t0).Nanoseconds()

			// client;request;time;requestBody
			go writeToFile(fmt.Sprintf("%d;%d;%d;%s", clientID, requestID, diff, requestBody))

		} else {
			go writeToFile(fmt.Sprintf("Error on command! Status code: %d Client: %d Request %d", resp.StatusCode, clientID, requestID))
		}

	}

	wg.Done()
}

func writeToFile(s string) {
	mutex.Lock()

	_, err := writer.WriteString(s + "\n")

	writer.Flush()

	fmt.Println(s)

	mutex.Unlock()

	if err != nil {
		os.Exit(1)
	}

}
<commit_msg>Added milliseconds time to log<commit_after>package main

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
)

const commandMessage = "Specify: n-clients n-requests ip:port n-replicas[this, just for logging]"

const requestBody = "BODY"

var (
	wg sync.WaitGroup

	mutex  sync.Mutex
	file   *os.File
	writer *bufio.Writer

	nClients, nRequests, nReplicas int

	targetIP string
)

func main() {

	var err error

	if len(os.Args) < 5 {
		fmt.Println(commandMessage)
		os.Exit(1)
	}

	nClients, err = strconv.Atoi(os.Args[1])

	if err != nil {
		fmt.Println(commandMessage, ": ", err.Error())
		os.Exit(1)
	}

	nRequests, err = strconv.Atoi(os.Args[2])

	if err != nil {
		fmt.Println(commandMessage, ": ", err.Error())
		os.Exit(1)
	}

	targetIP = "http://" + os.Args[3]

	nReplicas := os.Args[4]

	file, err = os.Create(fmt.Sprintf("raft_test_c%dreq%drep%s.txt", nClients, nRequests, nReplicas))

	if err != nil {
		fmt.Println("Can't create file")
		os.Exit(1)
	}

	writer = bufio.NewWriter(file)

	wg.Add(nClients)

	for c := 1; c <= nClients; c++ {
		go client(c)
	}

	wg.Wait()
	file.Sync()
}

func client(clientID int) {
	var t0, t1 time.Time

	for r := 0; r < nRequests; r++ {

		requestID := 1000*clientID + r

		target := fmt.Sprintf("%s/command/%d/%s", targetIP, requestID, requestBody)

		t0 = time.Now()

		resp, err := http.Get(target)

		if err != nil {
			go writeToFile(fmt.Sprintf("Error on HTTP/GET! Client: %d, Request %d", clientID, requestID))
			continue
		}

		defer resp.Body.Close()

		if resp.StatusCode == 299 || resp.StatusCode == 200 { // request accepted
			t1 = time.Now()

			diff := t1.Sub(t0).Nanoseconds()

			// client;request;time(ns);time(ms);requestBody
			go writeToFile(fmt.Sprintf("%d;%d;%d;%s", clientID, requestID, diff, int64(diff/1000000), requestBody))

		} else {
			go writeToFile(fmt.Sprintf("Error on command! Status code: %d Client: %d Request %d", resp.StatusCode, clientID, requestID))
		}

	}

	wg.Done()
}

func writeToFile(s string) {
	mutex.Lock()

	_, err := writer.WriteString(s + "\n")

	writer.Flush()

	fmt.Println(s)

	mutex.Unlock()

	if err != nil {
		os.Exit(1)
	}

}
<|endoftext|>


<commit_before>// Copyright 2013-present Greg Hurrell. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"syscall"
)

type Options struct {
	Address    string
	Config     string
	Logfile    string
	Executable string
	Flags      string
	Port       int
}

var version = "unknown"

var config Options   // Options read from disk.
var defaults Options // Default options.
var flags Options    // Options set via commandline flags.
var settings Options // Result of merging: flags > config > defaults.
var showHelp bool
var showVersion bool

func printVersion() {
	fmt.Fprintf(os.Stderr, "clipper version: %s (%s)\n", version, runtime.GOOS)
}

func initFlags() {
	const (
		flagsUsage      = "arguments passed to clipboard executable"
		configFileUsage = "path to (JSON) config file"
		executableUsage = "program called to write to clipboard"
		helpUsage       = "show usage information"
		listenAddrUsage = "address to bind to (default loopback interface)"
		listenPortUsage = "port to listen on"
		logFileUsage    = "path to logfile"
		versionUsage    = "show version information"
	)

	flag.BoolVar(&showHelp, "h", false, helpUsage)
	flag.BoolVar(&showHelp, "help", false, helpUsage)
	flag.IntVar(&flags.Port, "p", defaults.Port, listenPortUsage)
	flag.IntVar(&flags.Port, "port", defaults.Port, listenPortUsage)
	flag.StringVar(&flags.Address, "a", defaults.Address, listenAddrUsage)
	flag.StringVar(&flags.Address, "address", defaults.Address, listenAddrUsage)
	flag.StringVar(&flags.Config, "c", defaults.Config, configFileUsage)
	flag.StringVar(&flags.Config, "config", defaults.Config, configFileUsage)
	flag.StringVar(&flags.Executable, "e", defaults.Executable, executableUsage)
	flag.StringVar(&flags.Executable, "executable", defaults.Executable, executableUsage)
	flag.StringVar(&flags.Flags, "f", defaults.Flags, flagsUsage)
	flag.StringVar(&flags.Flags, "flags", defaults.Flags, flagsUsage)
	flag.StringVar(&flags.Logfile, "l", defaults.Logfile, logFileUsage)
	flag.StringVar(&flags.Logfile, "logfile", defaults.Logfile, logFileUsage)
	flag.BoolVar(&showVersion, "v", false, versionUsage)
	flag.BoolVar(&showVersion, "version", false, versionUsage)
}

func setDefaults() {
	defaults.Address = "" // IPv4/IPv6 loopback.
	defaults.Port = 8377

	if runtime.GOOS == "linux" {
		defaults.Config = "~/.config/clipper/clipper.json"
		defaults.Logfile = "~/.config/clipper/logs/clipper.log"
		defaults.Executable = "xclip"
		defaults.Flags = "-selection clipboard"
	} else {
		defaults.Config = "~/.clipper.json"
		defaults.Logfile = "~/Library/Logs/com.wincent.clipper.log"
		defaults.Executable = "pbcopy"
		defaults.Flags = ""
	}
}

func mergeSettings() {
	// Detect which flags were passed in explicitly, and set them immediately.
	// This is used below to determine response to a missing config file.
	visitor := func(f *flag.Flag) {
		if f.Name == "address" || f.Name == "a" {
			settings.Address = flags.Address
		} else if f.Name == "config" || f.Name == "c" {
			settings.Config = flags.Config
		} else if f.Name == "executable" || f.Name == "e" {
			settings.Executable = flags.Executable
		} else if f.Name == "flags" || f.Name == "f" {
			settings.Flags = flags.Flags
		} else if f.Name == "port" || f.Name == "p" {
			settings.Port = flags.Port
		} else if f.Name == "logfile" || f.Name == "l" {
			settings.Logfile = flags.Logfile
		}
	}
	flag.Visit(visitor)

	expandedPath := expandPath(flags.Config)

	if configData, err := ioutil.ReadFile(expandedPath); err != nil {
		if settings.Config != "" {
			// User explicitly asked for a config file and it wasn't there; fail hard.
			log.Fatal(err)
		} else {
			// Default config file missing; just warn.
			log.Print(err)
		}
	} else {
		if err = json.Unmarshal(configData, &config); err != nil {
			log.Fatal(err)
		}
	}

	// Final merge into settings object.
	if settings.Address == "" {
		if config.Address != "" {
			settings.Address = config.Address
		} else {
			settings.Address = defaults.Address
		}
	}
	if settings.Logfile == "" {
		if config.Logfile != "" {
			settings.Logfile = config.Logfile
		} else {
			settings.Logfile = defaults.Logfile
		}
	}
	if settings.Port != 0 || config.Port != 0 {
		if isPath(settings.Address) {
			log.Print("--port option ignored when listening on UNIX domain socket")
		}
	}
	if settings.Port == 0 {
		if config.Port != 0 {
			settings.Port = config.Port
		} else {
			settings.Port = defaults.Port
		}
	}
	if settings.Executable == "" {
		if config.Executable != "" {
			settings.Executable = config.Executable
		} else {
			settings.Executable = defaults.Executable
		}
	}
	if settings.Flags == "" {
		if config.Flags != "" {
			settings.Flags = config.Flags
		} else {
			settings.Flags = defaults.Flags
		}
	}
}

func main() {
	syscall.Umask(0077)
	// Set this up before we even know where our logfile is, in case we have to
	// bail early and print something to stderr.
	log.SetPrefix("clipper: ")
	// Set default values per GOOS.
	setDefaults()
	// Setup flags subsystem.
	initFlags()

	flag.Parse()
	if flag.NArg() != 0 {
		// Additional command-line options not supported.
		flag.Usage()
		os.Exit(1)
	}
	if showHelp {
		printVersion()
		flag.Usage()
		os.Exit(0)
	}
	if showVersion {
		printVersion()
		os.Exit(0)
	}

	// Merge flags -> config -> defaults.
	mergeSettings()

	expandedPath := expandPath(settings.Logfile)
	outfile, err := os.OpenFile(expandedPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatal(err)
	}
	defer outfile.Close()
	log.SetOutput(outfile)

	if _, err := exec.LookPath(settings.Executable); err != nil {
		log.Fatal(err)
	}

	var addr string
	var listeners []net.Listener
	if isPath(settings.Address) {
		addr = expandPath(settings.Address)
	} else {
		addr = settings.Address
	}
	if strings.HasPrefix(addr, "/") {
		log.Print("Starting UNIX domain socket server at ", addr)
		listeners = append(listeners, listen("unix", addr, -1))
	} else {
		if addr == "" {
			log.Print("Starting TCP server on loopback interface")
			listeners = append(listeners, listen("tcp4", "127.0.0.1", settings.Port))
			listeners = append(listeners, listen("tcp6", "[::1]", settings.Port))
		} else {
			log.Print("Starting TCP server on ", addr)
			listeners = append(listeners, listen("tcp", settings.Address, settings.Port))
		}
	}

	var listenerCount = 0
	for i := range listeners {
		if listeners[i] != nil {
			listenerCount++
		}
	}
	if listenerCount == 0 {
		log.Fatal("Failed to establish a listener")
	}

	for i := range listeners {
		if listeners[i] != nil {
			defer listeners[i].Close()
			go func(listener net.Listener) {
				for {
					conn, err := listener.Accept()
					if err != nil {
						log.Print(err)
						return
					}

					go handleConnection(conn)
				}
			}(listeners[i])
		}
	}

	// Need to catch signals in order for `defer`-ed clean-up items to run.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
	sig := <-c
	log.Print("Got signal ", sig)
}

func listen(listenType string, addr string, port int) net.Listener {
	if port >= 0 {
		addr = fmt.Sprintf("%s:%d", addr, port)
	}
	listener, err := net.Listen(listenType, addr)
	if err != nil {
		log.Print(err)
	}
	return listener
}

// Returns true for things which look like paths (start with "~", "." or "/").
func isPath(path string) bool {
	return strings.HasPrefix(path, "~") ||
		strings.HasPrefix(path, ".") ||
		strings.HasPrefix(path, "/")
}

func expandPath(path string) string {
	expanded := pathByExpandingTildeInPath(path)
	result, err := filepath.Abs(expanded)
	if err != nil {
		log.Fatal(err)
	}
	return result
}

func pathByExpandingTildeInPath(path string) string {
	if strings.HasPrefix(path, "~") {
		user, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}
		path = user.HomeDir + path[1:]
	}
	return path
}

func handleConnection(conn net.Conn) {
	defer log.Print("Connection closed")
	defer conn.Close()

	var args []string
	if settings.Flags != "" {
		whitespace := regexp.MustCompile("\\s+")
		args = whitespace.Split(strings.TrimSpace(settings.Flags), -1)
	}
	cmd := exec.Command(settings.Executable, args...)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] pipe init: %v\n", err)
		return
	}

	if err = cmd.Start(); err != nil {
		log.Printf("[ERROR] process start: %v\n", err)
		return
	}

	if copied, err := io.Copy(stdin, conn); err != nil {
		log.Printf("[ERROR] pipe copy: %v\n", err)
	} else {
		log.Print("Echoed ", copied, " bytes")
	}
	stdin.Close()

	if err = cmd.Wait(); err != nil {
		log.Printf("[ERROR] wait: %v\n", err)
	}
}
<commit_msg>Add a filter function to keep main body cleaner<commit_after>// Copyright 2013-present Greg Hurrell. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"syscall"
)

type Options struct {
	Address    string
	Config     string
	Logfile    string
	Executable string
	Flags      string
	Port       int
}

var version = "unknown"

var config Options   // Options read from disk.
var defaults Options // Default options.
var flags Options    // Options set via commandline flags.
var settings Options // Result of merging: flags > config > defaults.
var showHelp bool
var showVersion bool

func printVersion() {
	fmt.Fprintf(os.Stderr, "clipper version: %s (%s)\n", version, runtime.GOOS)
}

func initFlags() {
	const (
		flagsUsage      = "arguments passed to clipboard executable"
		configFileUsage = "path to (JSON) config file"
		executableUsage = "program called to write to clipboard"
		helpUsage       = "show usage information"
		listenAddrUsage = "address to bind to (default loopback interface)"
		listenPortUsage = "port to listen on"
		logFileUsage    = "path to logfile"
		versionUsage    = "show version information"
	)

	flag.BoolVar(&showHelp, "h", false, helpUsage)
	flag.BoolVar(&showHelp, "help", false, helpUsage)
	flag.IntVar(&flags.Port, "p", defaults.Port, listenPortUsage)
	flag.IntVar(&flags.Port, "port", defaults.Port, listenPortUsage)
	flag.StringVar(&flags.Address, "a", defaults.Address, listenAddrUsage)
	flag.StringVar(&flags.Address, "address", defaults.Address, listenAddrUsage)
	flag.StringVar(&flags.Config, "c", defaults.Config, configFileUsage)
	flag.StringVar(&flags.Config, "config", defaults.Config, configFileUsage)
	flag.StringVar(&flags.Executable, "e", defaults.Executable, executableUsage)
	flag.StringVar(&flags.Executable, "executable", defaults.Executable, executableUsage)
	flag.StringVar(&flags.Flags, "f", defaults.Flags, flagsUsage)
	flag.StringVar(&flags.Flags, "flags", defaults.Flags, flagsUsage)
	flag.StringVar(&flags.Logfile, "l", defaults.Logfile, logFileUsage)
	flag.StringVar(&flags.Logfile, "logfile", defaults.Logfile, logFileUsage)
	flag.BoolVar(&showVersion, "v", false, versionUsage)
	flag.BoolVar(&showVersion, "version", false, versionUsage)
}

func setDefaults() {
	defaults.Address = "" // IPv4/IPv6 loopback.
	defaults.Port = 8377

	if runtime.GOOS == "linux" {
		defaults.Config = "~/.config/clipper/clipper.json"
		defaults.Logfile = "~/.config/clipper/logs/clipper.log"
		defaults.Executable = "xclip"
		defaults.Flags = "-selection clipboard"
	} else {
		defaults.Config = "~/.clipper.json"
		defaults.Logfile = "~/Library/Logs/com.wincent.clipper.log"
		defaults.Executable = "pbcopy"
		defaults.Flags = ""
	}
}

func mergeSettings() {
	// Detect which flags were passed in explicitly, and set them immediately.
	// This is used below to determine response to a missing config file.
	visitor := func(f *flag.Flag) {
		if f.Name == "address" || f.Name == "a" {
			settings.Address = flags.Address
		} else if f.Name == "config" || f.Name == "c" {
			settings.Config = flags.Config
		} else if f.Name == "executable" || f.Name == "e" {
			settings.Executable = flags.Executable
		} else if f.Name == "flags" || f.Name == "f" {
			settings.Flags = flags.Flags
		} else if f.Name == "port" || f.Name == "p" {
			settings.Port = flags.Port
		} else if f.Name == "logfile" || f.Name == "l" {
			settings.Logfile = flags.Logfile
		}
	}
	flag.Visit(visitor)

	expandedPath := expandPath(flags.Config)

	if configData, err := ioutil.ReadFile(expandedPath); err != nil {
		if settings.Config != "" {
			// User explicitly asked for a config file and it wasn't there; fail hard.
			log.Fatal(err)
		} else {
			// Default config file missing; just warn.
			log.Print(err)
		}
	} else {
		if err = json.Unmarshal(configData, &config); err != nil {
			log.Fatal(err)
		}
	}

	// Final merge into settings object.
	if settings.Address == "" {
		if config.Address != "" {
			settings.Address = config.Address
		} else {
			settings.Address = defaults.Address
		}
	}
	if settings.Logfile == "" {
		if config.Logfile != "" {
			settings.Logfile = config.Logfile
		} else {
			settings.Logfile = defaults.Logfile
		}
	}
	if settings.Port != 0 || config.Port != 0 {
		if isPath(settings.Address) {
			log.Print("--port option ignored when listening on UNIX domain socket")
		}
	}
	if settings.Port == 0 {
		if config.Port != 0 {
			settings.Port = config.Port
		} else {
			settings.Port = defaults.Port
		}
	}
	if settings.Executable == "" {
		if config.Executable != "" {
			settings.Executable = config.Executable
		} else {
			settings.Executable = defaults.Executable
		}
	}
	if settings.Flags == "" {
		if config.Flags != "" {
			settings.Flags = config.Flags
		} else {
			settings.Flags = defaults.Flags
		}
	}
}

func main() {
	syscall.Umask(0077)
	// Set this up before we even know where our logfile is, in case we have to
	// bail early and print something to stderr.
	log.SetPrefix("clipper: ")
	// Set default values per GOOS.
	setDefaults()
	// Setup flags subsystem.
	initFlags()

	flag.Parse()
	if flag.NArg() != 0 {
		// Additional command-line options not supported.
		flag.Usage()
		os.Exit(1)
	}
	if showHelp {
		printVersion()
		flag.Usage()
		os.Exit(0)
	}
	if showVersion {
		printVersion()
		os.Exit(0)
	}

	// Merge flags -> config -> defaults.
	mergeSettings()

	expandedPath := expandPath(settings.Logfile)
	outfile, err := os.OpenFile(expandedPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatal(err)
	}
	defer outfile.Close()
	log.SetOutput(outfile)

	if _, err := exec.LookPath(settings.Executable); err != nil {
		log.Fatal(err)
	}

	var addr string
	var listeners []net.Listener
	if isPath(settings.Address) {
		addr = expandPath(settings.Address)
	} else {
		addr = settings.Address
	}
	if strings.HasPrefix(addr, "/") {
		log.Print("Starting UNIX domain socket server at ", addr)
		listeners = append(listeners, listen("unix", addr, -1))
	} else {
		if addr == "" {
			log.Print("Starting TCP server on loopback interface")
			listeners = append(listeners, listen("tcp4", "127.0.0.1", settings.Port))
			listeners = append(listeners, listen("tcp6", "[::1]", settings.Port))
		} else {
			log.Print("Starting TCP server on ", addr)
			listeners = append(listeners, listen("tcp", settings.Address, settings.Port))
		}
	}

	listeners = filter(&listeners, func(l net.Listener) bool {
		return l != nil
	})
	if len(listeners) == 0 {
		log.Fatal("Failed to establish a listener")
	}

	for i := range listeners {
		if listeners[i] != nil {
			defer listeners[i].Close()
			go func(listener net.Listener) {
				for {
					conn, err := listener.Accept()
					if err != nil {
						log.Print(err)
						return
					}

					go handleConnection(conn)
				}
			}(listeners[i])
		}
	}

	// Need to catch signals in order for `defer`-ed clean-up items to run.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
	sig := <-c
	log.Print("Got signal ", sig)
}

func listen(listenType string, addr string, port int) net.Listener {
	if port >= 0 {
		addr = fmt.Sprintf("%s:%d", addr, port)
	}
	listener, err := net.Listen(listenType, addr)
	if err != nil {
		log.Print(err)
	}
	return listener
}

func filter(ls *[]net.Listener, fn func(net.Listener) bool) []net.Listener {
	var out []net.Listener
	for i := range *ls {
		if fn((*ls)[i]) {
			out = append(out, (*ls)[i])
		}
	}
	return out
}

// Returns true for things which look like paths (start with "~", "." or "/").
func isPath(path string) bool {
	return strings.HasPrefix(path, "~") ||
		strings.HasPrefix(path, ".") ||
		strings.HasPrefix(path, "/")
}

func expandPath(path string) string {
	expanded := pathByExpandingTildeInPath(path)
	result, err := filepath.Abs(expanded)
	if err != nil {
		log.Fatal(err)
	}
	return result
}

func pathByExpandingTildeInPath(path string) string {
	if strings.HasPrefix(path, "~") {
		user, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}
		path = user.HomeDir + path[1:]
	}
	return path
}

func handleConnection(conn net.Conn) {
	defer log.Print("Connection closed")
	defer conn.Close()

	var args []string
	if settings.Flags != "" {
		whitespace := regexp.MustCompile("\\s+")
		args = whitespace.Split(strings.TrimSpace(settings.Flags), -1)
	}
	cmd := exec.Command(settings.Executable, args...)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] pipe init: %v\n", err)
		return
	}

	if err = cmd.Start(); err != nil {
		log.Printf("[ERROR] process start: %v\n", err)
		return
	}

	if copied, err := io.Copy(stdin, conn); err != nil {
		log.Printf("[ERROR] pipe copy: %v\n", err)
	} else {
		log.Print("Echoed ", copied, " bytes")
	}
	stdin.Close()

	if err = cmd.Wait(); err != nil {
		log.Printf("[ERROR] wait: %v\n", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"os"

	"github.com/goph/fw"
	"github.com/goph/healthz"
	"github.com/kelseyhightower/envconfig"
	"github.com/opentracing/opentracing-go"
)

// application collects all dependencies and exposes them in a single service locator.
//
// Although service location is a common anti-pattern, it's only purpose here is bootstrapping
// certain parts of the application. DI would be a more appropriate solution, but even there
// bootstrapping requires a single resolution of all dependencies.
type application struct {
	*fw.Application
	config          *configuration
	healthCollector healthz.Collector
	tracer          opentracing.Tracer
}

// provider is a mutator for an application registering it's dependencies.
type provider func(app *application) error

// newApplication initializes a new application using the passed providers.
func newApplication(providers ...provider) (*application, error) {
	app := new(application)

	for _, p := range providers {
		err := p(app)
		if err != nil {
			// Returning app, so that already initialized resources can still be closed.
			return app, err
		}
	}

	return app, nil
}

// configProvider registers configuration in the application.
func configProvider(app *application) error {
	config := new(configuration)

	// Load configuration from environment
	err := envconfig.Process("", config)
	if err != nil {
		return err
	}

	// Load configuration from flags
	flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	config.flags(flags)
	flags.Parse(os.Args[1:])

	app.config = config

	return nil
}

// healthProvider registers the health collector in the application.
func healthProvider(app *application) error {
	app.healthCollector = healthz.Collector{}

	return nil
}
<commit_msg>Remove unused tracer field<commit_after>package main

import (
	"flag"
	"os"

	"github.com/goph/fw"
	"github.com/goph/healthz"
	"github.com/kelseyhightower/envconfig"
)

// application collects all dependencies and exposes them in a single service locator.
//
// Although service location is a common anti-pattern, it's only purpose here is bootstrapping
// certain parts of the application. DI would be a more appropriate solution, but even there
// bootstrapping requires a single resolution of all dependencies.
type application struct {
	*fw.Application
	config          *configuration
	healthCollector healthz.Collector
}

// provider is a mutator for an application registering it's dependencies.
type provider func(app *application) error

// newApplication initializes a new application using the passed providers.
func newApplication(providers ...provider) (*application, error) {
	app := new(application)

	for _, p := range providers {
		err := p(app)
		if err != nil {
			// Returning app, so that already initialized resources can still be closed.
			return app, err
		}
	}

	return app, nil
}

// configProvider registers configuration in the application.
func configProvider(app *application) error {
	config := new(configuration)

	// Load configuration from environment
	err := envconfig.Process("", config)
	if err != nil {
		return err
	}

	// Load configuration from flags
	flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	config.flags(flags)
	flags.Parse(os.Args[1:])

	app.config = config

	return nil
}

// healthProvider registers the health collector in the application.
func healthProvider(app *application) error {
	app.healthCollector = healthz.Collector{}

	return nil
}
<|endoftext|>