<commit_before>package main

import (
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"net/http"
	"io"

	getopt "github.com/pborman/getopt"
)

var exclude = getopt.ListLong("exclude", 'x', "", "glob patterns to exclude")
var help = getopt.BoolLong("help", 'h', "", "print this help")

func main() {
	getopt.SetParameters("<root dir> <bucket name>")
	getopt.Parse()
	if *help {
		getopt.PrintUsage(os.Stdout)
		return
	}

	args := getopt.Args()
	if len(args) != 2 {
		getopt.PrintUsage(os.Stderr)
		os.Exit(1)
	}

	rootDir := args[0]
	bucketName := args[1]

	resourcesMap := map[string]interface{}{}
	result := map[string]interface{}{
		"resource": map[string]interface{}{
			"aws_s3_bucket_object": resourcesMap,
		},
	}

	filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, err)
			// Skip stuff we can't read.
			return nil
		}

		relPath, err := filepath.Rel(rootDir, path)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed make %s relative: %s\n", path, err)
			return nil
		}

		path, err = filepath.EvalSymlinks(path)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to resolve symlink %s: %s\n", path, err)
			return nil
		}

		if info.IsDir() {
			// Don't need to create directories since they are implied
			// by the files within.
			return nil
		}

		for _, pattern := range *exclude {
			var toMatch []string
			if strings.ContainsRune(pattern, filepath.Separator) {
				toMatch = append(toMatch, relPath)
			} else {
				// If the pattern does not include a path separator
				// then we apply it to all segments of the path
				// individually.
				toMatch = strings.Split(relPath, string(filepath.Separator))
			}

			for _, matchPath := range toMatch {
				matched, _ := filepath.Match(pattern, matchPath)
				if matched {
					return nil
				}
			}
		}

		// We use the initial bytes of the file to infer a MIME type
		file, err := os.Open(path)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error opening %s: %s\n", path, err)
			return nil
		}
		hasher := sha1.New()
		fileBytes := make([]byte, 1024*1024)
		contentType := ""
		_, err = file.Read(fileBytes)
		// If we got back and error and it isn't the end of file then
		// skip it.  This does "something" with 0 length files.  It is
		// likely we should really be categorizing those based on file
		// extension.
		if err != nil && err != io.EOF {
			fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, err)
			return nil
		}
		if strings.HasSuffix(relPath, ".svg") {
			// If we start to need a set of overrides for DetectContentType
			// then we need to find a different way to do this.
			contentType = "image/svg+xml"
		} else {
			contentType = http.DetectContentType(fileBytes)
		}

		// Resource name is a hash of the path, so it should stay consistent
		// for a given file path as long as the relative path to the target
		// directory is always the same across runs.
		hasher.Write([]byte(relPath))
		resourceName := fmt.Sprintf("%x", hasher.Sum(nil))

		resourcesMap[resourceName] = map[string]interface{}{
			"bucket":       bucketName,
			"key":          relPath,
			"source":       path,
			"etag":         fmt.Sprintf("${md5(file(%q))}", path),
			"content_type": contentType,
		}

		return nil
	})

	encoder := json.NewEncoder(os.Stdout)
	encoder.Encode(result)
}
<commit_msg>Alphabetize our standard library imports<commit_after>package main

import (
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	getopt "github.com/pborman/getopt"
)

var exclude = getopt.ListLong("exclude", 'x', "", "glob patterns to exclude")
var help = getopt.BoolLong("help", 'h', "", "print this help")

func main() {
	getopt.SetParameters("<root dir> <bucket name>")
	getopt.Parse()
	if *help {
		getopt.PrintUsage(os.Stdout)
		return
	}

	args := getopt.Args()
	if len(args) != 2 {
		getopt.PrintUsage(os.Stderr)
		os.Exit(1)
	}

	rootDir := args[0]
	bucketName := args[1]

	resourcesMap := map[string]interface{}{}
	result := map[string]interface{}{
		"resource": map[string]interface{}{
			"aws_s3_bucket_object": resourcesMap,
		},
	}

	filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, err)
			// Skip stuff we can't read.
			return nil
		}

		relPath, err := filepath.Rel(rootDir, path)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed make %s relative: %s\n", path, err)
			return nil
		}

		path, err = filepath.EvalSymlinks(path)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to resolve symlink %s: %s\n", path, err)
			return nil
		}

		if info.IsDir() {
			// Don't need to create directories since they are implied
			// by the files within.
			return nil
		}

		for _, pattern := range *exclude {
			var toMatch []string
			if strings.ContainsRune(pattern, filepath.Separator) {
				toMatch = append(toMatch, relPath)
			} else {
				// If the pattern does not include a path separator
				// then we apply it to all segments of the path
				// individually.
				toMatch = strings.Split(relPath, string(filepath.Separator))
			}

			for _, matchPath := range toMatch {
				matched, _ := filepath.Match(pattern, matchPath)
				if matched {
					return nil
				}
			}
		}

		// We use the initial bytes of the file to infer a MIME type
		file, err := os.Open(path)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error opening %s: %s\n", path, err)
			return nil
		}
		hasher := sha1.New()
		fileBytes := make([]byte, 1024*1024)
		contentType := ""
		_, err = file.Read(fileBytes)
		// If we got back and error and it isn't the end of file then
		// skip it.  This does "something" with 0 length files.  It is
		// likely we should really be categorizing those based on file
		// extension.
		if err != nil && err != io.EOF {
			fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, err)
			return nil
		}
		if strings.HasSuffix(relPath, ".svg") {
			// If we start to need a set of overrides for DetectContentType
			// then we need to find a different way to do this.
			contentType = "image/svg+xml"
		} else {
			contentType = http.DetectContentType(fileBytes)
		}

		// Resource name is a hash of the path, so it should stay consistent
		// for a given file path as long as the relative path to the target
		// directory is always the same across runs.
		hasher.Write([]byte(relPath))
		resourceName := fmt.Sprintf("%x", hasher.Sum(nil))

		resourcesMap[resourceName] = map[string]interface{}{
			"bucket":       bucketName,
			"key":          relPath,
			"source":       path,
			"etag":         fmt.Sprintf("${md5(file(%q))}", path),
			"content_type": contentType,
		}

		return nil
	})

	encoder := json.NewEncoder(os.Stdout)
	encoder.Encode(result)
}
<|endoftext|>


<commit_before>/**
* Welcome to Beacon; an application for keeping teams in sync with breaking changes in their codebases.
* This application is built with Golang! The Maintainer (yours truly) has not written much Golang so
* Refactors, tips, and working things out together is greatly appreciated.
* Please refer to our github issues page for working on this project! https://github.com/the-heap/beacon/issues
*
*** ======== Useful links, Resources, etc: ============ ***
  * 🗯 Join our Slack group:              https://slackin-onxcmypksl.now.sh/
  * 🎒 Learn about the Heap:              http://theheap.us/page/about/
  * 🎩 New to Open source resoures:       https://theheap.us/page/resources/
  * 🐹 Golang tips:                       https://gobyexample.com/
  * 🎧 Cool Golang podcast:               http://gotime.fm/
*** =================================================== ***
*
*/

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
)

// ============================
// Types
// ============================

// BeaconLog represents the entire blob of json from the beacon file
type BeaconLog struct {
	Logs []Log
}

// Log is a single entry in the BeaconLog;
//
// _Example_: a user submits a breaking change to the database,
// in which they would fill out all the info in this struct.
type Log struct {
	ID      string
	Date    string
	Email   string
	Author  string
	Message string
}

type beaconConfig struct {
	Author string `json:"author"`
	Email  string `json:"email"`
}

// ============================
// Vars and Constants
// ============================

var beaconLogData BeaconLog

// ============================
// FUNCS
// ============================

func checkError(e error) {
	if e != nil {
		panic(e)
	}
}

// Print the beacon log to the terminal
func printLog(data BeaconLog) {
	for _, element := range data.Logs {
		fmt.Println("")
		fmt.Println("==========================================")
		fmt.Println("Date: ", element.Date)
		fmt.Println("Author: ", element.Author+" ("+element.Email+")") // I bet there's a nicer way to do this.
		fmt.Println("Message: ", element.Message)
		fmt.Println("==========================================")
	}
}

// loadConfig loads the .beaconrc file into the beaconConfig
func loadConfig(config *beaconConfig) error {
	configFile, err := ioutil.ReadFile("./.beaconrc")
	if err != nil {
		return err
	}

	return json.Unmarshal(configFile, &config)
}

// ============================
// MAIN!
// ============================
func main() {
	var config beaconConfig

	if err := loadConfig(&config); err != nil {
		log.Fatal(err)
	}

	// read JSON file from disk
	beaconLogFile, err := ioutil.ReadFile("./beacon_log.json")

	checkError(err)

	// unmarshal json and store it in the pointer to beaconLogData {?}
	// NOTE: figure out if you can use `checkError` here; don't yet understand golang's idiomatic errors handling.
	if err := json.Unmarshal(beaconLogFile, &beaconLogData); err != nil {
		panic(err)
	}

	// Print the beacon log!
	printLog(beaconLogData)
}
<commit_msg>add command line prompts for user input<commit_after>/**
* Welcome to Beacon; an application for keeping teams in sync with breaking changes in their codebases.
* This application is built with Golang! The Maintainer (yours truly) has not written much Golang so
* Refactors, tips, and working things out together is greatly appreciated.
* Please refer to our github issues page for working on this project! https://github.com/the-heap/beacon/issues
*
*** ======== Useful links, Resources, etc: ============ ***
  * 🗯 Join our Slack group:              https://slackin-onxcmypksl.now.sh/
  * 🎒 Learn about the Heap:              http://theheap.us/page/about/
  * 🎩 New to Open source resoures:       https://theheap.us/page/resources/
  * 🐹 Golang tips:                       https://gobyexample.com/
  * 🎧 Cool Golang podcast:               http://gotime.fm/
*** =================================================== ***
*
*/

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

// ============================
// Types
// ============================

// BeaconLog represents the entire blob of json from the beacon file
type BeaconLog struct {
	Logs []Log
}

// Log is a single entry in the BeaconLog;
//
// _Example_: a user submits a breaking change to the database,
// in which they would fill out all the info in this struct.
type Log struct {
	ID      string
	Date    string
	Email   string
	Author  string
	Message string
}

type beaconConfig struct {
	Author string `json:"author"`
	Email  string `json:"email"`
}

// ============================
// Vars and Constants
// ============================

var beaconLogData BeaconLog

// ============================
// FUNCS
// ============================

func checkError(e error) {
	if e != nil {
		panic(e)
	}
}

// Print the beacon log to the terminal
func printLog(data BeaconLog) {
	for _, element := range data.Logs {
		fmt.Println("")
		fmt.Println("==========================================")
		fmt.Println("Date: ", element.Date)
		fmt.Println("Author: ", element.Author+" ("+element.Email+")") // I bet there's a nicer way to do this.
		fmt.Println("Message: ", element.Message)
		fmt.Println("==========================================")
	}
}

// loadConfig loads the .beaconrc file into the beaconConfig
func loadConfig(config *beaconConfig) error {
	configFile, err := ioutil.ReadFile("./.beaconrc")
	if err != nil {
		return err
	}

	return json.Unmarshal(configFile, &config)
}

func prompt(question string) string {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print(question)
	message, _ := reader.ReadString('\n')

	return message
}

// ============================
// MAIN!
// ============================
func main() {
	var config beaconConfig

	if err := loadConfig(&config); err != nil {
		log.Fatal(err)
	}
	// parse command line arguments to determine action
	if len(os.Args) == 2 {
		switch os.Args[1] {
		case "add":
			log := Log{}
			log.Author = prompt("Enter author: ")
			log.Message = prompt("Enter message: ")

			fmt.Printf("%+v\n", log)
		default:
			os.Exit(1)
		}
	}

	// read JSON file from disk
	beaconLogFile, err := ioutil.ReadFile("./beacon_log.json")

	checkError(err)

	// unmarshal json and store it in the pointer to beaconLogData {?}
	// NOTE: figure out if you can use `checkError` here; don't yet understand golang's idiomatic errors handling.
	if err := json.Unmarshal(beaconLogFile, &beaconLogData); err != nil {
		panic(err)
	}

	// Print the beacon log!
	// printLog(beaconLogData)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
)

var (
	inFile  string
	outFile string
)

func init() {
	flag.StringVar(&inFile, "f", "", "The file to read from to generate parameters")
	flag.StringVar(&outFile, "o", "", "Optional: Specify a file name to write out parameters")
}

func main() {
	flag.Parse()

	if inFile == "" {
		fmt.Fprintln(os.Stderr, "Missing required argument -f")
	}

	m := make(map[string]interface{})
	data, err := ioutil.ReadFile(inFile)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}
	err = json.Unmarshal(data, &m)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(-1)
	}

	if _, ok := m["Parameters"]; !ok {
		fmt.Fprintln(os.Stderr, "Parameters not found in file")
		os.Exit(-1)
	}

	params := m["Parameters"].(map[string]interface{})

	keys := make([]string, 0)

	for k := range params {
		keys = append(keys, k)
	}

	sort.Strings(keys)

	s := "[\n"

	for i, k := range keys {
		s += fmt.Sprintf(`	{
		"ParameterKey": "%s",
		"ParameterValue": ""
	}`, k)
		if i != len(keys)-1 {
			s += `,
`
		}
	}

	s += "\n]"

	if outFile != "" {
		err = ioutil.WriteFile(outFile, []byte(s), 0644)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
	} else {
		fmt.Print(s)
	}

}
<commit_msg>adds options for formatting<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strings"
)

var (
	inFile          string
	outFile         string
	min             bool
	numIndentSpaces int
)

func init() {
	flag.StringVar(&inFile, "f", "", "The file to read from to generate parameters.")
	flag.StringVar(&outFile, "o", "", "Optional: Specify a file name to write out parameters.")
	flag.BoolVar(&min, "min", false, "If given, will generate minified output.")
	flag.IntVar(&numIndentSpaces, "spaces", 2, "The number of spaces used to indent the file if not generating minified output.")

}

func main() {
	flag.Parse()

	if inFile == "" {
		fmt.Fprintln(os.Stderr, "Missing required argument -f")
	}

	m := make(map[string]interface{})
	data, err := ioutil.ReadFile(inFile)
	if err != nil {
		exitError(err)
	}
	err = json.Unmarshal(data, &m)
	if err != nil {
		exitError(err)
	}

	if _, ok := m["Parameters"]; !ok {
		exitError(errors.New("Parameters not found in file."))
	}

	params := m["Parameters"].(map[string]interface{})

	pl := make(paramList, 0)

	for k := range params {
		p := parameter{ParameterKey: k}
		pl = append(pl, p)
	}

	sort.Sort(pl)

	data, err = marshal(pl)
	if err != nil {
		exitError(err)
	}
	if outFile != "" {
		err = ioutil.WriteFile(outFile, data, 0644)
		if err != nil {
			exitError(err)
		}
	} else {
		fmt.Print(string(data))
	}

}

func marshal(i interface{}) ([]byte, error) {
	if min {
		return json.Marshal(i)
	}
	return json.MarshalIndent(i, "", strings.Repeat(" ", numIndentSpaces))
}

type parameter struct {
	ParameterKey   string
	ParameterValue string
}

type paramList []parameter

func (p paramList) Less(i, j int) bool {
	return p[i].ParameterKey < p[j].ParameterKey
}

func (p paramList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func (p paramList) Len() int {
	return len(p)
}

func exitError(err error) {
	fmt.Fprintln(os.Stderr, err)
	os.Exit(-1)
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"os/signal"
	"sync"
	"syscall"
)

func main() {
	quit := make(chan struct{})

	wg := &sync.WaitGroup{}
	for _, pat := range os.Args[1:] {
		r, err := parseRule(pat)
		if err != nil {
			return
		}

		p := NewProxy(r.src, r.dst)
		wg.Add(1)
		go func() {
			defer wg.Done()
			p.Run(quit)
		}()
	}

	done := make(chan struct{})
	go func() {
		wg.Wait()
		done <- struct{}{}
	}()

	sigCh := make(chan os.Signal)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	loop := true
	for loop {
		select {
		case <-sigCh:
			close(quit)
			loop = false
		case <-done:
			loop = false
		}
	}

}<commit_msg>Tweak exit status<commit_after>package main

import (
	"os"
	"os/signal"
	"sync"
	"syscall"
)

func main() {
	st := _main()
	os.Exit(st)
}

func _main() int {
	quit := make(chan struct{})

	wg := &sync.WaitGroup{}
	for _, pat := range os.Args[1:] {
		r, err := parseRule(pat)
		if err != nil {
			return 1
		}

		p := NewProxy(r.src, r.dst)
		wg.Add(1)
		go func() {
			defer wg.Done()
			p.Run(quit)
		}()
	}

	done := make(chan struct{})
	go func() {
		wg.Wait()
		done <- struct{}{}
	}()

	sigCh := make(chan os.Signal)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	loop := true
	exitStatus := 0
	for loop {
		select {
		case <-sigCh:
			close(quit)
			loop = false
			exitStatus = 1
		case <-done:
			loop = false
		}
	}

	return exitStatus
}<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/codegangsta/cli"
	"github.com/fatih/color"
)

var version = "dev-build"

func readIn(lines chan string, tee bool) {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		lines <- scanner.Text()
		if tee {
			fmt.Println(scanner.Text())
		}
	}
	close(lines)
}

func writeTemp(lines chan string) string {
	tmp, err := ioutil.TempFile(os.TempDir(), "slackcat-")
	failOnError(err, "unable to create tmpfile", false)

	w := bufio.NewWriter(tmp)
	for line := range lines {
		fmt.Fprintln(w, line)
	}
	w.Flush()

	return tmp.Name()
}

func output(s string) {
	cyan := color.New(color.Bold).SprintFunc()
	fmt.Printf("%s %s\n", cyan("slackcat"), s)
}

func failOnError(err error, msg string, appendErr bool) {
	if err != nil {
		if appendErr {
			exitErr(fmt.Errorf("%s: %s", msg, err))
		} else {
			exitErr(fmt.Errorf("%s", msg))
		}
	}
}

func exitErr(err error) {
	output(color.RedString(err.Error()))
	os.Exit(1)
}

func main() {
	app := cli.NewApp()
	app.Name = "slackcat"
	app.Usage = "redirect a file to slack"
	app.Version = version
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "tee, t",
			Usage: "Print stdin to screen before posting",
		},
		cli.BoolFlag{
			Name:  "stream, s",
			Usage: "Stream messages to Slack continuously instead of uploading a single snippet",
		},
		cli.BoolFlag{
			Name:  "plain, p",
			Usage: "Write messages as plain texts instead of code blocks",
		},
		cli.BoolFlag{
			Name:  "noop",
			Usage: "Skip posting file to Slack. Useful for testing",
		},
		cli.BoolFlag{
			Name:  "configure",
			Usage: "Configure Slackcat via oauth",
		},
		cli.StringFlag{
			Name:  "channel, c",
			Usage: "Slack channel or group to post to",
		},
		cli.StringFlag{
			Name:  "filename, n",
			Usage: "Filename for upload. Defaults to current timestamp",
		},
	}

	app.Action = func(c *cli.Context) {
		if c.Bool("configure") {
			configureOA()
			os.Exit(0)
		}

		config := readConfig()
		fileName := c.String("filename")
		team, channel, err := config.parseChannelOpt(c.String("channel"))
		failOnError(err, "", true)

		token := config.teams[team]
		if token == "" {
			exitErr(fmt.Errorf("no such team: %s", team))
		}

		if !c.Bool("stream") && c.Bool("plain") {
			exitErr(fmt.Errorf("'plain' flag requires 'stream' mode!"))
		}

		slackcat, err := newSlackCat(token, channel)
		failOnError(err, "Slack API Error", true)

		if len(c.Args()) > 0 {
			if c.Bool("stream") {
				output("filepath provided, ignoring stream option")
			}
			filePath := c.Args()[0]
			if fileName == "" {
				fileName = filepath.Base(filePath)
			}
			slackcat.postFile(filePath, fileName, c.Bool("noop"))
			os.Exit(0)
		}

		lines := make(chan string)
		go readIn(lines, c.Bool("tee"))

		if c.Bool("stream") {
			output("starting stream")
			go slackcat.addToStreamQ(lines)
			go slackcat.processStreamQ(c.Bool("noop"), c.Bool("plain"))
			go slackcat.trap()
			select {}
		} else {
			filePath := writeTemp(lines)
			defer os.Remove(filePath)
			slackcat.postFile(filePath, fileName, c.Bool("noop"))
			os.Exit(0)
		}
	}

	app.Run(os.Args)

}
<commit_msg>update function name<commit_after>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/codegangsta/cli"
	"github.com/fatih/color"
)

var version = "dev-build"

func readIn(lines chan string, tee bool) {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		lines <- scanner.Text()
		if tee {
			fmt.Println(scanner.Text())
		}
	}
	close(lines)
}

func writeTemp(lines chan string) string {
	tmp, err := ioutil.TempFile(os.TempDir(), "slackcat-")
	failOnError(err, "unable to create tmpfile", false)

	w := bufio.NewWriter(tmp)
	for line := range lines {
		fmt.Fprintln(w, line)
	}
	w.Flush()

	return tmp.Name()
}

func output(s string) {
	bold := color.New(color.Bold).SprintFunc()
	fmt.Printf("%s %s\n", bold("slackcat"), s)
}

func failOnError(err error, msg string, appendErr bool) {
	if err != nil {
		if appendErr {
			exitErr(fmt.Errorf("%s: %s", msg, err))
		} else {
			exitErr(fmt.Errorf("%s", msg))
		}
	}
}

func exitErr(err error) {
	output(color.RedString(err.Error()))
	os.Exit(1)
}

func main() {
	app := cli.NewApp()
	app.Name = "slackcat"
	app.Usage = "redirect a file to slack"
	app.Version = version
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "tee, t",
			Usage: "Print stdin to screen before posting",
		},
		cli.BoolFlag{
			Name:  "stream, s",
			Usage: "Stream messages to Slack continuously instead of uploading a single snippet",
		},
		cli.BoolFlag{
			Name:  "plain, p",
			Usage: "Write messages as plain texts instead of code blocks",
		},
		cli.BoolFlag{
			Name:  "noop",
			Usage: "Skip posting file to Slack. Useful for testing",
		},
		cli.BoolFlag{
			Name:  "configure",
			Usage: "Configure Slackcat via oauth",
		},
		cli.StringFlag{
			Name:  "channel, c",
			Usage: "Slack channel or group to post to",
		},
		cli.StringFlag{
			Name:  "filename, n",
			Usage: "Filename for upload. Defaults to current timestamp",
		},
	}

	app.Action = func(c *cli.Context) {
		if c.Bool("configure") {
			configureOA()
			os.Exit(0)
		}

		config := readConfig()
		fileName := c.String("filename")
		team, channel, err := config.parseChannelOpt(c.String("channel"))
		failOnError(err, "", true)

		token := config.teams[team]
		if token == "" {
			exitErr(fmt.Errorf("no such team: %s", team))
		}

		if !c.Bool("stream") && c.Bool("plain") {
			exitErr(fmt.Errorf("'plain' flag requires 'stream' mode!"))
		}

		slackcat, err := newSlackCat(token, channel)
		failOnError(err, "Slack API Error", true)

		if len(c.Args()) > 0 {
			if c.Bool("stream") {
				output("filepath provided, ignoring stream option")
			}
			filePath := c.Args()[0]
			if fileName == "" {
				fileName = filepath.Base(filePath)
			}
			slackcat.postFile(filePath, fileName, c.Bool("noop"))
			os.Exit(0)
		}

		lines := make(chan string)
		go readIn(lines, c.Bool("tee"))

		if c.Bool("stream") {
			output("starting stream")
			go slackcat.addToStreamQ(lines)
			go slackcat.processStreamQ(c.Bool("noop"), c.Bool("plain"))
			go slackcat.trap()
			select {}
		} else {
			filePath := writeTemp(lines)
			defer os.Remove(filePath)
			slackcat.postFile(filePath, fileName, c.Bool("noop"))
			os.Exit(0)
		}
	}

	app.Run(os.Args)

}
<|endoftext|>


<commit_before>package main

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/mpdroog/beanstalkd" //"github.com/maxid/beanstalkd"
	"gopkg.in/gomail.v1"
	"os"
	"smtpw/config"
	"strings"
	"time"
	"log"
)

const ERR_WAIT_SEC = 5
var errTimedOut = errors.New("timed out")

var verbose bool
var readonly bool
var hostname string
var L *log.Logger

func proc(m config.Email) error {
	conf, ok := config.C.From[m.From]
	if !ok {
		return errors.New("From does not exist: " + m.From)
	}

	msg := gomail.NewMessage()
	msg.SetHeader("Message-ID", fmt.Sprintf("<%s@%s>", RandText(32), hostname))
	if conf.Bounce == nil {
		msg.SetHeader("From", conf.Display+" <"+conf.From+">")
	} else {
		// Set bounce handling
		// From receives bounces
		// Human-clients send to Reply-To
		msg.SetHeader("From", fmt.Sprintf("%s <%s>", conf.Display, *conf.Bounce))
		msg.SetHeader("Reply-To", fmt.Sprintf("%s <%s>", conf.Display, conf.From))
	}
	msg.SetHeader("To", m.To...)
	msg.SetHeader("Bcc", conf.Bcc...)
	msg.SetHeader("Subject", m.Subject)
	msg.SetBody("text/plain", m.Text)
	if len(m.Html) > 0 {
		msg.AddAlternative("text/html", m.Html)
	}

	for name, embed := range m.HtmlEmbed {
		raw, e := base64.StdEncoding.DecodeString(embed)
		if e != nil {
			return errors.New("HtmlEmbed: " + name + " is not base64!")
		}
		if !strings.Contains(m.Html, fmt.Sprintf("cid:"+name)) {
			return errors.New("HtmlEmbed: " + name + " is not used in the HTML!")
		}
		msg.Embed(gomail.CreateFile(name, raw))
	}
	for name, attachment := range m.Attachments {
		raw, e := base64.StdEncoding.DecodeString(attachment)
		if e != nil {
			return errors.New("Attachment: " + name + " is not base64!")
		}
		msg.Embed(gomail.CreateFile(name, raw))
	}

	if readonly {
		L.Printf("From: %s <%s>\n", conf.Display, conf.From)
		L.Printf("To: %v\n", m.To)
		L.Printf("Bcc: %v\n", conf.Bcc)
		L.Printf("Subject: %s\n", m.Subject)
		L.Printf("\ntext/plain\n")
		L.Printf(m.Text)
		L.Printf("\ntext/html\n")
		L.Printf(m.Html)
		L.Printf("\n\n")
		return nil
	}

	mailer := gomail.NewMailer(conf.Host, conf.User, conf.Pass, conf.Port)
	return mailer.Send(msg)
}

func connect() (*beanstalkd.BeanstalkdClient, error) {
	queue, e := beanstalkd.Dial(config.C.Beanstalk)
	if e != nil {
		return nil, e
	}
	// Only listen to email queue.
	queue.Use("email")
	if _, e := queue.Watch("email"); e != nil {
		return nil, e
	}
	queue.Ignore("default")
	return queue, nil
}

func main() {
	var (
		configPath string
		skipOne    bool
	)
	L = log.New(os.Stdout, "", log.LstdFlags)

	flag.BoolVar(&verbose, "v", false, "Verbose-mode")
	flag.BoolVar(&skipOne, "s", false, "Delete e-mail on deverr")
	flag.BoolVar(&readonly, "r", false, "Don't email but flush to stdout")
	flag.StringVar(&configPath, "c", "./config.json", "Path to config.json")
	flag.Parse()

	if e := config.Init(configPath); e != nil {
		panic(e)
	}
	if verbose {
		L.Printf("%+v\n", config.C)
	}
	// TODO: Test config before starting?

	queue, e := connect()
	if e != nil {
		panic(e)
	}
	hostname, e = os.Hostname()
	if e != nil {
		panic(e)
	}

	if verbose {
		L.Printf("SMTPw(%s) email-tube (ignoring default)\n", hostname)
	}
	if readonly {
		L.Printf("!! ReadOnly mode !!\n")
	}
	for {
		job, e := queue.Reserve(15*60) //15min timeout
		if e != nil {
			if e.Error() == errTimedOut.Error() {
				// Reserve timeout
				continue
			}

			L.Printf("Beanstalkd err: %s\n", e.Error())
			time.Sleep(time.Second * ERR_WAIT_SEC)
			if strings.HasSuffix(e.Error(), "broken pipe") {
				// Beanstalkd down, reconnect!
				q, e := connect()
				if e != nil {
					L.Printf("Reconnect err: %s\n", e.Error())
				}
				if q != nil {
					queue = q
				}
			}
			continue
		}
		if verbose {
			L.Printf("Parse job %d\n", job.Id)
			L.Printf("JSON:\n%s\n", string(job.Data))
		}
		// Parse
		var m config.Email
		if e := json.Unmarshal(job.Data, &m); e != nil {
			// Broken JSON
			if skipOne {
				L.Printf("WARN: Skip job as JSON is invalid\n")
				queue.Delete(job.Id)
				skipOne = false
				continue
			}

			// Ignore decode trouble
			L.Printf("CRIT: Invalid JSON received (msg=%s)\n", e.Error())
			continue
		}

		if e := proc(m); e != nil {
			// 501 Syntax error in parameters or arguments
			if strings.HasPrefix(e.Error(), "501 ") {
				L.Printf("WARN: Job buried, invalid email address(es)? (msg=%s)\n", e.Error())
				queue.Bury(job.Id, 1)
				continue
			}
			// TODO: Isolate deverr from senderr
			// Processing trouble?
			L.Printf("WARN: Failed sending, retry in 20sec (msg=%s)\n", e.Error())
			time.Sleep(time.Second * 20)
			continue
		}
		queue.Delete(job.Id)
		if verbose {
			L.Printf("Finished job %d", job.Id)
		}
	}
	queue.Quit()
}
<commit_msg>Doc. Add SMTP codes link<commit_after>package main

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/mpdroog/beanstalkd" //"github.com/maxid/beanstalkd"
	"gopkg.in/gomail.v1"
	"os"
	"smtpw/config"
	"strings"
	"time"
	"log"
)

const ERR_WAIT_SEC = 5
var errTimedOut = errors.New("timed out")

var verbose bool
var readonly bool
var hostname string
var L *log.Logger

func proc(m config.Email) error {
	conf, ok := config.C.From[m.From]
	if !ok {
		return errors.New("From does not exist: " + m.From)
	}

	msg := gomail.NewMessage()
	msg.SetHeader("Message-ID", fmt.Sprintf("<%s@%s>", RandText(32), hostname))
	if conf.Bounce == nil {
		msg.SetHeader("From", conf.Display+" <"+conf.From+">")
	} else {
		// Set bounce handling
		// From receives bounces
		// Human-clients send to Reply-To
		msg.SetHeader("From", fmt.Sprintf("%s <%s>", conf.Display, *conf.Bounce))
		msg.SetHeader("Reply-To", fmt.Sprintf("%s <%s>", conf.Display, conf.From))
	}
	msg.SetHeader("To", m.To...)
	msg.SetHeader("Bcc", conf.Bcc...)
	msg.SetHeader("Subject", m.Subject)
	msg.SetBody("text/plain", m.Text)
	if len(m.Html) > 0 {
		msg.AddAlternative("text/html", m.Html)
	}

	for name, embed := range m.HtmlEmbed {
		raw, e := base64.StdEncoding.DecodeString(embed)
		if e != nil {
			return errors.New("HtmlEmbed: " + name + " is not base64!")
		}
		if !strings.Contains(m.Html, fmt.Sprintf("cid:"+name)) {
			return errors.New("HtmlEmbed: " + name + " is not used in the HTML!")
		}
		msg.Embed(gomail.CreateFile(name, raw))
	}
	for name, attachment := range m.Attachments {
		raw, e := base64.StdEncoding.DecodeString(attachment)
		if e != nil {
			return errors.New("Attachment: " + name + " is not base64!")
		}
		msg.Embed(gomail.CreateFile(name, raw))
	}

	if readonly {
		L.Printf("From: %s <%s>\n", conf.Display, conf.From)
		L.Printf("To: %v\n", m.To)
		L.Printf("Bcc: %v\n", conf.Bcc)
		L.Printf("Subject: %s\n", m.Subject)
		L.Printf("\ntext/plain\n")
		L.Printf(m.Text)
		L.Printf("\ntext/html\n")
		L.Printf(m.Html)
		L.Printf("\n\n")
		return nil
	}

	mailer := gomail.NewMailer(conf.Host, conf.User, conf.Pass, conf.Port)
	return mailer.Send(msg)
}

func connect() (*beanstalkd.BeanstalkdClient, error) {
	queue, e := beanstalkd.Dial(config.C.Beanstalk)
	if e != nil {
		return nil, e
	}
	// Only listen to email queue.
	queue.Use("email")
	if _, e := queue.Watch("email"); e != nil {
		return nil, e
	}
	queue.Ignore("default")
	return queue, nil
}

func main() {
	var (
		configPath string
		skipOne    bool
	)
	L = log.New(os.Stdout, "", log.LstdFlags)

	flag.BoolVar(&verbose, "v", false, "Verbose-mode")
	flag.BoolVar(&skipOne, "s", false, "Delete e-mail on deverr")
	flag.BoolVar(&readonly, "r", false, "Don't email but flush to stdout")
	flag.StringVar(&configPath, "c", "./config.json", "Path to config.json")
	flag.Parse()

	if e := config.Init(configPath); e != nil {
		panic(e)
	}
	if verbose {
		L.Printf("%+v\n", config.C)
	}
	// TODO: Test config before starting?

	queue, e := connect()
	if e != nil {
		panic(e)
	}
	hostname, e = os.Hostname()
	if e != nil {
		panic(e)
	}

	if verbose {
		L.Printf("SMTPw(%s) email-tube (ignoring default)\n", hostname)
	}
	if readonly {
		L.Printf("!! ReadOnly mode !!\n")
	}
	for {
		job, e := queue.Reserve(15*60) //15min timeout
		if e != nil {
			if e.Error() == errTimedOut.Error() {
				// Reserve timeout
				continue
			}

			L.Printf("Beanstalkd err: %s\n", e.Error())
			time.Sleep(time.Second * ERR_WAIT_SEC)
			if strings.HasSuffix(e.Error(), "broken pipe") {
				// Beanstalkd down, reconnect!
				q, e := connect()
				if e != nil {
					L.Printf("Reconnect err: %s\n", e.Error())
				}
				if q != nil {
					queue = q
				}
			}
			continue
		}
		if verbose {
			L.Printf("Parse job %d\n", job.Id)
			L.Printf("JSON:\n%s\n", string(job.Data))
		}
		// Parse
		var m config.Email
		if e := json.Unmarshal(job.Data, &m); e != nil {
			// Broken JSON
			if skipOne {
				L.Printf("WARN: Skip job as JSON is invalid\n")
				queue.Delete(job.Id)
				skipOne = false
				continue
			}

			// Ignore decode trouble
			L.Printf("CRIT: Invalid JSON received (msg=%s)\n", e.Error())
			continue
		}

		if e := proc(m); e != nil {
			// 501 Syntax error in parameters or arguments
			// http://www.greenend.org.uk/rjk/tech/smtpreplies.html
			if strings.HasPrefix(e.Error(), "501 ") {
				L.Printf("WARN: Job buried, invalid email address(es)? (msg=%s)\n", e.Error())
				queue.Bury(job.Id, 1)
				continue
			}
			// TODO: Isolate deverr from senderr
			// Processing trouble?
			L.Printf("WARN: Failed sending, retry in 20sec (msg=%s)\n", e.Error())
			time.Sleep(time.Second * 20)
			continue
		}
		queue.Delete(job.Id)
		if verbose {
			L.Printf("Finished job %d", job.Id)
		}
	}
	queue.Quit()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/tls"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"time"
)

import (
	"github.com/suntong/go-imap"
	"github.com/voxelbrain/goptions"
)

////////////////////////////////////////////////////////////////////////////
// Constant and data type/structure definitions

////////////////////////////////////////////////////////////////////////////
// Global variables definitions

var (
	dumpProtocol *bool = flag.Bool("dumpprotocol", false, "dump imap stream")
)

////////////////////////////////////////////////////////////////////////////
// Function definitions

func check(err error) {
	if err != nil {
		panic(err)
	}
}

type progressMessage struct {
	cur, total int
	text       string
}

type UI struct {
	statusChan chan interface{}
	netmon     *netmonReader
}

func (ui *UI) log(format string, args ...interface{}) {
	ui.statusChan <- fmt.Sprintf(format, args...)
}

func (ui *UI) progress(cur, total int, format string, args ...interface{}) {
	message := &progressMessage{cur, total, fmt.Sprintf(format, args...)}
	ui.statusChan <- message
}

func loadAuth(path string) (string, string, string) {
	f, err := os.Open(path)
	check(err)
	r := bufio.NewReader(f)

	imap, isPrefix, err := r.ReadLine()
	check(err)
	if isPrefix {
		panic("prefix")
	}

	user, isPrefix, err := r.ReadLine()
	check(err)
	if isPrefix {
		panic("prefix")
	}

	pass, isPrefix, err := r.ReadLine()
	check(err)
	if isPrefix {
		panic("prefix")
	}

	return string(imap), string(user), string(pass)
}

func readExtra(im *imap.IMAP) {
	for {
		select {
		case msg := <-im.Unsolicited:
			log.Printf("*** unsolicited: %T %+v", msg, msg)
		default:
			return
		}
	}
}

func (ui *UI) connect(useNetmon bool) *imap.IMAP {
	imapAddr, user, pass := loadAuth("auth")

	ui.log("connecting...")
	conn, err := tls.Dial("tcp", imapAddr, nil)
	check(err)

	var r io.Reader = conn
	if *dumpProtocol {
		r = newLoggingReader(r, 300)
	}
	if useNetmon {
		ui.netmon = newNetmonReader(r)
		r = ui.netmon
	}
	im := imap.New(r, conn)
	im.Unsolicited = make(chan interface{}, 100)

	hello, err := im.Start()
	check(err)
	ui.log("server hello: %s", hello)

	ui.log("logging in...")
	resp, caps, err := im.Auth(user, pass)
	check(err)
	ui.log("%s", resp)
	ui.log("server capabilities: %s", caps)

	return im
}

func (ui *UI) fetch(im *imap.IMAP, mailbox string) {
	ui.log("opening %s...", mailbox)
	examine, err := im.Examine(mailbox)
	check(err)
	ui.log("mailbox status: %+v", examine)
	readExtra(im)

	f, err := os.Create(mailbox + ".mbox")
	check(err)
	mbox := newMbox(f)

	query := fmt.Sprintf("1:%d", examine.Exists)
	ui.log("requesting messages %s", query)

	ch, err := im.FetchAsync(query, []string{"RFC822"})
	check(err)

	i := 0
	total := examine.Exists
	ui.progress(i, total, "fetching messages", i, total)
L:
	for {
		r := <-ch
		switch r := r.(type) {
		case *imap.ResponseFetch:
			if err = mbox.writeMessage(r.Rfc822, messageFilter); err != nil &&
				VERBOSITY > 0 {
				ui.log("message ignored: %v", err)
			}
			i++
			ui.progress(i, total, "fetching messages")
		case *imap.ResponseStatus:
			ui.log("complete %v\n", r)
			break L
		}
	}
	readExtra(im)
}

var (
	messageFilterFunc = validation(emptyFilter)
	validFrom         time.Time
)

func messageFilter(rfc822 []byte, envelopeDate time.Time) error {
	if envelopeDate.Before(validFrom) {
		return errors.New("older than picked date")
	}
	return nil
}

func (ui *UI) reportOnStatus() {

	ticker := time.NewTicker(1000 * 1000 * 1000)
	overprint := false
	status := ""
	overprintLast := false
	for ui.statusChan != nil {
		select {
		case s, stillOpen := <-ui.statusChan:
			switch s := s.(type) {
			case string:
				status = s
				overprint = false
			case *progressMessage:
				status = fmt.Sprintf("%s [%d/%d]", s.text, s.cur, s.total)
				overprint = true
			default:
				if s != nil {
					status = s.(error).Error()
					ui.statusChan = nil
					ticker.Stop()
				}
			}
			if !stillOpen {
				ui.statusChan = nil
				ticker.Stop()
			}
		case <-ticker.C:
			if ui.netmon != nil {
				ui.netmon.Tick()
			}
		}

		if overprintLast {
			fmt.Printf("\r\x1B[K")
		} else {
			fmt.Printf("\n")
		}
		overprintLast = overprint
		fmt.Printf("%s", status)
		if overprint && ui.netmon != nil {
			fmt.Printf(" [%.1fk/s]", ui.netmon.Bandwidth()/1000.0)
		}
	}
	fmt.Printf("\n")
}

func (ui *UI) runFetch(mailbox string) {
	ui.statusChan = make(chan interface{})
	go func() {
		defer func() {
			if e := recover(); e != nil {
				ui.statusChan <- e
			}
		}()
		im := ui.connect(true)
		ui.fetch(im, mailbox)
		close(ui.statusChan)
	}()

	ui.reportOnStatus()
}

func (ui *UI) runList() {
	ui.statusChan = make(chan interface{})
	go func() {
		im := ui.connect(false)
		mailboxes, err := im.List("", imap.WildcardAny)
		check(err)
		fmt.Printf("Available mailboxes:\n")
		for _, mailbox := range mailboxes {
			fmt.Printf("  %s\n", mailbox.Name)
		}
		readExtra(im)
	}()

	ui.reportOnStatus()
}

////////////////////////////////////////////////////////////////////////////
// Main

type Options struct {
	Verbosity []bool        `goptions:"-v, --verbose, description='Be verbose'"`
	Help      goptions.Help `goptions:"-h, --help, description='Show this help\n\nSub-commands (Verbs):\n\tlist\t\tList mailboxes\n\tfetch\t\tDownload mailbox'"`

	goptions.Verbs

	List struct{} `goptions:"list"`

	Fetch struct {
		Folder      string `goptions:"-f, --folder, description='Mail folder to fetch', obligatory"`
		WithinYear  int    `goptions:"--wy, description='Within years, only to fetch mails within this number of years'"`
		WithinMonth int    `goptions:"--wm, description='Within months, ditto for months'"`
		WithinDay   int    `goptions:"--wd, description='Within days, ditto for days'"`
	} `goptions:"fetch"`
}

var options = Options{ // Default values goes here
}

type Command func(Options) error

var commands = map[goptions.Verbs]Command{
	"list":  listCmd,
	"fetch": fetchCmd,
}

var (
	VERBOSITY = 0
)

func main() {
	log.SetFlags(log.Ltime | log.Lshortfile)

	goptions.ParseAndFail(&options)
	//fmt.Printf("] %#v\r\n", options)

	if len(options.Verbs) == 0 {
		goptions.PrintHelp()
		os.Exit(2)
	}

	VERBOSITY = len(options.Verbosity)

	if cmd, found := commands[options.Verbs]; found {
		err := cmd(options)
		check(err)
	}

}

func listCmd(options Options) error {
	ui := new(UI)
	ui.runList()
	println("done")
	return nil
}

func fetchCmd(options Options) error {
	ui := new(UI)

	if options.Fetch.WithinYear+options.Fetch.WithinMonth+
		options.Fetch.WithinDay > 0 {
		messageFilterFunc = validation(messageFilter)
		validFrom = time.Now()
		validFrom = validFrom.AddDate(-options.Fetch.WithinYear,
			-options.Fetch.WithinMonth, -options.Fetch.WithinDay)
	}

	ui.runFetch(options.Fetch.Folder)
	return nil
}
<commit_msg>- [*] shuffle code around<commit_after>package main

import (
	"bufio"
	"crypto/tls"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"time"
)

import (
	"github.com/suntong/go-imap"
	"github.com/voxelbrain/goptions"
	"gopkg.in/yaml.v2"
)

////////////////////////////////////////////////////////////////////////////
// Constant and data type/structure definitions

////////////////////////////////////////////////////////////////////////////
// Global variables definitions

var (
	dumpProtocol *bool = flag.Bool("dumpprotocol", false, "dump imap stream")
)

////////////////////////////////////////////////////////////////////////////
// Function definitions

func check(err error) {
	if err != nil {
		panic(err)
	}
}

type progressMessage struct {
	cur, total int
	text       string
}

type UI struct {
	statusChan chan interface{}
	netmon     *netmonReader
}

func (ui *UI) log(format string, args ...interface{}) {
	ui.statusChan <- fmt.Sprintf(format, args...)
}

func (ui *UI) progress(cur, total int, format string, args ...interface{}) {
	message := &progressMessage{cur, total, fmt.Sprintf(format, args...)}
	ui.statusChan <- message
}

func loadAuth(path string) (string, string, string) {
	f, err := os.Open(path)
	check(err)
	r := bufio.NewReader(f)

	imap, isPrefix, err := r.ReadLine()
	check(err)
	if isPrefix {
		panic("prefix")
	}

	user, isPrefix, err := r.ReadLine()
	check(err)
	if isPrefix {
		panic("prefix")
	}

	pass, isPrefix, err := r.ReadLine()
	check(err)
	if isPrefix {
		panic("prefix")
	}

	return string(imap), string(user), string(pass)
}

func readExtra(im *imap.IMAP) {
	for {
		select {
		case msg := <-im.Unsolicited:
			log.Printf("*** unsolicited: %T %+v", msg, msg)
		default:
			return
		}
	}
}

func (ui *UI) connect(useNetmon bool) *imap.IMAP {
	imapAddr, user, pass := loadAuth("auth")

	ui.log("connecting...")
	conn, err := tls.Dial("tcp", imapAddr, nil)
	check(err)

	var r io.Reader = conn
	if *dumpProtocol {
		r = newLoggingReader(r, 300)
	}
	if useNetmon {
		ui.netmon = newNetmonReader(r)
		r = ui.netmon
	}
	im := imap.New(r, conn)
	im.Unsolicited = make(chan interface{}, 100)

	hello, err := im.Start()
	check(err)
	ui.log("server hello: %s", hello)

	ui.log("logging in...")
	resp, caps, err := im.Auth(user, pass)
	check(err)
	ui.log("%s", resp)
	ui.log("server capabilities: %s", caps)

	return im
}

func (ui *UI) fetch(im *imap.IMAP, mailbox string) {
	ui.log("opening %s...", mailbox)
	examine, err := im.Examine(mailbox)
	check(err)
	ui.log("mailbox status: %+v", examine)
	readExtra(im)

	f, err := os.Create(mailbox + ".mbox")
	check(err)
	mbox := newMbox(f)

	query := fmt.Sprintf("1:%d", examine.Exists)
	ui.log("requesting messages %s", query)

	ch, err := im.FetchAsync(query, []string{"RFC822"})
	check(err)

	i := 0
	total := examine.Exists
	ui.progress(i, total, "fetching messages", i, total)
L:
	for {
		r := <-ch
		switch r := r.(type) {
		case *imap.ResponseFetch:
			if err = mbox.writeMessage(r.Rfc822, messageFilter); err != nil &&
				VERBOSITY > 0 {
				ui.log("message ignored: %v", err)
			}
			i++
			ui.progress(i, total, "fetching messages")
		case *imap.ResponseStatus:
			ui.log("complete %v\n", r)
			break L
		}
	}
	readExtra(im)
}

func (ui *UI) reportOnStatus() {

	ticker := time.NewTicker(1000 * 1000 * 1000)
	overprint := false
	status := ""
	overprintLast := false
	for ui.statusChan != nil {
		select {
		case s, stillOpen := <-ui.statusChan:
			switch s := s.(type) {
			case string:
				status = s
				overprint = false
			case *progressMessage:
				status = fmt.Sprintf("%s [%d/%d]", s.text, s.cur, s.total)
				overprint = true
			default:
				if s != nil {
					status = s.(error).Error()
					ui.statusChan = nil
					ticker.Stop()
				}
			}
			if !stillOpen {
				ui.statusChan = nil
				ticker.Stop()
			}
		case <-ticker.C:
			if ui.netmon != nil {
				ui.netmon.Tick()
			}
		}

		if overprintLast {
			fmt.Printf("\r\x1B[K")
		} else {
			fmt.Printf("\n")
		}
		overprintLast = overprint
		fmt.Printf("%s", status)
		if overprint && ui.netmon != nil {
			fmt.Printf(" [%.1fk/s]", ui.netmon.Bandwidth()/1000.0)
		}
	}
	fmt.Printf("\n")
}

func (ui *UI) runFetch(mailbox string) {
	ui.statusChan = make(chan interface{})
	go func() {
		defer func() {
			if e := recover(); e != nil {
				ui.statusChan <- e
			}
		}()
		im := ui.connect(true)
		ui.fetch(im, mailbox)
		close(ui.statusChan)
	}()

	ui.reportOnStatus()
}

func (ui *UI) runList() {
	ui.statusChan = make(chan interface{})
	go func() {
		im := ui.connect(false)
		mailboxes, err := im.List("", imap.WildcardAny)
		check(err)
		fmt.Printf("Available mailboxes:\n")
		for _, mailbox := range mailboxes {
			fmt.Printf("  %s\n", mailbox.Name)
		}
		readExtra(im)
	}()

	ui.reportOnStatus()
}

////////////////////////////////////////////////////////////////////////////
// Main

type Options struct {
	Verbosity []bool        `goptions:"-v, --verbose, description='Be verbose'"`
	Help      goptions.Help `goptions:"-h, --help, description='Show this help\n\nSub-commands (Verbs):\n\tlist\t\tList mailboxes\n\tfetch\t\tDownload mailbox'"`

	goptions.Verbs

	List struct{} `goptions:"list"`

	Fetch struct {
		Folder      string `goptions:"-f, --folder, description='Mail folder to fetch', obligatory"`
		WithinYear  int    `goptions:"--wy, description='Within years, only to fetch mails within this number of years'"`
		WithinMonth int    `goptions:"--wm, description='Within months, ditto for months'"`
		WithinDay   int    `goptions:"--wd, description='Within days, ditto for days'"`
	} `goptions:"fetch"`
}

var options = Options{ // Default values goes here
}

type Command func(Options) error

var commands = map[goptions.Verbs]Command{
	"list":  listCmd,
	"fetch": fetchCmd,
}

var (
	VERBOSITY = 0
)

func main() {
	log.SetFlags(log.Ltime | log.Lshortfile)

	goptions.ParseAndFail(&options)
	//fmt.Printf("] %#v\r\n", options)

	if len(options.Verbs) == 0 {
		goptions.PrintHelp()
		os.Exit(2)
	}

	VERBOSITY = len(options.Verbosity)

	if cmd, found := commands[options.Verbs]; found {
		err := cmd(options)
		check(err)
	}

}

////////////////////////////////////////////////////////////////////////////
// Dispatch function definitions

func listCmd(options Options) error {
	ui := new(UI)
	ui.runList()
	println("done")
	return nil
}

func fetchCmd(options Options) error {
	ui := new(UI)

	if options.Fetch.WithinYear+options.Fetch.WithinMonth+
		options.Fetch.WithinDay > 0 {
		messageFilterFunc = validation(messageFilter)
		validFrom = time.Now()
		validFrom = validFrom.AddDate(-options.Fetch.WithinYear,
			-options.Fetch.WithinMonth, -options.Fetch.WithinDay)
	}

	ui.runFetch(options.Fetch.Folder)
	return nil
}

////////////////////////////////////////////////////////////////////////////
// message filtering

var (
	messageFilterFunc = validation(emptyFilter)
	validFrom         time.Time
)

func messageFilter(rfc822 []byte, envelopeDate time.Time) error {
	if envelopeDate.Before(validFrom) {
		return errors.New("older than picked date")
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"math/rand"
	"sort"
	"time"
)

func main() {
	/*
		log.Print("started.")
		// チャネル
		sleep1_finished := make(chan bool)

		go func() {
			// 0.2秒かかるコマンド
			log.Print("sleep1 started.")
			time.Sleep(200 * 1000 * 1000 * time.Nanosecond)
			log.Print("sleep1 finished.")
			sleep1_finished <- true
		}()

		// 終わるまで待つ
		<- sleep1_finished
	*/

	// 136枚の中から14枚返したい
	list := shuffled_cards()

	// 出力
	// digit_output(list)
	solve(list)
	string_output(list)
}

// http://d.hatena.ne.jp/hake/20150930/p1
func shuffle(list []int) {
	for i := len(list); i > 1; i-- {
		j := rand.Intn(i) // 0　.. i-1 の乱数発生
		list[i-1], list[j] = list[j], list[i-1]
	}
}

func shuffled_cards() []int {
	rand.Seed(time.Now().UnixNano())

	// データ要素数指定、および初期データ作成
	size := 136
	list := make([]int, size, size)
	for i := 0; i < size; i++ {
		list[i] = i
	}

	// シャッフル
	shuffle(list)

	return list[:14]
}

// 単純出力
func digit_output(list []int) {
	for j := 0; j < 14; j++ {
		log.Println(list[j])
	}
}

// 牌の出力
func string_output(list []int) {
	// http://qiita.com/ruiu/items/2bb83b29baeae2433a79
	// サイズ0、内部バッファの長さ69の[]byteの値を割り当てる
	b := make([]byte, 0, 70)

	// bに文字列を追加
	for j := 0; j < 14; j++ {
		// 126976 is 'ton'
		// https://codepoints.net/U+1F000
		b = append(b, string(list[j]/4+126976)...) // ...が必要
		b = append(b, string(32)...)               // ...が必要
	}
	log.Print(string(b))
}

// solve
// 字マンソーピンのリストをつくる
func solve(list []int) bool {
	if is_chitoitsu(list) {
		return true
	}
	matrix := [][]int{{}, {}, {}, {}}
	for _, value := range list {
		group(matrix, value)
	}
	log.Println(matrix)

	group_scan(matrix)
	return false
}

// スート分類してくれる
func group(m [][]int, j int) {
	// 4で割ると本来のインデックスに
	i := j / 4
	switch {
	case i < 7:
		m[0] = append(m[0], i)
	case i < 7+(9*1):
		m[1] = append(m[1], i-7)
	case i < 7+(9*2):
		m[2] = append(m[2], i-7-(9*1))
	case i < 7+(9*3):
		m[3] = append(m[3], i-7-(9*2))
	}
}

func group_scan(m [][]int) bool {
	if !validate_mod3(m) {
		log.Println("immi")
		return false
	}
	if !validate_33332(m) {
		return false
	}
	return true
}

func validate_mod3(m [][]int) bool {
	//スートのサイズを3で割った時
	//あまりが2であるすーとグループが1つであること
	c := 0
	for _, a := range m {
		switch len(a) % 3 {
		case 0:
			// noop
		case 1:
			return false
		case 2:
			c++
		}
	}
	return c == 1
}

func validate_33332(m [][]int) bool {
	for _, a := range m {
		if !validate_suit_group(a) {
			return false
		}
	}
	return true
}

func validate_suit_group(a []int) bool {
	//TODO: implement
	for _, v := range a {
		// 4で割ると本来のインデックスに
		v = v / 4
	}

	//ソート
	sort.Ints(a)
	if len(a)%3 == 2 {
		//ペアを探す
		pair_numbers := pairable_numbers(a)
		//ペア候補がなかったらぬける
		if len(pair_numbers) == 0 {
			return false
		}
		//ペア候補毎に繰り返し処理
		for _, v := range pair_numbers {
			//ペアとなる２枚を除去
			rest := []int{}
			c := 2
			for _, w := range a {
				if w != v && c != 0 {
					rest = append(rest, w)
					c--
				}
			}
			if valid_3cards(rest) {
				return true
			} else {
				continue
			}
		}
		return false
	} else if len(a)%3 == 0 {
		return valid_3cards(a)
	}
	// 来ないはず
	return false
}

func valid_3cards(a []int) bool {
	// 刻子や順子のみで構成されている場合true
	// a is sorted
	// a.size % 3 is0
	for {
		if remove_kotsu(a) {
			continue
		}
		if remove_shuntsu(a) {
			continue
		}
		return len(a) == 0
	}
}

func remove_kotsu(a []int) bool {
	// 刻子を除去できればtrue
	// a is sorted
	if len(a) < 3 {
		return false
	}
	if a[0] == a[1] && a[0] == a[2] {
		a = a[3:]
		return true
	}
	return false
}

func remove_shuntsu(a []int) bool {
	// 順子を除去できればtrue
	// a is sorted
	rest := []int{}
	first := -1
	second := -1
	found := false
	for _, v := range a {
		if found {
			rest = append(rest, v)
			continue
		}
		if first == -1 {
			first = v
		} else if second == -1 && first+1 == v {
			second = v
		} else if second != -1 && first+2 == v {
			//flush
			first = -1
			second = -1
			found = true
		} else {
			rest = append(rest, v)
		}
	}
	a = rest
	return found
}

func pairable_numbers(sorted []int) []int {
	//カウンタ
	retval := []int{}
	a := 999 // 2つ前
	b := 999 // 1つ前
	for _, v := range sorted {
		if b == v && a != v {
			retval = append(retval, v)
		} else {
			b = v
		}
	}
	return retval
}

func is_chitoitsu(list []int) bool {
	//カウンタ
	c := map[int]int{}

	//コピー
	l := list

	for _, v := range l {
		// 4で割ると本来のインデックスに
		v = v / 4
		count, ok := c[v]
		if ok {
			if count == 1 {
				c[v] = 2
			} else {
				// c[v] == 2
				return false
			}
		} else {
			c[v] = 1
		}
		//8個チェック
		if len(c) >= 8 {
			return false
		}
	}
	return true
}
<commit_msg>Update<commit_after>package main

import (
	"log"
	"math/rand"
	"sort"
	"time"
)

func main() {
	i := 0
	for {
		i++
		log.Printf("%d\n", i)
		if submain() {
			break
		}
	}
}

func submain() bool {
	/*
		log.Print("started.")
		// チャネル
		sleep1_finished := make(chan bool)

		go func() {
			// 0.2秒かかるコマンド
			log.Print("sleep1 started.")
			time.Sleep(200 * 1000 * 1000 * time.Nanosecond)
			log.Print("sleep1 finished.")
			sleep1_finished <- true
		}()

		// 終わるまで待つ
		<- sleep1_finished
	*/

	// 136枚の中から14枚返したい
	list := shuffled_cards()

	// 出力
	// digit_output(list)
	result := solve(list)
	string_output(list)
	return result
}

// http://d.hatena.ne.jp/hake/20150930/p1
func shuffle(list []int) {
	for i := len(list); i > 1; i-- {
		j := rand.Intn(i) // 0　.. i-1 の乱数発生
		list[i-1], list[j] = list[j], list[i-1]
	}
}

func shuffled_cards() []int {
	rand.Seed(time.Now().UnixNano())

	// データ要素数指定、および初期データ作成
	size := 136
	list := make([]int, size, size)
	for i := 0; i < size; i++ {
		list[i] = i
	}

	// シャッフル
	shuffle(list)

	return list[:14]
}

// 単純出力
func digit_output(list []int) {
	for j := 0; j < 14; j++ {
		log.Println(list[j])
	}
}

// 牌の出力
func string_output(list []int) {
	// http://qiita.com/ruiu/items/2bb83b29baeae2433a79
	// サイズ0、内部バッファの長さ69の[]byteの値を割り当てる
	b := make([]byte, 0, 70)

	// bに文字列を追加
	for j := 0; j < 14; j++ {
		// 126976 is 'ton'
		// https://codepoints.net/U+1F000
		b = append(b, string(list[j]/4+126976)...) // ...が必要
		b = append(b, string(32)...)               // ...が必要
	}
	log.Print(string(b))
}

// solve
// 字マンソーピンのリストをつくる
func solve(list []int) bool {
	if is_chitoitsu(list) {
		return true
	}
	matrix := [][]int{{}, {}, {}, {}}
	for _, value := range list {
		group(matrix, value)
	}
	log.Println(matrix)
	log.Println("##end solve")

	return group_scan(matrix)
}

// スート分類してくれる
func group(m [][]int, j int) {
	// 4で割ると本来のインデックスに
	i := j / 4
	switch {
	case i < 7:
		m[0] = append(m[0], i)
	case i < 7+(9*1):
		m[1] = append(m[1], i-7)
	case i < 7+(9*2):
		m[2] = append(m[2], i-7-(9*1))
	case i < 7+(9*3):
		m[3] = append(m[3], i-7-(9*2))
	}
}

func group_scan(m [][]int) bool {
	log.Println("##start solve")
	if !validate_mod3(m) {
		log.Println("immi")
		return false
	}
	if !validate_33332(m) {
		return false
	}
	return true
}

func validate_mod3(m [][]int) bool {
	//スートのサイズを3で割った時
	//あまりが2であるすーとグループが1つであること
	c := 0
	for _, a := range m {
		switch len(a) % 3 {
		case 0:
			// noop
		case 1:
			return false
		case 2:
			c++
		}
	}
	return c == 1
}

func validate_33332(m [][]int) bool {
	log.Println("##start validate_33332")
	for _, a := range m {
		if !validate_suit_group(a) {
			return false
		}
	}
	log.Println("##end validate_33332")
	return true
}

func validate_suit_group(a []int) bool {
	log.Println("##start validate_suit_group")
	for _, v := range a {
		// 4で割ると本来のインデックスに
		v = v / 4
	}

	log.Println("##1")
	//ソート
	sort.Ints(a)
	log.Println("##2")
	log.Println("##2-1")
	if len(a)%3 == 2 {
		log.Println("##3")
		//ペアを探す
		pair_numbers := pairable_numbers(a)
		//ペア候補がなかったらぬける
		log.Println("##4")
		if len(pair_numbers) == 0 {
			log.Println("##5")
			return false
		}
		//ペア候補毎に繰り返し処理
		log.Println("##6")
		log.Println("======")
		log.Println(pair_numbers)
		for _, v := range pair_numbers {
			//ペアとなる２枚を除去
			log.Println("##7")
			rest := []int{}
			c := 2
			for _, w := range a {
				if w == v && c != 0 {
					c--
				}
				if w != v || c == 0 {
					rest = append(rest, w)
				}
			}
			if valid_3cards(rest) {
				log.Println("------")
				log.Println(rest)
				return true
			} else {
				continue
			}
		}
		return false
	} else if len(a)%3 == 0 {
		log.Println("##8")
		return valid_3cards(a)
	}
	// 来ないはず
	return false
}

func valid_3cards(a []int) bool {
	// 刻子や順子のみで構成されている場合true
	// a is sorted
	// a.size % 3 is0
	ok := false
	log.Println("##start valid_3cards")
	for {
		log.Println("##11")
		a, ok = remove_kotsu(a)
		log.Println("##12")
		log.Println("%v", a)
		if ok {
			continue
		}
		a, ok = remove_shuntsu(a)
		log.Println("##13")
		log.Println("%v", a)
		if ok {
			continue
		}
		log.Println("hore", len(a) == 0)
		return len(a) == 0
	}
}

func remove_kotsu(a []int) ([]int, bool) {
	// 刻子を除去できればtrue
	// a is sorted
	retval := a
	if len(a) < 3 {
		return retval, false
	}
	if a[0] == a[1] && a[0] == a[2] {
		retval = a[3:]
		return retval, true
	}
	return retval, false
}

func remove_shuntsu(a []int) ([]int, bool) {
	// 順子を除去できればtrue
	// a is sorted
	rest := []int{}
	first := -1
	second := -1
	found := false
	for _, v := range a {
		if found {
			rest = append(rest, v)
			continue
		}
		if first == -1 {
			first = v
		} else if second == -1 && first+1 == v {
			second = v
		} else if second != -1 && first+2 == v {
			//flush
			first = -1
			second = -1
			found = true
		} else {
			rest = append(rest, v)
		}
	}
	return rest, found
}

func pairable_numbers(sorted []int) []int {
	//カウンタ
	retval := []int{}
	a := 999 // 2つ前
	b := 999 // 1つ前
	for _, v := range sorted {
		if b == v && a != v {
			retval = append(retval, v)
		} else {
			b = v
		}
	}
	return retval
}

func is_chitoitsu(list []int) bool {
	//カウンタ
	c := map[int]int{}

	//コピー
	l := list

	for _, v := range l {
		// 4で割ると本来のインデックスに
		v = v / 4
		count, ok := c[v]
		if ok {
			if count == 1 {
				c[v] = 2
			} else {
				// c[v] == 2
				return false
			}
		} else {
			c[v] = 1
		}
		//8個チェック
		if len(c) >= 8 {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"errors"
	"flag"
	"github.com/gorilla/mux"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

func (bh BoxHandler) GetBox(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	user := vars["user"]
	boxName := vars["boxname"]
	log.Println("Queried for ", user, "/", boxName)
	box := bh.Boxes[user][boxName]

	jsonResponse, _ := json.Marshal(box)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(jsonResponse)
}

func (bh BoxHandler) DownloadBox(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	user := vars["user"]
	boxName := vars["boxname"]
	log.Println("Downloading ", user, "/", boxName)

	localBoxfile := bh.Boxes[user][boxName]
	http.ServeFile(w, r, localBoxfile.LocalBoxFile)
}

func (bh BoxHandler) CheckBox(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	user := vars["user"]
	boxName := vars["boxname"]
	log.Println("Checking ", user, "/", boxName)

	localBoxfile := bh.Boxes[user][boxName]
	if localBoxfile.Username != "" {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
	} else {
		w.WriteHeader(http.StatusNotFound)
	}
}
func (bh BoxHandler) NotFound(w http.ResponseWriter, r *http.Request) {
	log.Println("404 :", r.URL.Path, " ", r.Method)
	w.WriteHeader(http.StatusNotFound)
}

func main() {

	directory := flag.String("d", "./", "Base directory containing .box files")
	port := flag.Int("p", 8099, "Port to listen on.")
	hostname := flag.String("h", "localhost", "Hostname for static box content.")

	flag.Parse()

	absolute := *directory
	if !path.IsAbs(*directory) {
		wd, _ := os.Getwd()
		absolute = path.Clean(path.Join(wd, *directory))
	}

	log.Println("Responding on host: ", *hostname)
	log.Println("Serving files from: ", *directory)
	boxfiles := getBoxList(absolute)
	boxdata := getBoxData(boxfiles)
	boxes := createBoxes(boxdata, *port, hostname)

	for namespace, boxinfo := range boxes {
		for boxname, _ := range boxinfo {
			log.Println(strings.Join([]string{"Found: ", namespace, "/", boxname}, ""))
		}
	}

	bh := BoxHandler{}
	bh.Boxes = boxes

	m := mux.NewRouter()
	m.HandleFunc("/{user}/{boxname}", bh.GetBox).Methods("GET")
	m.HandleFunc("/{user}/{boxname}", bh.CheckBox).Methods("HEAD")
	//Handling downloads that look like Vagrant Cloud
	//https://vagrantcloud.com/benphegan/boot2docker/version/2/provider/vmware_desktop.box
	m.HandleFunc("/{user}/{boxname}/{version}/provider/{boxfile}", bh.DownloadBox).Methods("GET")
	m.NotFoundHandler = http.HandlerFunc(bh.NotFound)
	http.Handle("/", m)

	log.Println("Listening on port: ", *port)
	http.ListenAndServe(":"+strconv.Itoa(*port), nil)
}

type BoxHandler struct {
	Boxes map[string]map[string]Box
}

func createBoxes(sb []SimpleBox, port int, hostname *string) map[string]map[string]Box {
	boxes := make(map[string]map[string]Box)
	for _, b := range sb {
		box := Box{}
		box.Name = b.Username + "/" + b.Boxname
		box.Username = b.Username
		box.Private = false
		box.LocalBoxFile = b.Location

		provider := Provider{}
		provider.Name = b.Provider
		provider.Hosted = "true"
		provider.DownloadUrl = "http://" + *hostname + ":" + strconv.Itoa(port) + "/" + b.Username + "/" + b.Boxname + "/1/provider/" + b.Provider + ".box"
		provider.Url = provider.DownloadUrl
		version := Version{}
		version.Status = "active"
		version.Version = "1.0"

		version.Providers = []Provider{provider}

		box.Versions = []Version{version}
		box.CurrentVersion = version
		boxes[b.Username] = make(map[string]Box)
		boxes[b.Username][b.Boxname] = box
	}
	return boxes
}

func getBoxList(directory string) []string {
	directoryglob := path.Join(directory, "*.box")
	files, _ := filepath.Glob(directoryglob)
	return files
}

func getBoxData(boxfiles []string) []SimpleBox {
	results := []SimpleBox{}
	for _, b := range boxfiles {
		nameparts := strings.Split(filepath.Base(b), "-VAGRANTSLASH-")
		if len(nameparts) == 2 {
			provider, _ := getProvider(b)
			newbox := SimpleBox{}
			newbox.Username = nameparts[0]
			newbox.Boxname = nameparts[1][0 : len(nameparts[1])-4]
			newbox.Location = b
			newbox.Provider = provider.Provider
			results = append(results, newbox)
		}
	}
	return results
}

func getProvider(location string) (BoxMetadata, error) {

	log.Println("Checking: ", location)
	//boxes are either .tar, .tar.gz or .zip so we have to get a gzip stream and pass to tar, just tar or zip check

	// Check zip first...
	newbox, unzipError := getProviderFromZip(location)
	if unzipError != nil {
		log.Println("Not a zip file: " + location + " - " + unzipError.Error())
	} else {
		return newbox, nil
	}

	// Check tar and gzip
	f, openerr := os.Open(location)
	if openerr != nil {
		openerrmessage := "Could not open file: " + location + " - " + openerr.Error()
		log.Fatalln(openerrmessage)
		return BoxMetadata{}, errors.New(openerrmessage)
	}

	// Try gzip first, fall back to tar
	var tr *tar.Reader
	r, gziperr := gzip.NewReader(f)
	if gziperr != nil {
		gziperrmessage := "Could not get GZIP reader: " + location + " - " + gziperr.Error()
		log.Println(gziperrmessage)
		f.Seek(0, 0)
		tr = tar.NewReader(f)
	} else {
		tr = tar.NewReader(r)
	}

	// Iterate through the files in the archive.
	// Some boxes do not have a metadata.json, generally old virtualbox boxes
	// In this case, fall through to just checking them for an ovf and returning provider
	metadatafound := false
	ovf := false
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			if !metadatafound {
				log.Println("Could not find metadata.json in file: " + location)
				if ovf {
					log.Println("Found OVF, creating for VirtualBox: " + location)
					metadata := BoxMetadata{}
					metadata.Provider = "virtualbox"
					return metadata, nil
				}
			}
			break
		}
		if err != nil {
			log.Fatalln(err)
		}

		if filepath.Ext(hdr.Name) == ".ovf" {
			ovf = true
		}

		if hdr.Name == "metadata.json" {
			metadatafound = true
			metadata := BoxMetadata{}

			boxmetadata := BoxMetadata{}

			buf := new(bytes.Buffer)
			buf.ReadFrom(tr)

			json.Unmarshal(buf.Bytes(), &boxmetadata)
			metadata.Provider = boxmetadata.Provider
			return metadata, nil
		}

	}
	return BoxMetadata{}, errors.New("box: could not find metadata.json or box malformed")
}

func getProviderFromZip(src string) (BoxMetadata, error) {
	r, err := zip.OpenReader(src)
	if err != nil {
		return BoxMetadata{}, err
	}
	defer r.Close()

	for _, f := range r.File {

		if f.Name == "metadata.json" {
			log.Println("ZIP!!!")
		}

		rc, err := f.Open()
		if err != nil {
			return BoxMetadata{}, err
		}
		defer rc.Close()

		metadata := BoxMetadata{}

		boxmetadata := BoxMetadata{}

		buf := new(bytes.Buffer)
		buf.ReadFrom(rc)

		json.Unmarshal(buf.Bytes(), &boxmetadata)
		metadata.Provider = boxmetadata.Provider
		return metadata, nil

	}

	return BoxMetadata{}, nil
}

type BoxMetadata struct {
	Provider string `json:"provider"`
}

type SimpleBox struct {
	Username string
	Boxname  string
	Location string
	Provider string
}

type Box struct {
	Created             string    `json:"created_at"`
	Updated             string    `json:"updated_at"`
	Tag                 string    `json:"tag"`
	Name                string    `json:"name"`
	ShortDescription    string    `json:"short_description"`
	DescriptionHtml     string    `json:"description_html"`
	DescriptionMarkdown string    `json:"description_markdown"`
	Username            string    `json:"username"`
	Private             bool      `json:"private"`
	CurrentVersion      Version   `json:"current_version"`
	Versions            []Version `json:"versions"`
	LocalBoxFile        string    `json:"-"`
}

type Version struct {
	Version             string     `json:"version"`
	Status              string     `json:"status"`
	DescriptionHtml     string     `json:"description_html"`
	DescriptionMarkdown string     `json:"description_markdown"`
	Created             string     `json:"created_at"`
	Updated             string     `json:"updated_at"`
	Number              int        `json:"number"`
	Downloads           int        `json:"downloads"`
	ReleaseUrl          string     `json:"release_url"`
	RevokeUrl           string     `json:"revoke_url"`
	Providers           []Provider `json:"providers"`
}

type Provider struct {
	Name        string `json:"name"`
	Hosted      string `json:"hosted"`
	HostedToken string `json:"hosted_token"`
	OriginalUrl string `json:"original_url"`
	UploadUrl   string `json:"upload_url"`
	Created     string `json:"created_at"`
	Updated     string `json:"updated_at"`
	DownloadUrl string `json:"download_url"`
	Url         string `json:"url"`
}
<commit_msg>Add ability to specify multiple directories to pick up box files.  Resolves #5<commit_after>package main

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"errors"
	"flag"
	"github.com/gorilla/mux"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

func (bh BoxHandler) GetBox(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	user := vars["user"]
	boxName := vars["boxname"]
	log.Println("Queried for ", user, "/", boxName)
	box := bh.Boxes[user][boxName]

	jsonResponse, _ := json.Marshal(box)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(jsonResponse)
}

func (bh BoxHandler) DownloadBox(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	user := vars["user"]
	boxName := vars["boxname"]
	log.Println("Downloading ", user, "/", boxName)

	localBoxfile := bh.Boxes[user][boxName]
	http.ServeFile(w, r, localBoxfile.LocalBoxFile)
}

func (bh BoxHandler) CheckBox(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	user := vars["user"]
	boxName := vars["boxname"]
	log.Println("Checking ", user, "/", boxName)

	localBoxfile := bh.Boxes[user][boxName]
	if localBoxfile.Username != "" {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
	} else {
		w.WriteHeader(http.StatusNotFound)
	}
}
func (bh BoxHandler) NotFound(w http.ResponseWriter, r *http.Request) {
	log.Println("404 :", r.URL.Path, " ", r.Method)
	w.WriteHeader(http.StatusNotFound)
}

func main() {

	directory := flag.String("d", "./", "Semicolon separated list of directories containing .box files")
	port := flag.Int("p", 8099, "Port to listen on.")
	hostname := flag.String("h", "localhost", "Hostname for static box content.")

	flag.Parse()

	directories := strings.Split(*directory, ";")
	absolutedirectories := []string{}
	for _, d := range directories {
		if !path.IsAbs(d) {
			wd, _ := os.Getwd()
			absolute := path.Clean(path.Join(wd, d))
			absolutedirectories = append(absolutedirectories, absolute)
		}
	}

	log.Println("Responding on host: ", *hostname)
	log.Println("Serving files from: ", *directory)
	boxfiles := getBoxList(absolutedirectories)
	boxdata := getBoxData(boxfiles)
	boxes := createBoxes(boxdata, *port, hostname)

	for namespace, boxinfo := range boxes {
		for boxname, _ := range boxinfo {
			log.Println(strings.Join([]string{"Found: ", namespace, "/", boxname}, ""))
		}
	}

	bh := BoxHandler{}
	bh.Boxes = boxes

	m := mux.NewRouter()
	m.HandleFunc("/{user}/{boxname}", bh.GetBox).Methods("GET")
	m.HandleFunc("/{user}/{boxname}", bh.CheckBox).Methods("HEAD")
	//Handling downloads that look like Vagrant Cloud
	//https://vagrantcloud.com/benphegan/boot2docker/version/2/provider/vmware_desktop.box
	m.HandleFunc("/{user}/{boxname}/{version}/provider/{boxfile}", bh.DownloadBox).Methods("GET")
	m.NotFoundHandler = http.HandlerFunc(bh.NotFound)
	http.Handle("/", m)

	log.Println("Listening on port: ", *port)
	http.ListenAndServe(":"+strconv.Itoa(*port), nil)
}

type BoxHandler struct {
	Boxes map[string]map[string]Box
}

//Creates the data structure used to provide box data to Vagrant
func createBoxes(sb []SimpleBox, port int, hostname *string) map[string]map[string]Box {
	boxes := make(map[string]map[string]Box)
	for _, b := range sb {
		box := Box{}
		box.Name = b.Username + "/" + b.Boxname
		box.Username = b.Username
		box.Private = false
		box.LocalBoxFile = b.Location

		provider := Provider{}
		provider.Name = b.Provider
		provider.Hosted = "true"
		provider.DownloadUrl = "http://" + *hostname + ":" + strconv.Itoa(port) + "/" + b.Username + "/" + b.Boxname + "/1/provider/" + b.Provider + ".box"
		provider.Url = provider.DownloadUrl
		version := Version{}
		version.Status = "active"
		version.Version = "1.0"

		version.Providers = []Provider{provider}

		box.Versions = []Version{version}
		box.CurrentVersion = version
		boxes[b.Username] = make(map[string]Box)
		boxes[b.Username][b.Boxname] = box
	}
	return boxes
}

// getBoxList returns a list of .box files in the directories provided.
func getBoxList(directories []string) []string {
	boxes := []string{}
	for _, d := range directories {

		directoryglob := path.Join(d, "*.box")
		files, _ := filepath.Glob(directoryglob)
		boxes = append(boxes, files...)
	}
	return boxes
}

//getBoxData returns an array of SimpleBox objects based on Vagrant box files
func getBoxData(boxfiles []string) []SimpleBox {
	results := []SimpleBox{}
	for _, b := range boxfiles {
		nameparts := strings.Split(filepath.Base(b), "-VAGRANTSLASH-")
		if len(nameparts) == 2 {
			provider, _ := getProvider(b)
			newbox := SimpleBox{}
			newbox.Username = nameparts[0]
			newbox.Boxname = nameparts[1][0 : len(nameparts[1])-4]
			newbox.Location = b
			newbox.Provider = provider.Provider
			results = append(results, newbox)
		}
	}
	return results
}

// Gets the provider information from the Vagrant .box files
func getProvider(location string) (BoxMetadata, error) {

	log.Println("Checking: ", location)
	//boxes are either .tar, .tar.gz or .zip so we have to get a gzip stream and pass to tar, just tar or zip check

	// Check zip first...
	newbox, unzipError := getProviderFromZip(location)
	if unzipError != nil {
		log.Println("Not a zip file: " + location + " - " + unzipError.Error())
	} else {
		return newbox, nil
	}

	// Check tar and gzip
	f, openerr := os.Open(location)
	if openerr != nil {
		openerrmessage := "Could not open file: " + location + " - " + openerr.Error()
		log.Fatalln(openerrmessage)
		return BoxMetadata{}, errors.New(openerrmessage)
	}

	// Try gzip first, fall back to tar
	var tr *tar.Reader
	r, gziperr := gzip.NewReader(f)
	if gziperr != nil {
		gziperrmessage := "Could not get GZIP reader: " + location + " - " + gziperr.Error()
		log.Println(gziperrmessage)
		f.Seek(0, 0)
		tr = tar.NewReader(f)
	} else {
		tr = tar.NewReader(r)
	}

	// Iterate through the files in the archive.
	// Some boxes do not have a metadata.json, generally old virtualbox boxes
	// In this case, fall through to just checking them for an ovf and returning provider
	metadatafound := false
	ovf := false
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			if !metadatafound {
				log.Println("Could not find metadata.json in file: " + location)
				if ovf {
					log.Println("Found OVF, creating for VirtualBox: " + location)
					metadata := BoxMetadata{}
					metadata.Provider = "virtualbox"
					return metadata, nil
				}
			}
			break
		}
		if err != nil {
			log.Fatalln(err)
		}

		if filepath.Ext(hdr.Name) == ".ovf" {
			ovf = true
		}

		if hdr.Name == "metadata.json" {
			metadatafound = true
			metadata := BoxMetadata{}

			boxmetadata := BoxMetadata{}

			buf := new(bytes.Buffer)
			buf.ReadFrom(tr)

			json.Unmarshal(buf.Bytes(), &boxmetadata)
			metadata.Provider = boxmetadata.Provider
			return metadata, nil
		}

	}
	return BoxMetadata{}, errors.New("box: could not find metadata.json or box malformed")
}

// Gets the provider information fromt the Vagrant .box files using Zip
func getProviderFromZip(src string) (BoxMetadata, error) {
	r, err := zip.OpenReader(src)
	if err != nil {
		return BoxMetadata{}, err
	}
	defer r.Close()

	for _, f := range r.File {

		if f.Name == "metadata.json" {
			log.Println("ZIP!!!")
		}

		rc, err := f.Open()
		if err != nil {
			return BoxMetadata{}, err
		}
		defer rc.Close()

		metadata := BoxMetadata{}

		boxmetadata := BoxMetadata{}

		buf := new(bytes.Buffer)
		buf.ReadFrom(rc)

		json.Unmarshal(buf.Bytes(), &boxmetadata)
		metadata.Provider = boxmetadata.Provider
		return metadata, nil

	}

	return BoxMetadata{}, nil
}

type BoxMetadata struct {
	Provider string `json:"provider"`
}

type SimpleBox struct {
	Username string
	Boxname  string
	Location string
	Provider string
}

type Box struct {
	Created             string    `json:"created_at"`
	Updated             string    `json:"updated_at"`
	Tag                 string    `json:"tag"`
	Name                string    `json:"name"`
	ShortDescription    string    `json:"short_description"`
	DescriptionHtml     string    `json:"description_html"`
	DescriptionMarkdown string    `json:"description_markdown"`
	Username            string    `json:"username"`
	Private             bool      `json:"private"`
	CurrentVersion      Version   `json:"current_version"`
	Versions            []Version `json:"versions"`
	LocalBoxFile        string    `json:"-"`
}

type Version struct {
	Version             string     `json:"version"`
	Status              string     `json:"status"`
	DescriptionHtml     string     `json:"description_html"`
	DescriptionMarkdown string     `json:"description_markdown"`
	Created             string     `json:"created_at"`
	Updated             string     `json:"updated_at"`
	Number              int        `json:"number"`
	Downloads           int        `json:"downloads"`
	ReleaseUrl          string     `json:"release_url"`
	RevokeUrl           string     `json:"revoke_url"`
	Providers           []Provider `json:"providers"`
}

type Provider struct {
	Name        string `json:"name"`
	Hosted      string `json:"hosted"`
	HostedToken string `json:"hosted_token"`
	OriginalUrl string `json:"original_url"`
	UploadUrl   string `json:"upload_url"`
	Created     string `json:"created_at"`
	Updated     string `json:"updated_at"`
	DownloadUrl string `json:"download_url"`
	Url         string `json:"url"`
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/enaml-ops/omg-cli/aws-cli"
	"github.com/enaml-ops/omg-cli/azure-cli"
	"github.com/enaml-ops/omg-cli/pluginlib/registry"
	"github.com/enaml-ops/omg-cli/utils"
	"github.com/pivotalservices/gtils/osutils"
	"github.com/xchapter7x/lo"
)

var Version string
var CloudConfigPluginsDir = "./.plugins/cloudconfig"
var ProductPluginsDir = "./.plugins/product"

func main() {
	app := cli.NewApp()
	app.Version = Version
	app.Commands = []cli.Command{
		{
			Name:   "azure",
			Usage:  "azure [--flags] - deploy a bosh to azure",
			Action: azurecli.GetAction(BoshInitDeploy),
			Flags:  azurecli.GetFlags(),
		},
		{
			Name:   "aws",
			Usage:  "aws [--flags] - deploy a bosh to aws",
			Action: awscli.GetAction(BoshInitDeploy),
			Flags:  awscli.GetFlags(),
		},
		{
			Name: "list-cloudconfigs",
			Action: func(c *cli.Context) error {
				fmt.Println("Cloud Configs:")
				for _, plgn := range registry.ListCloudConfigs() {
					fmt.Println(plgn.Name, " - ", plgn.Path, " - ", plgn.Properties)
				}
				return nil
			},
		},
		{
			Name: "list-products",
			Action: func(c *cli.Context) error {
				fmt.Println("Products:")
				for _, plgn := range registry.ListProducts() {
					fmt.Println(plgn.Name, " - ", plgn.Path, " - ", plgn.Properties)
				}
				return nil
			},
		},
		{
			Name:  "register-plugin",
			Usage: "register-plugin -type [cloudconfig, product] -pluginpath <plugin-binary>",
			Action: func(c *cli.Context) (err error) {

				if c.String("type") != "" && c.String("pluginpath") != "" {
					err = registerPlugin(c.String("type"), c.String("pluginpath"))
				}
				return
			},
			Flags: []cli.Flag{
				cli.StringFlag{Name: "type", Value: "product", Usage: "define if the plugin to be registered is a cloud-config or a product"},
				cli.StringFlag{Name: "pluginpath", Value: "", Usage: "the path to the plugin you wish to register"},
			},
		},
		{
			Name:        "deploy-cloudconfig",
			Usage:       "deploy-cloudconfig <cloudconfig-name> [--flags] - deploy a cloudconfig to bosh",
			Flags:       getBoshAuthFlags(),
			Subcommands: utils.GetCloudConfigCommands(CloudConfigPluginsDir),
		},
		{
			Name:        "deploy-product",
			Usage:       "deploy-product <prod-name> [--flags] - deploy a product via bosh",
			Flags:       getBoshAuthFlags(),
			Subcommands: GetProductCommands(ProductPluginsDir),
		},
	}
	app.Run(os.Args)
}

func init() {

	if strings.ToLower(os.Getenv("LOG_LEVEL")) != "debug" {
		log.SetOutput(ioutil.Discard)
	}
}

func registerPlugin(typename, pluginpath string) (err error) {
	var srcPlugin *os.File

	if srcPlugin, err = os.Open(pluginpath); err == nil {
		defer srcPlugin.Close()

		switch typename {
		case "cloudconfig":
			dstFilepath := path.Join(CloudConfigPluginsDir, path.Base(pluginpath))
			err = copyPlugin(srcPlugin, dstFilepath)

		case "product":
			dstFilepath := path.Join(ProductPluginsDir, path.Base(pluginpath))
			err = copyPlugin(srcPlugin, dstFilepath)

		default:
			err = errors.New("invalid type selected")
			lo.G.Error("error: ", err)
		}
	}
	return
}

func copyPlugin(src io.Reader, dst string) (err error) {
	var dstPlugin *os.File
	if dstPlugin, err = osutils.SafeCreate(dst); err == nil {
		defer dstPlugin.Close()
		_, err = io.Copy(dstPlugin, src)
		os.Chmod(dst, 755)
	}
	return
}

func GetProductCommands(target string) (commands []cli.Command) {
	files, _ := ioutil.ReadDir(target)
	for _, f := range files {
		lo.G.Debug("registering: ", f.Name())
		pluginPath := path.Join(target, f.Name())
		flags, _ := registry.RegisterProduct(pluginPath)

		commands = append(commands, cli.Command{
			Name:  f.Name(),
			Usage: "deploy the " + f.Name() + " product",
			Flags: flags,
			Action: func(c *cli.Context) error {
				client, productDeployment := registry.GetProductReference(pluginPath)
				defer client.Kill()
				_ = productDeployment
				//call bosh
				//deploymentManifest := productDeployment.GetProduct(c.Parent().Args())
				return nil
			},
		})
	}
	lo.G.Debug("registered product plugins: ", registry.ListProducts())
	return
}

func getBoshAuthFlags() []cli.Flag {
	return []cli.Flag{
		cli.StringFlag{Name: "bosh-url", Value: "https://mybosh.com", Usage: "this is the url or ip of your bosh director"},
		cli.IntFlag{Name: "bosh-port", Value: 25555, Usage: "this is the port of your bosh director"},
		cli.StringFlag{Name: "bosh-user", Value: "bosh", Usage: "this is the username for your bosh director"},
		cli.StringFlag{Name: "bosh-pass", Value: "", Usage: "this is the pasword for your bosh director"},
		cli.BoolFlag{Name: "ssl-ignore", Usage: "ingore ssl self signed cert warnings"},
		cli.BoolFlag{Name: "print-manifest", Usage: "if you would simply like to output a manifest the set this flag as true."},
	}
}
<commit_msg>Add vsphere cli registration<commit_after>package main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/enaml-ops/omg-cli/aws-cli"
	"github.com/enaml-ops/omg-cli/azure-cli"
	"github.com/enaml-ops/omg-cli/pluginlib/registry"
	"github.com/enaml-ops/omg-cli/utils"
	"github.com/enaml-ops/omg-cli/vsphere-cli"
	"github.com/pivotalservices/gtils/osutils"
	"github.com/xchapter7x/lo"
)

var Version string
var CloudConfigPluginsDir = "./.plugins/cloudconfig"
var ProductPluginsDir = "./.plugins/product"

func main() {
	app := cli.NewApp()
	app.Version = Version
	app.Commands = []cli.Command{
		{
			Name:   "azure",
			Usage:  "azure [--flags] - deploy a bosh to azure",
			Action: azurecli.GetAction(BoshInitDeploy),
			Flags:  azurecli.GetFlags(),
		},
		{
			Name:   "aws",
			Usage:  "aws [--flags] - deploy a bosh to aws",
			Action: awscli.GetAction(BoshInitDeploy),
			Flags:  awscli.GetFlags(),
		},
		{
			Name:   "vsphere",
			Usage:  "vsphere [--flags] - deploy a bosh to vsphere",
			Action: vspherecli.GetAction(BoshInitDeploy),
			Flags:  vspherecli.GetFlags(),
		},
		{
			Name: "list-cloudconfigs",
			Action: func(c *cli.Context) error {
				fmt.Println("Cloud Configs:")
				for _, plgn := range registry.ListCloudConfigs() {
					fmt.Println(plgn.Name, " - ", plgn.Path, " - ", plgn.Properties)
				}
				return nil
			},
		},
		{
			Name: "list-products",
			Action: func(c *cli.Context) error {
				fmt.Println("Products:")
				for _, plgn := range registry.ListProducts() {
					fmt.Println(plgn.Name, " - ", plgn.Path, " - ", plgn.Properties)
				}
				return nil
			},
		},
		{
			Name:  "register-plugin",
			Usage: "register-plugin -type [cloudconfig, product] -pluginpath <plugin-binary>",
			Action: func(c *cli.Context) (err error) {

				if c.String("type") != "" && c.String("pluginpath") != "" {
					err = registerPlugin(c.String("type"), c.String("pluginpath"))
				}
				return
			},
			Flags: []cli.Flag{
				cli.StringFlag{Name: "type", Value: "product", Usage: "define if the plugin to be registered is a cloud-config or a product"},
				cli.StringFlag{Name: "pluginpath", Value: "", Usage: "the path to the plugin you wish to register"},
			},
		},
		{
			Name:        "deploy-cloudconfig",
			Usage:       "deploy-cloudconfig <cloudconfig-name> [--flags] - deploy a cloudconfig to bosh",
			Flags:       getBoshAuthFlags(),
			Subcommands: utils.GetCloudConfigCommands(CloudConfigPluginsDir),
		},
		{
			Name:        "deploy-product",
			Usage:       "deploy-product <prod-name> [--flags] - deploy a product via bosh",
			Flags:       getBoshAuthFlags(),
			Subcommands: GetProductCommands(ProductPluginsDir),
		},
	}
	app.Run(os.Args)
}

func init() {

	if strings.ToLower(os.Getenv("LOG_LEVEL")) != "debug" {
		log.SetOutput(ioutil.Discard)
	}
}

func registerPlugin(typename, pluginpath string) (err error) {
	var srcPlugin *os.File

	if srcPlugin, err = os.Open(pluginpath); err == nil {
		defer srcPlugin.Close()

		switch typename {
		case "cloudconfig":
			dstFilepath := path.Join(CloudConfigPluginsDir, path.Base(pluginpath))
			err = copyPlugin(srcPlugin, dstFilepath)

		case "product":
			dstFilepath := path.Join(ProductPluginsDir, path.Base(pluginpath))
			err = copyPlugin(srcPlugin, dstFilepath)

		default:
			err = errors.New("invalid type selected")
			lo.G.Error("error: ", err)
		}
	}
	return
}

func copyPlugin(src io.Reader, dst string) (err error) {
	var dstPlugin *os.File
	if dstPlugin, err = osutils.SafeCreate(dst); err == nil {
		defer dstPlugin.Close()
		_, err = io.Copy(dstPlugin, src)
		os.Chmod(dst, 755)
	}
	return
}

func GetProductCommands(target string) (commands []cli.Command) {
	files, _ := ioutil.ReadDir(target)
	for _, f := range files {
		lo.G.Debug("registering: ", f.Name())
		pluginPath := path.Join(target, f.Name())
		flags, _ := registry.RegisterProduct(pluginPath)

		commands = append(commands, cli.Command{
			Name:  f.Name(),
			Usage: "deploy the " + f.Name() + " product",
			Flags: flags,
			Action: func(c *cli.Context) error {
				client, productDeployment := registry.GetProductReference(pluginPath)
				defer client.Kill()
				_ = productDeployment
				//call bosh
				//deploymentManifest := productDeployment.GetProduct(c.Parent().Args())
				return nil
			},
		})
	}
	lo.G.Debug("registered product plugins: ", registry.ListProducts())
	return
}

func getBoshAuthFlags() []cli.Flag {
	return []cli.Flag{
		cli.StringFlag{Name: "bosh-url", Value: "https://mybosh.com", Usage: "this is the url or ip of your bosh director"},
		cli.IntFlag{Name: "bosh-port", Value: 25555, Usage: "this is the port of your bosh director"},
		cli.StringFlag{Name: "bosh-user", Value: "bosh", Usage: "this is the username for your bosh director"},
		cli.StringFlag{Name: "bosh-pass", Value: "", Usage: "this is the pasword for your bosh director"},
		cli.BoolFlag{Name: "ssl-ignore", Usage: "ingore ssl self signed cert warnings"},
		cli.BoolFlag{Name: "print-manifest", Usage: "if you would simply like to output a manifest the set this flag as true."},
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/user"
	"strings"
	"text/template"
)

// Server struct holds Droplet data for simpler syntax.
type Server struct {
	Hostname string
	IP       string
}

type Droplets struct {
	Droplets []Droplet
}

type Droplet struct {
	Name     string `json:"name"`
	Networks Network
}

type Network struct {
	Version []ProtocolStruct `json:"v4"`
}

type ProtocolStruct struct {
	IP string `json:"ip_address"`
}

const templ = `

#START dossh{{range .}}
Host {{.Hostname}}
	Hostname {{.IP}}
	Port 22
	User root
	IdentitiesOnly yes
{{end}}
#END dossh
`

// getServers connects to DO API and fetches droplet list.
func getServers(token string) []byte {

	req, err := http.NewRequest("GET", "https://api.digitalocean.com/v2/droplets", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	if err != nil {
		panic(err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	return body

}

// parseServers unmarshals the JSON data from DO API and returns []Server struct
func parseServers(body []byte) []Server {

	var d Droplets
	err := json.Unmarshal(body, &d)
	if err != nil {
		panic(err)
	}

	var servers []Server
	for _, droplet := range d.Droplets {
		servers = append(servers, Server{droplet.Name, droplet.Networks.Version[0].IP})
	}

	return servers
}

// templateFrom creates a new text template from given []Server struct. It also fetches the old .ssh/config file
// and parses it so that the old dossh values are removed. It then inserts the new values to the old file template
// and finally returns the completed byte array.
func templateFrom(servers []Server) []byte {
	t, err := template.New("config").Parse(templ)
	if err != nil {
		panic(err)
	}

	var doc bytes.Buffer
	err = t.Execute(&doc, servers)
	if err != nil {
		panic(err)
	}

	usr, err := user.Current()
	if err != nil {
		panic(err)
	}

	file, err := ioutil.ReadFile(usr.HomeDir + "/.ssh/config")
	if err != nil {
		panic(err)
	}

	config := string(file)

	e := strings.Split(config, "#START dossh")
	h := strings.Split(config, "#END dossh")

	newconfig := e[0] + h[1]

	return []byte(strings.TrimSpace(newconfig) + doc.String())
}

// saveconfig saves the new .ssh/config file with file permissions 0612.
func saveconfig(document []byte) {
	usr, err := user.Current()
	if err != nil {
		panic(err)
	}
	err = ioutil.WriteFile(usr.HomeDir+"/.ssh/config", document, 0612)
	if err != nil {
		panic(err)
	}
}

func main() {

	body := getServers(os.Args[1])

	servers := parseServers(body)

	document := templateFrom(servers)

	saveconfig(document)

	fmt.Println("SSH config file updated successfully.")
}
<commit_msg>append dossh rules to ssh config if none yet exist, see #1<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/user"
	"strings"
	"text/template"
)

// Server struct holds Droplet data for simpler syntax.
type Server struct {
	Hostname string
	IP       string
}

type Droplets struct {
	Droplets []Droplet
}

type Droplet struct {
	Name     string `json:"name"`
	Networks Network
}

type Network struct {
	Version []ProtocolStruct `json:"v4"`
}

type ProtocolStruct struct {
	IP string `json:"ip_address"`
}

const templ = `

#START dossh{{range .}}
Host {{.Hostname}}
	Hostname {{.IP}}
	Port 22
	User root
	IdentitiesOnly yes
{{end}}
#END dossh
`

// getServers connects to DO API and fetches droplet list.
func getServers(token string) []byte {

	req, err := http.NewRequest("GET", "https://api.digitalocean.com/v2/droplets", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	if err != nil {
		panic(err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	return body

}

// parseServers unmarshals the JSON data from DO API and returns []Server struct
func parseServers(body []byte) []Server {

	var d Droplets
	err := json.Unmarshal(body, &d)
	if err != nil {
		panic(err)
	}

	var servers []Server
	for _, droplet := range d.Droplets {
		servers = append(servers, Server{droplet.Name, droplet.Networks.Version[0].IP})
	}

	return servers
}

// templateFrom creates a new text template from given []Server struct. It also fetches the old .ssh/config file
// and parses it so that the old dossh values are removed. It then inserts the new values to the old file template
// and finally returns the completed byte array.
func templateFrom(servers []Server) []byte {
	t, err := template.New("config").Parse(templ)
	if err != nil {
		panic(err)
	}

	var doc bytes.Buffer
	err = t.Execute(&doc, servers)
	if err != nil {
		panic(err)
	}

	usr, err := user.Current()
	if err != nil {
		panic(err)
	}

	file, err := ioutil.ReadFile(usr.HomeDir + "/.ssh/config")
	if err != nil {
		panic(err)
	}

	firstRun := strings.Contains(string(file), "#START dossh")

	if !firstRun {
		return []byte(strings.TrimSpace(string(file) + doc.String()))
	}

	config := string(file)

	e := strings.Split(config, "#START dossh")
	h := strings.Split(config, "#END dossh")

	newconfig := e[0] + h[1]

	return []byte(strings.TrimSpace(newconfig) + doc.String())
}

// saveconfig saves the new .ssh/config file with file permissions 0612.
func saveconfig(document []byte) {
	usr, err := user.Current()
	if err != nil {
		panic(err)
	}
	err = ioutil.WriteFile(usr.HomeDir+"/.ssh/config", document, 0612)
	if err != nil {
		panic(err)
	}
}

func main() {

	body := getServers(os.Args[1])

	servers := parseServers(body)

	document := templateFrom(servers)

	saveconfig(document)

	fmt.Println("SSH config file updated successfully.")
}
<|endoftext|>