<commit_before>package cmd

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"errors"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/nullbio/abcweb/cert"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

type newConfig struct {
	AppPath        string
	ImportPath     string
	AppName        string
	TLSCommonName  string
	ProdStorer     string
	DevStorer      string
	DefaultEnv     string
	NoGitIgnore    bool
	NoBootstrap    bool
	NoFontAwesome  bool
	NoLiveReload   bool
	NoTLSCerts     bool
	NoReadme       bool
	NoConfig       bool
	ForceOverwrite bool
	TLSCertsOnly   bool
}

const (
	templatesDirectory = "templates"
	basePackage        = "github.com/nullbio/abcweb"
)

var skipDirs = []string{
	// i18n is not implemented yet
	"i18n",
}

var fontAwesomeFiles = []string{
	"font-awesome.min.css",
	"FontAwesome.otf",
	"fontawesome-webfont.eot",
	"fontawesome-webfont.svg",
	"fontawesome-webfont.ttf",
	"fontawesome-webfont.woff",
	"fontawesome-webfont.woff2",
}

var bootstrapFiles = []string{
	"bootstrap.css",
	"bootstrap-flex.css",
	"bootstrap-grid.css",
	"bootstrap-reboot.css",
	"bootstrap.js",
}

var newCmdConfig newConfig

// newCmd represents the new command
var newCmd = &cobra.Command{
	Use:   "new <import_path> [flags]",
	Short: "Generate a new ABCWeb application.",
	Long: `The 'abcweb new' command generates a new ABCWeb application with a
default directory structure and configuration at the Go src import path you specify.

The app will generate in $GOPATH/src/<import_path>.
`,
	Example: "abcweb new github.com/yourusername/myapp",
	PreRunE: newCmdPreRun,
	RunE:    newCmdRun,
}

func init() {
	newCmd.Flags().StringP("sessions-prod-storer", "p", "disk", "Session storer to use in production mode")
	newCmd.Flags().StringP("sessions-dev-storer", "d", "cookie", "Session storer to use in development mode")
	newCmd.Flags().StringP("tls-common-name", "", "localhost", "Common Name for generated TLS certificate")
	newCmd.Flags().StringP("default-env", "", "prod", "Default APP_ENV to use when starting server")
	newCmd.Flags().BoolP("no-gitignore", "g", false, "Skip .gitignore file")
	newCmd.Flags().BoolP("no-twitter-bootstrap", "t", false, "Skip Twitter Bootstrap 4 inclusion")
	newCmd.Flags().BoolP("no-font-awesome", "f", false, "Skip Font Awesome inclusion")
	newCmd.Flags().BoolP("no-livereload", "l", false, "Don't support LiveReload")
	newCmd.Flags().BoolP("no-tls-certs", "s", false, "Skip generation of self-signed TLS cert files")
	newCmd.Flags().BoolP("no-readme", "r", false, "Skip README.md files")
	newCmd.Flags().BoolP("no-config", "c", false, "Skip default config.toml file")
	newCmd.Flags().BoolP("force-overwrite", "", false, "Force overwrite of existing files in your import_path")
	newCmd.Flags().BoolP("tls-certs-only", "", false, "Only generate self-signed TLS cert files")

	RootCmd.AddCommand(newCmd)
	viper.BindPFlags(newCmd.Flags())
}

func newCmdPreRun(cmd *cobra.Command, args []string) error {
	var err error

	newCmdConfig = newConfig{
		NoGitIgnore:    viper.GetBool("no-gitignore"),
		NoBootstrap:    viper.GetBool("no-twitter-bootstrap"),
		NoFontAwesome:  viper.GetBool("no-font-awesome"),
		NoLiveReload:   viper.GetBool("no-livereload"),
		NoTLSCerts:     viper.GetBool("no-tls-certs"),
		TLSCertsOnly:   viper.GetBool("tls-certs-only"),
		NoReadme:       viper.GetBool("no-readme"),
		NoConfig:       viper.GetBool("no-config"),
		ForceOverwrite: viper.GetBool("force-overwrite"),
		ProdStorer:     viper.GetString("sessions-prod-storer"),
		DevStorer:      viper.GetString("sessions-dev-storer"),
		TLSCommonName:  viper.GetString("tls-common-name"),
		DefaultEnv:     viper.GetString("default-env"),
	}

	newCmdConfig.AppPath, newCmdConfig.ImportPath, newCmdConfig.AppName, err = getAppPath(args)
	return err
}

func newCmdRun(cmd *cobra.Command, args []string) error {
	fmt.Println("Generating in:", newCmdConfig.AppPath)

	// Make the app directory if it doesnt already exist.
	// Can get dir not exist errors on --tls-cert-only runs if we don't do this.
	err := os.MkdirAll(newCmdConfig.AppPath, 0755)
	if err != nil {
		return err
	}

	if !newCmdConfig.TLSCertsOnly {
		// Get base path containing templates folder and source files
		p, _ := build.Default.Import(basePackage, "", build.FindOnly)
		if p == nil || len(p.Dir) == 0 {
			return errors.New("cannot locate base path containing templates folder")
		}

		// Walk all files in the templates folder
		basePath := filepath.Join(p.Dir, "templates")
		err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
			return newCmdWalk(basePath, path, info, err)
		})
		if err != nil {
			return err
		}
	}

	// Generate TLS certs if requested
	if !newCmdConfig.NoTLSCerts {
		err := generateTLSCerts()
		if err != nil {
			return err
		}
	}

	fmt.Println("\tresult -> Finished")
	return nil
}

func generateTLSCerts() error {
	certFilePath := filepath.Join(newCmdConfig.AppPath, "cert.pem")
	privateKeyPath := filepath.Join(newCmdConfig.AppPath, "private.key")

	if !newCmdConfig.TLSCertsOnly {
		_, err := os.Stat(certFilePath)
		if err == nil || (err != nil && !os.IsNotExist(err)) {
			return nil
		}
	}

	fmt.Println("\trun -> TLS Certificate Generator")
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	err = cert.WriteCertFile(certFilePath, newCmdConfig.AppName,
		newCmdConfig.TLSCommonName, &privateKey.PublicKey, privateKey)
	if err != nil {
		return err
	}
	fmt.Printf("\tcreate -> %s\n", filepath.Join(newCmdConfig.AppName, "cert.pem"))

	if err := cert.WritePrivateKey(privateKeyPath, privateKey); err != nil {
		return err
	}
	fmt.Printf("\tcreate -> %s\n", filepath.Join(newCmdConfig.AppName, "private.key"))

	return nil
}

func newCmdWalk(basePath string, path string, info os.FileInfo, err error) error {
	// Skip files and dirs depending on command line args
	if skip, err := processSkips(basePath, path, info); skip {
		return err
	}

	// Get the path for command line output, and the output target fullPath
	cleanPath, fullPath := getProcessedPaths(path, string(os.PathSeparator), newCmdConfig)

	fileContents := &bytes.Buffer{}

	// Check if the output file or folder already exists
	_, err = os.Stat(fullPath)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	outputExists := err == nil

	// Dirs only get created if they don't already exist
	if info.IsDir() {
		// Don't bother trying to create the folder if it already exists.
		// Return now so we don't get "created" output
		if outputExists {
			return nil
		}

		err = os.MkdirAll(fullPath, 0755)
		if err != nil {
			return err
		}
	} else {
		// Files only get created if they don't already exist, or force overwrite is enabled
		if !newCmdConfig.ForceOverwrite && outputExists {
			return nil
		}

		rawFileContents, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}

		// Only process the file as a template if it has the .tmpl extension
		if strings.HasSuffix(path, ".tmpl") {
			t, err := template.New("").Funcs(templateFuncs).Parse(string(rawFileContents))
			if err != nil {
				return err
			}

			err = t.Execute(fileContents, newCmdConfig)
			if err != nil {
				return err
			}
		}

		err = ioutil.WriteFile(fullPath, fileContents.Bytes(), 0644)
		if err != nil {
			return err
		}
	}

	fmt.Printf("\tcreate -> %s\n", cleanPath)
	return nil
}

func processSkips(basePath string, path string, info os.FileInfo) (skip bool, err error) {
	// Ignore the root folder
	if path == basePath && info.IsDir() {
		return true, nil
	}

	// Skip directories defined in skipDirs slice
	if info.IsDir() {
		for _, skipDir := range skipDirs {
			if info.Name() == skipDir {
				return true, filepath.SkipDir
			}
		}
	}

	// Skip readme files if requested
	if newCmdConfig.NoReadme {
		if info.Name() == "README.md" || info.Name() == "README.md.tmpl" {
			return true, nil
		}
	}

	// Skip gitignore if requested
	if newCmdConfig.NoGitIgnore {
		if info.Name() == ".gitignore" || info.Name() == ".gitignore.tmpl" {
			return true, nil
		}
	}

	// Skip default config.toml if requested
	if newCmdConfig.NoConfig {
		if info.Name() == "config.toml" || info.Name() == "config.toml.tmpl" {
			return true, nil
		}
	}

	// Skip FontAwesome files if requested
	if newCmdConfig.NoFontAwesome {
		for _, faFile := range fontAwesomeFiles {
			if info.Name() == faFile || info.Name() == faFile+".tmpl" {
				return true, nil
			}
		}
	}

	// Skip Twitter Bootstrap files if requested
	if newCmdConfig.NoBootstrap {
		for _, bsFile := range bootstrapFiles {
			if info.Name() == bsFile || info.Name() == bsFile+".tmpl" {
				return true, nil
			}
		}
	}

	return false, nil
}

func getAppPath(args []string) (appPath string, importPath string, appName string, err error) {
	if len(args) == 0 {
		return "", "", "", errors.New("must provide an app path")
	}

	appPath = filepath.Clean(args[0])
	importPath = appPath

	// Somewhat validate provided app path, valid paths will have at least 2 components
	appPathChunks := strings.Split(appPath, string(os.PathSeparator))
	if len(appPathChunks) < 2 {
		return "", "", "", errors.New("invalid app path provided, see --help for example")
	}

	_, appName = filepath.Split(appPath)

	if appName == "" || appName == "." || appName == "/" {
		return "", "", "", errors.New("app path must contain an output folder name")
	}

	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return "", "", "", errors.New("cannot get GOPATH from environment variables")
	}

	// If GOPATH has more than one directory, prompt user to choose which one
	goPathChunks := strings.Split(gopath, string(os.PathListSeparator))
	if len(goPathChunks) > 1 {
		fmt.Println("Your GOPATH has multiple paths, select your desired GOPATH:")
		for pos, chunk := range goPathChunks {
			fmt.Printf("[%d] %s\n", pos+1, chunk)
		}

		num := 0
		for num < 1 || num > len(goPathChunks) {
			fmt.Printf("Select GOPATH number: ")
			fmt.Scanln(&num)
			fmt.Println(num)
		}

		gopath = goPathChunks[num-1]
	}

	// Target directory is $GOPATH/src/<import_path>
	appPath = filepath.Join(gopath, "src", appPath)
	return appPath, importPath, appName, nil
}

func getProcessedPaths(path string, pathSeparator string, config newConfig) (cleanPath string, fullPath string) {
	chunks := strings.Split(path, pathSeparator)
	var newChunks []string

	var found int
	for i := 0; i < len(chunks); i++ {
		if chunks[i] == templatesDirectory {
			found = i
			break
		}
	}

	newChunks = append(newChunks, chunks[found:]...)

	// Make cleanPath for results output
	newChunks[0] = config.AppName
	newChunks[len(newChunks)-1] = strings.TrimSuffix(newChunks[len(newChunks)-1], ".tmpl")
	cleanPath = strings.Join(newChunks, string(os.PathSeparator))

	// Make fullPath for destination save
	newChunks[0] = config.AppPath
	fullPath = strings.Join(newChunks, string(os.PathSeparator))

	return cleanPath, fullPath
}
<commit_msg>Fix file perm from 644 to 664<commit_after>package cmd

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"errors"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/nullbio/abcweb/cert"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

type newConfig struct {
	AppPath        string
	ImportPath     string
	AppName        string
	TLSCommonName  string
	ProdStorer     string
	DevStorer      string
	DefaultEnv     string
	NoGitIgnore    bool
	NoBootstrap    bool
	NoFontAwesome  bool
	NoLiveReload   bool
	NoTLSCerts     bool
	NoReadme       bool
	NoConfig       bool
	ForceOverwrite bool
	TLSCertsOnly   bool
}

const (
	templatesDirectory = "templates"
	basePackage        = "github.com/nullbio/abcweb"
)

var skipDirs = []string{
	// i18n is not implemented yet
	"i18n",
}

var fontAwesomeFiles = []string{
	"font-awesome.min.css",
	"FontAwesome.otf",
	"fontawesome-webfont.eot",
	"fontawesome-webfont.svg",
	"fontawesome-webfont.ttf",
	"fontawesome-webfont.woff",
	"fontawesome-webfont.woff2",
}

var bootstrapFiles = []string{
	"bootstrap.css",
	"bootstrap-flex.css",
	"bootstrap-grid.css",
	"bootstrap-reboot.css",
	"bootstrap.js",
}

var newCmdConfig newConfig

// newCmd represents the new command
var newCmd = &cobra.Command{
	Use:   "new <import_path> [flags]",
	Short: "Generate a new ABCWeb application.",
	Long: `The 'abcweb new' command generates a new ABCWeb application with a
default directory structure and configuration at the Go src import path you specify.

The app will generate in $GOPATH/src/<import_path>.
`,
	Example: "abcweb new github.com/yourusername/myapp",
	PreRunE: newCmdPreRun,
	RunE:    newCmdRun,
}

func init() {
	newCmd.Flags().StringP("sessions-prod-storer", "p", "disk", "Session storer to use in production mode")
	newCmd.Flags().StringP("sessions-dev-storer", "d", "cookie", "Session storer to use in development mode")
	newCmd.Flags().StringP("tls-common-name", "", "localhost", "Common Name for generated TLS certificate")
	newCmd.Flags().StringP("default-env", "", "prod", "Default APP_ENV to use when starting server")
	newCmd.Flags().BoolP("no-gitignore", "g", false, "Skip .gitignore file")
	newCmd.Flags().BoolP("no-twitter-bootstrap", "t", false, "Skip Twitter Bootstrap 4 inclusion")
	newCmd.Flags().BoolP("no-font-awesome", "f", false, "Skip Font Awesome inclusion")
	newCmd.Flags().BoolP("no-livereload", "l", false, "Don't support LiveReload")
	newCmd.Flags().BoolP("no-tls-certs", "s", false, "Skip generation of self-signed TLS cert files")
	newCmd.Flags().BoolP("no-readme", "r", false, "Skip README.md files")
	newCmd.Flags().BoolP("no-config", "c", false, "Skip default config.toml file")
	newCmd.Flags().BoolP("force-overwrite", "", false, "Force overwrite of existing files in your import_path")
	newCmd.Flags().BoolP("tls-certs-only", "", false, "Only generate self-signed TLS cert files")

	RootCmd.AddCommand(newCmd)
	viper.BindPFlags(newCmd.Flags())
}

func newCmdPreRun(cmd *cobra.Command, args []string) error {
	var err error

	newCmdConfig = newConfig{
		NoGitIgnore:    viper.GetBool("no-gitignore"),
		NoBootstrap:    viper.GetBool("no-twitter-bootstrap"),
		NoFontAwesome:  viper.GetBool("no-font-awesome"),
		NoLiveReload:   viper.GetBool("no-livereload"),
		NoTLSCerts:     viper.GetBool("no-tls-certs"),
		TLSCertsOnly:   viper.GetBool("tls-certs-only"),
		NoReadme:       viper.GetBool("no-readme"),
		NoConfig:       viper.GetBool("no-config"),
		ForceOverwrite: viper.GetBool("force-overwrite"),
		ProdStorer:     viper.GetString("sessions-prod-storer"),
		DevStorer:      viper.GetString("sessions-dev-storer"),
		TLSCommonName:  viper.GetString("tls-common-name"),
		DefaultEnv:     viper.GetString("default-env"),
	}

	newCmdConfig.AppPath, newCmdConfig.ImportPath, newCmdConfig.AppName, err = getAppPath(args)
	return err
}

func newCmdRun(cmd *cobra.Command, args []string) error {
	fmt.Println("Generating in:", newCmdConfig.AppPath)

	// Make the app directory if it doesnt already exist.
	// Can get dir not exist errors on --tls-cert-only runs if we don't do this.
	err := os.MkdirAll(newCmdConfig.AppPath, 0755)
	if err != nil {
		return err
	}

	if !newCmdConfig.TLSCertsOnly {
		// Get base path containing templates folder and source files
		p, _ := build.Default.Import(basePackage, "", build.FindOnly)
		if p == nil || len(p.Dir) == 0 {
			return errors.New("cannot locate base path containing templates folder")
		}

		// Walk all files in the templates folder
		basePath := filepath.Join(p.Dir, "templates")
		err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
			return newCmdWalk(basePath, path, info, err)
		})
		if err != nil {
			return err
		}
	}

	// Generate TLS certs if requested
	if !newCmdConfig.NoTLSCerts {
		err := generateTLSCerts()
		if err != nil {
			return err
		}
	}

	fmt.Println("\tresult -> Finished")
	return nil
}

func generateTLSCerts() error {
	certFilePath := filepath.Join(newCmdConfig.AppPath, "cert.pem")
	privateKeyPath := filepath.Join(newCmdConfig.AppPath, "private.key")

	if !newCmdConfig.TLSCertsOnly {
		_, err := os.Stat(certFilePath)
		if err == nil || (err != nil && !os.IsNotExist(err)) {
			return nil
		}
	}

	fmt.Println("\trun -> TLS Certificate Generator")
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	err = cert.WriteCertFile(certFilePath, newCmdConfig.AppName,
		newCmdConfig.TLSCommonName, &privateKey.PublicKey, privateKey)
	if err != nil {
		return err
	}
	fmt.Printf("\tcreate -> %s\n", filepath.Join(newCmdConfig.AppName, "cert.pem"))

	if err := cert.WritePrivateKey(privateKeyPath, privateKey); err != nil {
		return err
	}
	fmt.Printf("\tcreate -> %s\n", filepath.Join(newCmdConfig.AppName, "private.key"))

	return nil
}

func newCmdWalk(basePath string, path string, info os.FileInfo, err error) error {
	// Skip files and dirs depending on command line args
	if skip, err := processSkips(basePath, path, info); skip {
		return err
	}

	// Get the path for command line output, and the output target fullPath
	cleanPath, fullPath := getProcessedPaths(path, string(os.PathSeparator), newCmdConfig)

	fileContents := &bytes.Buffer{}

	// Check if the output file or folder already exists
	_, err = os.Stat(fullPath)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	outputExists := err == nil

	// Dirs only get created if they don't already exist
	if info.IsDir() {
		// Don't bother trying to create the folder if it already exists.
		// Return now so we don't get "created" output
		if outputExists {
			return nil
		}

		err = os.MkdirAll(fullPath, 0755)
		if err != nil {
			return err
		}
	} else {
		// Files only get created if they don't already exist, or force overwrite is enabled
		if !newCmdConfig.ForceOverwrite && outputExists {
			return nil
		}

		rawFileContents, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}

		// Only process the file as a template if it has the .tmpl extension
		if strings.HasSuffix(path, ".tmpl") {
			t, err := template.New("").Funcs(templateFuncs).Parse(string(rawFileContents))
			if err != nil {
				return err
			}

			err = t.Execute(fileContents, newCmdConfig)
			if err != nil {
				return err
			}
		}

		err = ioutil.WriteFile(fullPath, fileContents.Bytes(), 0664)
		if err != nil {
			return err
		}
	}

	fmt.Printf("\tcreate -> %s\n", cleanPath)
	return nil
}

func processSkips(basePath string, path string, info os.FileInfo) (skip bool, err error) {
	// Ignore the root folder
	if path == basePath && info.IsDir() {
		return true, nil
	}

	// Skip directories defined in skipDirs slice
	if info.IsDir() {
		for _, skipDir := range skipDirs {
			if info.Name() == skipDir {
				return true, filepath.SkipDir
			}
		}
	}

	// Skip readme files if requested
	if newCmdConfig.NoReadme {
		if info.Name() == "README.md" || info.Name() == "README.md.tmpl" {
			return true, nil
		}
	}

	// Skip gitignore if requested
	if newCmdConfig.NoGitIgnore {
		if info.Name() == ".gitignore" || info.Name() == ".gitignore.tmpl" {
			return true, nil
		}
	}

	// Skip default config.toml if requested
	if newCmdConfig.NoConfig {
		if info.Name() == "config.toml" || info.Name() == "config.toml.tmpl" {
			return true, nil
		}
	}

	// Skip FontAwesome files if requested
	if newCmdConfig.NoFontAwesome {
		for _, faFile := range fontAwesomeFiles {
			if info.Name() == faFile || info.Name() == faFile+".tmpl" {
				return true, nil
			}
		}
	}

	// Skip Twitter Bootstrap files if requested
	if newCmdConfig.NoBootstrap {
		for _, bsFile := range bootstrapFiles {
			if info.Name() == bsFile || info.Name() == bsFile+".tmpl" {
				return true, nil
			}
		}
	}

	return false, nil
}

func getAppPath(args []string) (appPath string, importPath string, appName string, err error) {
	if len(args) == 0 {
		return "", "", "", errors.New("must provide an app path")
	}

	appPath = filepath.Clean(args[0])
	importPath = appPath

	// Somewhat validate provided app path, valid paths will have at least 2 components
	appPathChunks := strings.Split(appPath, string(os.PathSeparator))
	if len(appPathChunks) < 2 {
		return "", "", "", errors.New("invalid app path provided, see --help for example")
	}

	_, appName = filepath.Split(appPath)

	if appName == "" || appName == "." || appName == "/" {
		return "", "", "", errors.New("app path must contain an output folder name")
	}

	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return "", "", "", errors.New("cannot get GOPATH from environment variables")
	}

	// If GOPATH has more than one directory, prompt user to choose which one
	goPathChunks := strings.Split(gopath, string(os.PathListSeparator))
	if len(goPathChunks) > 1 {
		fmt.Println("Your GOPATH has multiple paths, select your desired GOPATH:")
		for pos, chunk := range goPathChunks {
			fmt.Printf("[%d] %s\n", pos+1, chunk)
		}

		num := 0
		for num < 1 || num > len(goPathChunks) {
			fmt.Printf("Select GOPATH number: ")
			fmt.Scanln(&num)
			fmt.Println(num)
		}

		gopath = goPathChunks[num-1]
	}

	// Target directory is $GOPATH/src/<import_path>
	appPath = filepath.Join(gopath, "src", appPath)
	return appPath, importPath, appName, nil
}

func getProcessedPaths(path string, pathSeparator string, config newConfig) (cleanPath string, fullPath string) {
	chunks := strings.Split(path, pathSeparator)
	var newChunks []string

	var found int
	for i := 0; i < len(chunks); i++ {
		if chunks[i] == templatesDirectory {
			found = i
			break
		}
	}

	newChunks = append(newChunks, chunks[found:]...)

	// Make cleanPath for results output
	newChunks[0] = config.AppName
	newChunks[len(newChunks)-1] = strings.TrimSuffix(newChunks[len(newChunks)-1], ".tmpl")
	cleanPath = strings.Join(newChunks, string(os.PathSeparator))

	// Make fullPath for destination save
	newChunks[0] = config.AppPath
	fullPath = strings.Join(newChunks, string(os.PathSeparator))

	return cleanPath, fullPath
}
<|endoftext|>


<commit_before>package cmd

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

	"github.com/atotto/clipboard"
	"github.com/b4b4r07/gist/api"
	"github.com/b4b4r07/gist/cli"
	"github.com/b4b4r07/gist/util"
	"github.com/fatih/color"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

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

var newCmd = &cobra.Command{
	Use:   "new [FILE/DIR]",
	Short: "Create a new gist",
	Long:  `Create a new gist. If you pass file/dir paths, upload those files`,
	RunE:  new,
}

type gistItem struct {
	files api.Files
	desc  string
}

func new(cmd *cobra.Command, args []string) error {
	var err error
	var gi gistItem

	gist, err := cli.NewGist()
	if err != nil {
		return err
	}

	// Make Gist from various conditions
	switch {
	case cli.Conf.Flag.FromClipboard:
		gi, err = makeFromClipboard()
	case !terminal.IsTerminal(0):
		gi, err = makeFromStdin()
	case len(args) > 0:
		gi, err = makeFromArguments(args)
	case len(args) == 0:
		gi, err = makeFromEditor()
	}
	if err != nil {
		return err
	}

	item, err := gist.Create(gi.files, gi.desc)
	if err != nil {
		return err
	}

	if cli.Conf.Gist.UseCache {
		cache := cli.NewCache()
		files, err := cache.Load()
		if err != nil {
			return err
		}
		for _, file := range gi.files {
			// append to the top of slice (unshift)
			files, files[0] = append(files[0:1], files[0:]...), api.File{
				ID:          *item.ID,
				ShortID:     api.ShortenID(*item.ID),
				Filename:    file.Filename,
				Description: *item.Description,
				Public:      *item.Public,
			}
		}
		cache.Create(files)
	}

	util.Underline("Created", *item.HTMLURL)
	if cli.Conf.Flag.OpenURL {
		util.Open(*item.HTMLURL)
	}
	return nil
}

func makeFromClipboard() (gi gistItem, err error) {
	content, err := clipboard.ReadAll()
	if err != nil {
		return
	}
	if content == "" {
		return gi, errors.New("clipboard is empty")
	}
	filename, err := util.Scan(color.YellowString("Filename> "), !util.ScanAllowEmpty)
	if err != nil {
		return
	}
	desc, err := util.Scan(color.GreenString("Description> "), util.ScanAllowEmpty)
	if err != nil {
		return
	}
	return gistItem{
		files: api.Files{api.File{
			Filename: filename,
			Content:  content,
		}},
		desc: desc,
	}, nil
}

func makeFromStdin() (gi gistItem, err error) {
	body, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		return
	}
	filename := util.RandomString(20)
	ext := cli.Conf.Gist.FileExt
	if len(ext) > 0 {
		if !strings.HasPrefix(ext, ".") {
			ext = "." + ext
		}
		filename = filename + ext
	}
	return gistItem{
		files: api.Files{api.File{
			Filename: filename,
			Content:  string(body),
		}},
		desc: "",
	}, nil
}

func makeFromEditor() (gi gistItem, err error) {
	filename, err := util.Scan(color.YellowString("Filename> "), !util.ScanAllowEmpty)
	if err != nil {
		return
	}

	filename, title, asBlog := func(filename string) (string, string, bool) {
		if !cli.Conf.Flag.BlogMode {
			return filename, "", false
		}
		switch filepath.Ext(filename) {
		case "", ".md", ".mkd", ".markdown":
			return filename + ".md", "# " + filename, true
		default:
			return filename, filename, false
		}
	}(filename)

	f, err := util.TempFile(filename)
	defer os.Remove(f.Name())
	if asBlog {
		f.Write([]byte(title))
		f.Sync()
	}

	err = cli.Run(cli.Conf.Core.Editor, f.Name())
	if err != nil {
		return
	}

	content, _ := util.FileContent(f.Name())
	if content == title {
		return gi, errors.New("no contents")
	}

	desc, err := util.Scan(color.GreenString("Description> "), util.ScanAllowEmpty)
	if err != nil {
		return
	}

	return gistItem{
		files: api.Files{api.File{
			Filename: filename,
			Content:  content,
		}},
		desc: desc,
	}, nil
}

func makeFromArguments(args []string) (gi gistItem, err error) {
	var (
		gistFiles api.Files
		files     []string
	)

	// Check if the path is directory
	isdir := func(path string) bool {
		if stat, err := os.Stat(path); err == nil && stat.IsDir() {
			return true
		}
		return false
	}

	for _, arg := range args {
		// if the arg is dir, walk within the dir and add them to slice
		// otherwise (regular file), just add it to slice
		if isdir(arg) {
			err = filepath.Walk(arg, func(arg string, info os.FileInfo, err error) error {
				if strings.HasPrefix(arg, ".") {
					return nil
				}
				if info.IsDir() {
					return nil
				}
				files = append(files, arg)
				return nil
			})
			if err != nil {
				return
			}
		} else {
			if !util.Exists(arg) {
				log.Infof("%s: no such file or directory", arg)
				continue
			}
			files = append(files, arg)
		}
	}

	if len(files) == 0 {
		return gi, errors.New("no files to be able create")
	}

	for _, file := range files {
		fmt.Fprintf(color.Output, "%s %s\n", color.YellowString("Filename>"), file)
		content, _ := util.FileContent(file)
		gistFiles = append(gistFiles, api.File{
			Filename: filepath.Base(file),
			Content:  content,
		})
	}

	desc, err := util.Scan(color.GreenString("Description> "), util.ScanAllowEmpty)
	if err != nil {
		return
	}

	return gistItem{
		files: gistFiles,
		desc:  desc,
	}, nil
}

func init() {
	RootCmd.AddCommand(newCmd)
	newCmd.Flags().BoolVarP(&cli.Conf.Flag.OpenURL, "open", "o", false, "Open with the default browser")
	newCmd.Flags().BoolVarP(&cli.Conf.Flag.NewPrivate, "private", "p", false, "Create as private gist")
	newCmd.Flags().BoolVarP(&cli.Conf.Flag.FromClipboard, "from-clipboard", "c", false, "Create gist from clipboard")
}
<commit_msg>When creating a new file, allow to change file name if only one file is passed as argument<commit_after>package cmd

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

	"github.com/atotto/clipboard"
	"github.com/b4b4r07/gist/api"
	"github.com/b4b4r07/gist/cli"
	"github.com/b4b4r07/gist/util"
	"github.com/fatih/color"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

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

var newCmd = &cobra.Command{
	Use:   "new [FILE/DIR]",
	Short: "Create a new gist",
	Long:  `Create a new gist. If you pass file/dir paths, upload those files`,
	RunE:  new,
}

type gistItem struct {
	files api.Files
	desc  string
}

func new(cmd *cobra.Command, args []string) error {
	var err error
	var gi gistItem

	gist, err := cli.NewGist()
	if err != nil {
		return err
	}

	// Make Gist from various conditions
	switch {
	case cli.Conf.Flag.FromClipboard:
		gi, err = makeFromClipboard()
	case !terminal.IsTerminal(0):
		gi, err = makeFromStdin()
	case len(args) > 0:
		gi, err = makeFromArguments(args)
	case len(args) == 0:
		gi, err = makeFromEditor()
	}
	if err != nil {
		return err
	}

	item, err := gist.Create(gi.files, gi.desc)
	if err != nil {
		return err
	}

	if cli.Conf.Gist.UseCache {
		cache := cli.NewCache()
		files, err := cache.Load()
		if err != nil {
			return err
		}
		for _, file := range gi.files {
			// append to the top of slice (unshift)
			files, files[0] = append(files[0:1], files[0:]...), api.File{
				ID:          *item.ID,
				ShortID:     api.ShortenID(*item.ID),
				Filename:    file.Filename,
				Description: *item.Description,
				Public:      *item.Public,
			}
		}
		cache.Create(files)
	}

	util.Underline("Created", *item.HTMLURL)
	if cli.Conf.Flag.OpenURL {
		util.Open(*item.HTMLURL)
	}
	return nil
}

func makeFromClipboard() (gi gistItem, err error) {
	content, err := clipboard.ReadAll()
	if err != nil {
		return
	}
	if content == "" {
		return gi, errors.New("clipboard is empty")
	}
	filename, err := util.Scan(color.YellowString("Filename> "), !util.ScanAllowEmpty)
	if err != nil {
		return
	}
	desc, err := util.Scan(color.GreenString("Description> "), util.ScanAllowEmpty)
	if err != nil {
		return
	}
	return gistItem{
		files: api.Files{api.File{
			Filename: filename,
			Content:  content,
		}},
		desc: desc,
	}, nil
}

func makeFromStdin() (gi gistItem, err error) {
	body, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		return
	}
	filename := util.RandomString(20)
	ext := cli.Conf.Gist.FileExt
	if len(ext) > 0 {
		if !strings.HasPrefix(ext, ".") {
			ext = "." + ext
		}
		filename = filename + ext
	}
	return gistItem{
		files: api.Files{api.File{
			Filename: filename,
			Content:  string(body),
		}},
		desc: "",
	}, nil
}

func makeFromEditor() (gi gistItem, err error) {
	filename, err := util.Scan(color.YellowString("Filename> "), !util.ScanAllowEmpty)
	if err != nil {
		return
	}

	filename, title, asBlog := func(filename string) (string, string, bool) {
		if !cli.Conf.Flag.BlogMode {
			return filename, "", false
		}
		switch filepath.Ext(filename) {
		case "", ".md", ".mkd", ".markdown":
			return filename + ".md", "# " + filename, true
		default:
			return filename, filename, false
		}
	}(filename)

	f, err := util.TempFile(filename)
	defer os.Remove(f.Name())
	if asBlog {
		f.Write([]byte(title))
		f.Sync()
	}

	err = cli.Run(cli.Conf.Core.Editor, f.Name())
	if err != nil {
		return
	}

	content, _ := util.FileContent(f.Name())
	if content == title {
		return gi, errors.New("no contents")
	}

	desc, err := util.Scan(color.GreenString("Description> "), util.ScanAllowEmpty)
	if err != nil {
		return
	}

	return gistItem{
		files: api.Files{api.File{
			Filename: filename,
			Content:  content,
		}},
		desc: desc,
	}, nil
}

func makeFromArguments(args []string) (gi gistItem, err error) {
	var (
		gistFiles api.Files
		files     []string
	)

	// Check if the path is directory
	isdir := func(path string) bool {
		if stat, err := os.Stat(path); err == nil && stat.IsDir() {
			return true
		}
		return false
	}

	for _, arg := range args {
		// if the arg is dir, walk within the dir and add them to slice
		// otherwise (regular file), just add it to slice
		if isdir(arg) {
			err = filepath.Walk(arg, func(arg string, info os.FileInfo, err error) error {
				if strings.HasPrefix(arg, ".") {
					return nil
				}
				if info.IsDir() {
					return nil
				}
				files = append(files, arg)
				return nil
			})
			if err != nil {
				return
			}
		} else {
			if !util.Exists(arg) {
				log.Infof("%s: no such file or directory", arg)
				continue
			}
			files = append(files, arg)
		}
	}

	switch len(files) {
	case 0:
		return gi, errors.New("no files to be able create")
	case 1:
		file := files[0]
		util.ScanDefaultString = file
		file, err = util.Scan(color.YellowString("Filename> "), !util.ScanAllowEmpty)
		if err != nil {
			return
		}
		content, _ := util.FileContent(files[0]) // Use original file: files[0]
		gistFiles = append(gistFiles, api.File{
			Filename: filepath.Base(file),
			Content:  content,
		})
		util.ScanDefaultString = "" // reset deafult string
	default:
		for _, file := range files {
			fmt.Fprintf(color.Output, "%s %s\n", color.YellowString("Filename>"), file)
			content, _ := util.FileContent(file)
			gistFiles = append(gistFiles, api.File{
				Filename: filepath.Base(file),
				Content:  content,
			})
		}
	}

	desc, err := util.Scan(color.GreenString("Description> "), util.ScanAllowEmpty)
	if err != nil {
		return
	}

	return gistItem{
		files: gistFiles,
		desc:  desc,
	}, nil
}

func init() {
	RootCmd.AddCommand(newCmd)
	newCmd.Flags().BoolVarP(&cli.Conf.Flag.OpenURL, "open", "o", false, "Open with the default browser")
	newCmd.Flags().BoolVarP(&cli.Conf.Flag.NewPrivate, "private", "p", false, "Create as private gist")
	newCmd.Flags().BoolVarP(&cli.Conf.Flag.FromClipboard, "from-clipboard", "c", false, "Create gist from clipboard")
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"io"
	"os"
	"path"
	"time"

	"github.com/containerum/chkit/cmd/config_dir"

	kubeClientModels "git.containerum.net/ch/kube-client/pkg/model"
	"github.com/blang/semver"
	"github.com/containerum/chkit/cmd/util"
	"github.com/containerum/chkit/pkg/chkitErrors"
	"github.com/containerum/chkit/pkg/client"
	"github.com/containerum/chkit/pkg/model"
	"github.com/sirupsen/logrus"
	cli "gopkg.in/urfave/cli.v2"
)

var (
	// Version -- chkit version
	Version = "3.0.0-alpha"
)

const (
	// FlagConfigFile -- context config data key
	FlagConfigFile = "config"
	// FlagAPIaddr -- API address context key
	FlagAPIaddr = "apiaddr"
)

var (
	// ErrFatalError -- unrecoverable fatal error
	ErrFatalError chkitErrors.Err = "fatal error"
)

// Run -- root action
func Run(args []string) error {
	logrus.SetLevel(logrus.DebugLevel)
	logrus.SetFormatter(&logrus.TextFormatter{
		FullTimestamp:   true,
		TimestampFormat: time.RFC1123,
	})
	logFile := path.Join(confDir.LogDir(), util.LogFileName())
	file, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
	if err != nil {
		logrus.Fatalf("error while creating log file: %v", err)
	}
	if !DEBUG {
		logrus.SetOutput(file)
	} else {
		logWriter := io.MultiWriter(os.Stdout, file)
		logrus.SetOutput(logWriter)
	}
	var App = &cli.App{
		Name:    "chkit",
		Usage:   "containerum cli",
		Version: semver.MustParse(Version).String(),
		/*Before: func(ctx *cli.Context) error {
			var updater update.LatestCheckerDownloader
			currVersion := semver.MustParse(Version)
			updater = update.NewGithubLatestCheckerDownloader(ctx, "containerum", "chkit")
			version, err := updater.LatestVersion()
			if err != nil {
				return err
			}
			if currVersion.LE(version) {
				if yes, err := update.AskForUpdate(ctx, version); err != nil {
					return err
				} else if yes {
					if err := update.Update(ctx, updater, true); err != nil {
						return err
					}
				}
			}
			return nil
		},*/
		Action: runAction,
		Metadata: map[string]interface{}{
			"client":     chClient.Client{},
			"configPath": confDir.ConfigDir(),
			"config":     model.Config{},
			"tokens":     kubeClientModels.Tokens{},
			"version":    semver.MustParse(Version),
		},
		Commands: []*cli.Command{
			&cli.Command{
				Name:        "version",
				Usage:       "prints chkit version",
				Description: "prints chkit version. Aliases: vers, vs, v",
				Aliases:     []string{"vers", "vs", "v"},
				Action: func(ctx *cli.Context) error {
					fmt.Println(Version)
					return nil
				},
			},
			commandLogin,
			commandGet,
			commandDelete,
			commandUpdate,
			commandLogs,
			CommandCreate,
		},
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "config",
				Usage:   "config file",
				Aliases: []string{"c"},
				Value:   path.Join(confDir.ConfigDir(), "config.toml"),
			},
			&cli.StringFlag{
				Name:    "api",
				Usage:   "API address",
				Value:   "",
				Hidden:  true,
				EnvVars: []string{"CONTAINERUM_API"},
			},
			&cli.BoolFlag{
				Name:   "debug-requests",
				Value:  false,
				Hidden: true,
			},
			&cli.StringFlag{
				Name:  "username",
				Usage: "your account email",
			},
			&cli.StringFlag{
				Name:  "pass",
				Usage: "password to system",
			},
		},
	}
	return App.Run(args)
}

func runAction(ctx *cli.Context) error {
	logrus.Debugf("loading config")
	if err := loadConfig(ctx); err != nil {
		return err
	}
	logrus.Debugf("running setup")
	err := setupConfig(ctx)
	config := util.GetConfig(ctx)
	switch {
	case err == nil:
		// pass
	case ErrInvalidUserInfo.Match(err):
		logrus.Debugf("invalid user information")
		logrus.Debugf("running login")
		user, err := login(ctx)
		if err != nil {
			return err
		}
		config.UserInfo = user
		util.SetConfig(ctx, config)
	default:
		logrus.Debugf("fatal error")
		return ErrFatalError.Wrap(err)
	}
	logrus.Debugf("client initialisation")
	if err := setupClient(ctx); err != nil {
		return err
	}
	client := util.GetClient(ctx)
	if err := util.SaveTokens(ctx, client.Tokens); err != nil {
		return chkitErrors.NewExitCoder(err)
	}
	client.Config.DefaultNamespace, err = util.GetFirstClientNamespace(ctx)
	if err != nil {
		return err
	}
	util.SetConfig(ctx, config)
	if err := persist(ctx); err != nil {
		logrus.Fatalf("%v", err)
	}
	logrus.Infof("Hello, %q!", client.Config.Username)
	if err := mainActivity(ctx); err != nil {
		logrus.Fatalf("error in main activity: %v", err)
	}
	return nil
}
<commit_msg>switch of terminal debug logging<commit_after>package cmd

import (
	"fmt"
	"os"
	"path"
	"time"

	kubeClientModels "git.containerum.net/ch/kube-client/pkg/model"
	"github.com/blang/semver"
	"github.com/containerum/chkit/cmd/config_dir"
	"github.com/containerum/chkit/cmd/util"
	"github.com/containerum/chkit/pkg/chkitErrors"
	"github.com/containerum/chkit/pkg/client"
	"github.com/containerum/chkit/pkg/model"
	"github.com/sirupsen/logrus"
	cli "gopkg.in/urfave/cli.v2"
)

var (
	// Version -- chkit version
	Version = "3.0.0-alpha"
)

const (
	// FlagConfigFile -- context config data key
	FlagConfigFile = "config"
	// FlagAPIaddr -- API address context key
	FlagAPIaddr = "apiaddr"
)

var (
	// ErrFatalError -- unrecoverable fatal error
	ErrFatalError chkitErrors.Err = "fatal error"
)

// Run -- root action
func Run(args []string) error {
	logrus.SetLevel(logrus.DebugLevel)
	logrus.SetFormatter(&logrus.TextFormatter{
		FullTimestamp:   true,
		TimestampFormat: time.RFC1123,
	})
	logFile := path.Join(confDir.LogDir(), util.LogFileName())
	file, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
	if err != nil {
		logrus.Fatalf("error while creating log file: %v", err)
	}
	logrus.SetOutput(file)
	//logWriter := io.MultiWriter(os.Stdout, file)
	//logrus.SetOutput(logWriter)
	var App = &cli.App{
		Name:    "chkit",
		Usage:   "containerum cli",
		Version: semver.MustParse(Version).String(),
		/*Before: func(ctx *cli.Context) error {
			var updater update.LatestCheckerDownloader
			currVersion := semver.MustParse(Version)
			updater = update.NewGithubLatestCheckerDownloader(ctx, "containerum", "chkit")
			version, err := updater.LatestVersion()
			if err != nil {
				return err
			}
			if currVersion.LE(version) {
				if yes, err := update.AskForUpdate(ctx, version); err != nil {
					return err
				} else if yes {
					if err := update.Update(ctx, updater, true); err != nil {
						return err
					}
				}
			}
			return nil
		},*/
		Action: runAction,
		Metadata: map[string]interface{}{
			"client":     chClient.Client{},
			"configPath": confDir.ConfigDir(),
			"config":     model.Config{},
			"tokens":     kubeClientModels.Tokens{},
			"version":    semver.MustParse(Version),
		},
		Commands: []*cli.Command{
			&cli.Command{
				Name:        "version",
				Usage:       "prints chkit version",
				Description: "prints chkit version. Aliases: vers, vs, v",
				Aliases:     []string{"vers", "vs", "v"},
				Action: func(ctx *cli.Context) error {
					fmt.Println(Version)
					return nil
				},
			},
			commandLogin,
			commandGet,
			commandDelete,
			commandUpdate,
			commandLogs,
			CommandCreate,
		},
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "config",
				Usage:   "config file",
				Aliases: []string{"c"},
				Value:   path.Join(confDir.ConfigDir(), "config.toml"),
			},
			&cli.StringFlag{
				Name:    "api",
				Usage:   "API address",
				Value:   "",
				Hidden:  true,
				EnvVars: []string{"CONTAINERUM_API"},
			},
			&cli.BoolFlag{
				Name:   "debug-requests",
				Value:  false,
				Hidden: true,
			},
			&cli.StringFlag{
				Name:  "username",
				Usage: "your account email",
			},
			&cli.StringFlag{
				Name:  "pass",
				Usage: "password to system",
			},
		},
	}
	return App.Run(args)
}

func runAction(ctx *cli.Context) error {
	logrus.Debugf("loading config")
	if err := loadConfig(ctx); err != nil {
		return err
	}
	logrus.Debugf("running setup")
	err := setupConfig(ctx)
	config := util.GetConfig(ctx)
	switch {
	case err == nil:
		// pass
	case ErrInvalidUserInfo.Match(err):
		logrus.Debugf("invalid user information")
		logrus.Debugf("running login")
		user, err := login(ctx)
		if err != nil {
			return err
		}
		config.UserInfo = user
		util.SetConfig(ctx, config)
	default:
		logrus.Debugf("fatal error")
		return ErrFatalError.Wrap(err)
	}
	logrus.Debugf("client initialisation")
	if err := setupClient(ctx); err != nil {
		return err
	}
	client := util.GetClient(ctx)
	if err := util.SaveTokens(ctx, client.Tokens); err != nil {
		return chkitErrors.NewExitCoder(err)
	}
	client.Config.DefaultNamespace, err = util.GetFirstClientNamespace(ctx)
	if err != nil {
		return err
	}
	util.SetConfig(ctx, config)
	if err := persist(ctx); err != nil {
		logrus.Fatalf("%v", err)
	}
	logrus.Infof("Hello, %q!", client.Config.Username)
	if err := mainActivity(ctx); err != nil {
		logrus.Fatalf("error in main activity: %v", err)
	}
	return nil
}
<|endoftext|>


<commit_before>package sourcegraph

// SearchService communicates with the search-related endpoints in
// the Sourcegraph API.
type SearchService interface {
	// Search searches the full index.
	Search(opt *SearchOptions) (*SearchResults, error)

	// Complete completes the token at the RawQuery's InsertionPoint.
	Complete(q RawQuery) (*Completions, error)

	// Suggest suggests queries given an existing query. It can be
	// called with an empty query to get example queries that pertain
	// to the current user's repositories, orgs, etc.
	Suggest(q RawQuery) ([]*Suggestion, error)
}

// Empty is whether there are no search results for any result type.
func (r *SearchResults) Empty() bool {
	return len(r.Defs) == 0 && len(r.People) == 0 && len(r.Repos) == 0 && len(r.Tree) == 0
}
<commit_msg>rm search (unused, meant to delete before)<commit_after>package sourcegraph

// Empty is whether there are no search results for any result type.
func (r *SearchResults) Empty() bool {
	return len(r.Defs) == 0 && len(r.People) == 0 && len(r.Repos) == 0 && len(r.Tree) == 0
}
<|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))
		}
	}

	for i := range listeners {
		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.Fatal(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>Bail if we failed to set up any listeners<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))
		}
	}

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

					go handleConnection(conn)
				}
			}(listeners[i])
		}
	}
	if (listenerCount == 0) {
		log.Fatal("Failed to establish a listener")
	}

	// 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)
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	ccli "github.com/micro/cli/v2"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/config/cmd"
	"github.com/micro/micro/v2/api"
	"github.com/micro/micro/v2/auth"
	"github.com/micro/micro/v2/bot"
	"github.com/micro/micro/v2/broker"
	"github.com/micro/micro/v2/cli"
	"github.com/micro/micro/v2/config"
	"github.com/micro/micro/v2/debug"
	"github.com/micro/micro/v2/health"
	"github.com/micro/micro/v2/monitor"
	"github.com/micro/micro/v2/network"
	"github.com/micro/micro/v2/new"
	"github.com/micro/micro/v2/plugin"
	"github.com/micro/micro/v2/plugin/build"
	"github.com/micro/micro/v2/proxy"
	"github.com/micro/micro/v2/registry"
	"github.com/micro/micro/v2/router"
	"github.com/micro/micro/v2/runtime"
	"github.com/micro/micro/v2/server"
	"github.com/micro/micro/v2/service"
	"github.com/micro/micro/v2/store"
	"github.com/micro/micro/v2/token"
	"github.com/micro/micro/v2/tunnel"
	"github.com/micro/micro/v2/web"

	// include usage

	"github.com/micro/micro/v2/internal/platform"
	_ "github.com/micro/micro/v2/internal/usage"
)

var (
	GitCommit string
	GitTag    string
	BuildDate string

	name        = "micro"
	description = "A microservice runtime"
	version     = "2.0.0"
)

func init() {
	// setup the build plugin
	plugin.Register(build.Flags())

	// set platform build date
	platform.Version = BuildDate
}

func setup(app *ccli.App) {
	app.Flags = append(app.Flags,
		&ccli.BoolFlag{
			Name:  "local",
			Usage: "Enable local only development: Defaults to true.",
		},
		&ccli.BoolFlag{
			Name:  "peer",
			Usage: "Peer with the global network to share services",
		},
		&ccli.BoolFlag{
			Name:    "enable_acme",
			Usage:   "Enables ACME support via Let's Encrypt. ACME hosts should also be specified.",
			EnvVars: []string{"MICRO_ENABLE_ACME"},
		},
		&ccli.StringFlag{
			Name:    "acme_hosts",
			Usage:   "Comma separated list of hostnames to manage ACME certs for",
			EnvVars: []string{"MICRO_ACME_HOSTS"},
		},
		&ccli.StringFlag{
			Name:    "acme_provider",
			Usage:   "The provider that will be used to communicate with Let's Encrypt. Valid options: autocert, certmagic",
			EnvVars: []string{"MICRO_ACME_PROVIDER"},
		},
		&ccli.BoolFlag{
			Name:    "enable_tls",
			Usage:   "Enable TLS support. Expects cert and key file to be specified",
			EnvVars: []string{"MICRO_ENABLE_TLS"},
		},
		&ccli.StringFlag{
			Name:    "tls_cert_file",
			Usage:   "Path to the TLS Certificate file",
			EnvVars: []string{"MICRO_TLS_CERT_FILE"},
		},
		&ccli.StringFlag{
			Name:    "tls_key_file",
			Usage:   "Path to the TLS Key file",
			EnvVars: []string{"MICRO_TLS_KEY_FILE"},
		},
		&ccli.StringFlag{
			Name:    "tls_client_ca_file",
			Usage:   "Path to the TLS CA file to verify clients against",
			EnvVars: []string{"MICRO_TLS_CLIENT_CA_FILE"},
		},
		&ccli.StringFlag{
			Name:    "api_address",
			Usage:   "Set the api address e.g 0.0.0.0:8080",
			EnvVars: []string{"MICRO_API_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "proxy_address",
			Usage:   "Proxy requests via the HTTP address specified",
			EnvVars: []string{"MICRO_PROXY_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "web_address",
			Usage:   "Set the web UI address e.g 0.0.0.0:8082",
			EnvVars: []string{"MICRO_WEB_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "network",
			Usage:   "Set the micro network name: local, go.micro",
			EnvVars: []string{"MICRO_NETWORK"},
		},
		&ccli.StringFlag{
			Name:    "network_address",
			Usage:   "Set the micro network address e.g. :9093",
			EnvVars: []string{"MICRO_NETWORK_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "router_address",
			Usage:   "Set the micro router address e.g. :8084",
			EnvVars: []string{"MICRO_ROUTER_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "gateway_address",
			Usage:   "Set the micro default gateway address e.g. :9094",
			EnvVars: []string{"MICRO_GATEWAY_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "tunnel_address",
			Usage:   "Set the micro tunnel address e.g. :8083",
			EnvVars: []string{"MICRO_TUNNEL_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "api_handler",
			Usage:   "Specify the request handler to be used for mapping HTTP requests to services; {api, proxy, rpc}",
			EnvVars: []string{"MICRO_API_HANDLER"},
		},
		&ccli.StringFlag{
			Name:    "api_namespace",
			Usage:   "Set the namespace used by the API e.g. com.example.api",
			EnvVars: []string{"MICRO_API_NAMESPACE"},
		},
		&ccli.StringFlag{
			Name:    "web_namespace",
			Usage:   "Set the namespace used by the Web proxy e.g. com.example.web",
			EnvVars: []string{"MICRO_WEB_NAMESPACE"},
		},
		&ccli.BoolFlag{
			Name:    "enable_stats",
			Usage:   "Enable stats",
			EnvVars: []string{"MICRO_ENABLE_STATS"},
		},
		&ccli.BoolFlag{
			Name:    "auto_update",
			Usage:   "Enable automatic updates",
			EnvVars: []string{"MICRO_AUTO_UPDATE"},
		},
		&ccli.BoolFlag{
			Name:    "report_usage",
			Usage:   "Report usage statistics",
			EnvVars: []string{"MICRO_REPORT_USAGE"},
			Value:   true,
		},
		&ccli.StringFlag{
			Name:    "namespace",
			Usage:   "Set the micro service namespace",
			EnvVars: []string{"MICRO_NAMESPACE"},
			Value:   "go.micro",
		},
	)

	plugins := plugin.Plugins()

	for _, p := range plugins {
		if flags := p.Flags(); len(flags) > 0 {
			app.Flags = append(app.Flags, flags...)
		}

		if cmds := p.Commands(); len(cmds) > 0 {
			app.Commands = append(app.Commands, cmds...)
		}
	}

	before := app.Before

	app.Before = func(ctx *ccli.Context) error {
		if len(ctx.String("api_handler")) > 0 {
			api.Handler = ctx.String("api_handler")
		}
		if len(ctx.String("api_address")) > 0 {
			api.Address = ctx.String("api_address")
		}
		if len(ctx.String("proxy_address")) > 0 {
			proxy.Address = ctx.String("proxy_address")
		}
		if len(ctx.String("web_address")) > 0 {
			web.Address = ctx.String("web_address")
		}
		if len(ctx.String("network_address")) > 0 {
			server.Network = ctx.String("network_address")
		}
		if len(ctx.String("router_address")) > 0 {
			router.Address = ctx.String("router_address")
		}
		if len(ctx.String("tunnel_address")) > 0 {
			tunnel.Address = ctx.String("tunnel_address")
		}
		if len(ctx.String("api_namespace")) > 0 {
			api.Namespace = ctx.String("api_namespace")
		}
		if len(ctx.String("web_namespace")) > 0 {
			web.Namespace = ctx.String("web_namespace")
		}

		for _, p := range plugins {
			if err := p.Init(ctx); err != nil {
				return err
			}
		}

		// now do previous before
		return before(ctx)
	}
}

func buildVersion() string {
	microVersion := version

	if GitTag != "" {
		microVersion = GitTag
	}

	if GitCommit != "" {
		microVersion += fmt.Sprintf("-%s", GitCommit)
	}

	if BuildDate != "" {
		microVersion += fmt.Sprintf("-%s", BuildDate)
	}

	return microVersion
}

// Init initialised the command line
func Init(options ...micro.Option) {
	Setup(cmd.App(), options...)

	cmd.Init(
		cmd.Name(name),
		cmd.Description(description),
		cmd.Version(buildVersion()),
	)
}

// Setup sets up a cli.App
func Setup(app *ccli.App, options ...micro.Option) {
	// Add the various commands
	app.Commands = append(app.Commands, api.Commands(options...)...)
	app.Commands = append(app.Commands, auth.Commands()...)
	app.Commands = append(app.Commands, bot.Commands()...)
	app.Commands = append(app.Commands, cli.Commands()...)
	app.Commands = append(app.Commands, broker.Commands(options...)...)
	app.Commands = append(app.Commands, health.Commands(options...)...)
	app.Commands = append(app.Commands, proxy.Commands(options...)...)
	app.Commands = append(app.Commands, monitor.Commands(options...)...)
	app.Commands = append(app.Commands, router.Commands(options...)...)
	app.Commands = append(app.Commands, tunnel.Commands(options...)...)
	app.Commands = append(app.Commands, network.Commands(options...)...)
	app.Commands = append(app.Commands, registry.Commands(options...)...)
	app.Commands = append(app.Commands, runtime.Commands(options...)...)
	app.Commands = append(app.Commands, debug.Commands(options...)...)
	app.Commands = append(app.Commands, server.Commands(options...)...)
	app.Commands = append(app.Commands, service.Commands(options...)...)
	app.Commands = append(app.Commands, store.Commands(options...)...)
	app.Commands = append(app.Commands, token.Commands()...)
	app.Commands = append(app.Commands, new.Commands()...)
	app.Commands = append(app.Commands, build.Commands()...)
	app.Commands = append(app.Commands, web.Commands(options...)...)
	app.Commands = append(app.Commands, config.Commands(options...)...)

	// add the init command for our internal operator
	app.Commands = append(app.Commands, &ccli.Command{
		Name:  "init",
		Usage: "Run the micro operator",
		Action: func(c *ccli.Context) error {
			platform.Init(c)
			return nil
		},
		Flags: []ccli.Flag{},
	})

	// boot micro runtime
	app.Action = platform.Run

	setup(app)
}
<commit_msg>cut new v2.1.0 release (#562)<commit_after>package cmd

import (
	"fmt"

	ccli "github.com/micro/cli/v2"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/config/cmd"
	"github.com/micro/micro/v2/api"
	"github.com/micro/micro/v2/auth"
	"github.com/micro/micro/v2/bot"
	"github.com/micro/micro/v2/broker"
	"github.com/micro/micro/v2/cli"
	"github.com/micro/micro/v2/config"
	"github.com/micro/micro/v2/debug"
	"github.com/micro/micro/v2/health"
	"github.com/micro/micro/v2/monitor"
	"github.com/micro/micro/v2/network"
	"github.com/micro/micro/v2/new"
	"github.com/micro/micro/v2/plugin"
	"github.com/micro/micro/v2/plugin/build"
	"github.com/micro/micro/v2/proxy"
	"github.com/micro/micro/v2/registry"
	"github.com/micro/micro/v2/router"
	"github.com/micro/micro/v2/runtime"
	"github.com/micro/micro/v2/server"
	"github.com/micro/micro/v2/service"
	"github.com/micro/micro/v2/store"
	"github.com/micro/micro/v2/token"
	"github.com/micro/micro/v2/tunnel"
	"github.com/micro/micro/v2/web"

	// include usage

	"github.com/micro/micro/v2/internal/platform"
	_ "github.com/micro/micro/v2/internal/usage"
)

var (
	GitCommit string
	GitTag    string
	BuildDate string

	name        = "micro"
	description = "A microservice runtime"
	version     = "2.1.0"
)

func init() {
	// setup the build plugin
	plugin.Register(build.Flags())

	// set platform build date
	platform.Version = BuildDate
}

func setup(app *ccli.App) {
	app.Flags = append(app.Flags,
		&ccli.BoolFlag{
			Name:  "local",
			Usage: "Enable local only development: Defaults to true.",
		},
		&ccli.BoolFlag{
			Name:  "peer",
			Usage: "Peer with the global network to share services",
		},
		&ccli.BoolFlag{
			Name:    "enable_acme",
			Usage:   "Enables ACME support via Let's Encrypt. ACME hosts should also be specified.",
			EnvVars: []string{"MICRO_ENABLE_ACME"},
		},
		&ccli.StringFlag{
			Name:    "acme_hosts",
			Usage:   "Comma separated list of hostnames to manage ACME certs for",
			EnvVars: []string{"MICRO_ACME_HOSTS"},
		},
		&ccli.StringFlag{
			Name:    "acme_provider",
			Usage:   "The provider that will be used to communicate with Let's Encrypt. Valid options: autocert, certmagic",
			EnvVars: []string{"MICRO_ACME_PROVIDER"},
		},
		&ccli.BoolFlag{
			Name:    "enable_tls",
			Usage:   "Enable TLS support. Expects cert and key file to be specified",
			EnvVars: []string{"MICRO_ENABLE_TLS"},
		},
		&ccli.StringFlag{
			Name:    "tls_cert_file",
			Usage:   "Path to the TLS Certificate file",
			EnvVars: []string{"MICRO_TLS_CERT_FILE"},
		},
		&ccli.StringFlag{
			Name:    "tls_key_file",
			Usage:   "Path to the TLS Key file",
			EnvVars: []string{"MICRO_TLS_KEY_FILE"},
		},
		&ccli.StringFlag{
			Name:    "tls_client_ca_file",
			Usage:   "Path to the TLS CA file to verify clients against",
			EnvVars: []string{"MICRO_TLS_CLIENT_CA_FILE"},
		},
		&ccli.StringFlag{
			Name:    "api_address",
			Usage:   "Set the api address e.g 0.0.0.0:8080",
			EnvVars: []string{"MICRO_API_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "proxy_address",
			Usage:   "Proxy requests via the HTTP address specified",
			EnvVars: []string{"MICRO_PROXY_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "web_address",
			Usage:   "Set the web UI address e.g 0.0.0.0:8082",
			EnvVars: []string{"MICRO_WEB_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "network",
			Usage:   "Set the micro network name: local, go.micro",
			EnvVars: []string{"MICRO_NETWORK"},
		},
		&ccli.StringFlag{
			Name:    "network_address",
			Usage:   "Set the micro network address e.g. :9093",
			EnvVars: []string{"MICRO_NETWORK_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "router_address",
			Usage:   "Set the micro router address e.g. :8084",
			EnvVars: []string{"MICRO_ROUTER_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "gateway_address",
			Usage:   "Set the micro default gateway address e.g. :9094",
			EnvVars: []string{"MICRO_GATEWAY_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "tunnel_address",
			Usage:   "Set the micro tunnel address e.g. :8083",
			EnvVars: []string{"MICRO_TUNNEL_ADDRESS"},
		},
		&ccli.StringFlag{
			Name:    "api_handler",
			Usage:   "Specify the request handler to be used for mapping HTTP requests to services; {api, proxy, rpc}",
			EnvVars: []string{"MICRO_API_HANDLER"},
		},
		&ccli.StringFlag{
			Name:    "api_namespace",
			Usage:   "Set the namespace used by the API e.g. com.example.api",
			EnvVars: []string{"MICRO_API_NAMESPACE"},
		},
		&ccli.StringFlag{
			Name:    "web_namespace",
			Usage:   "Set the namespace used by the Web proxy e.g. com.example.web",
			EnvVars: []string{"MICRO_WEB_NAMESPACE"},
		},
		&ccli.BoolFlag{
			Name:    "enable_stats",
			Usage:   "Enable stats",
			EnvVars: []string{"MICRO_ENABLE_STATS"},
		},
		&ccli.BoolFlag{
			Name:    "auto_update",
			Usage:   "Enable automatic updates",
			EnvVars: []string{"MICRO_AUTO_UPDATE"},
		},
		&ccli.BoolFlag{
			Name:    "report_usage",
			Usage:   "Report usage statistics",
			EnvVars: []string{"MICRO_REPORT_USAGE"},
			Value:   true,
		},
		&ccli.StringFlag{
			Name:    "namespace",
			Usage:   "Set the micro service namespace",
			EnvVars: []string{"MICRO_NAMESPACE"},
			Value:   "go.micro",
		},
	)

	plugins := plugin.Plugins()

	for _, p := range plugins {
		if flags := p.Flags(); len(flags) > 0 {
			app.Flags = append(app.Flags, flags...)
		}

		if cmds := p.Commands(); len(cmds) > 0 {
			app.Commands = append(app.Commands, cmds...)
		}
	}

	before := app.Before

	app.Before = func(ctx *ccli.Context) error {
		if len(ctx.String("api_handler")) > 0 {
			api.Handler = ctx.String("api_handler")
		}
		if len(ctx.String("api_address")) > 0 {
			api.Address = ctx.String("api_address")
		}
		if len(ctx.String("proxy_address")) > 0 {
			proxy.Address = ctx.String("proxy_address")
		}
		if len(ctx.String("web_address")) > 0 {
			web.Address = ctx.String("web_address")
		}
		if len(ctx.String("network_address")) > 0 {
			server.Network = ctx.String("network_address")
		}
		if len(ctx.String("router_address")) > 0 {
			router.Address = ctx.String("router_address")
		}
		if len(ctx.String("tunnel_address")) > 0 {
			tunnel.Address = ctx.String("tunnel_address")
		}
		if len(ctx.String("api_namespace")) > 0 {
			api.Namespace = ctx.String("api_namespace")
		}
		if len(ctx.String("web_namespace")) > 0 {
			web.Namespace = ctx.String("web_namespace")
		}

		for _, p := range plugins {
			if err := p.Init(ctx); err != nil {
				return err
			}
		}

		// now do previous before
		return before(ctx)
	}
}

func buildVersion() string {
	microVersion := version

	if GitTag != "" {
		microVersion = GitTag
	}

	if GitCommit != "" {
		microVersion += fmt.Sprintf("-%s", GitCommit)
	}

	if BuildDate != "" {
		microVersion += fmt.Sprintf("-%s", BuildDate)
	}

	return microVersion
}

// Init initialised the command line
func Init(options ...micro.Option) {
	Setup(cmd.App(), options...)

	cmd.Init(
		cmd.Name(name),
		cmd.Description(description),
		cmd.Version(buildVersion()),
	)
}

// Setup sets up a cli.App
func Setup(app *ccli.App, options ...micro.Option) {
	// Add the various commands
	app.Commands = append(app.Commands, api.Commands(options...)...)
	app.Commands = append(app.Commands, auth.Commands()...)
	app.Commands = append(app.Commands, bot.Commands()...)
	app.Commands = append(app.Commands, cli.Commands()...)
	app.Commands = append(app.Commands, broker.Commands(options...)...)
	app.Commands = append(app.Commands, health.Commands(options...)...)
	app.Commands = append(app.Commands, proxy.Commands(options...)...)
	app.Commands = append(app.Commands, monitor.Commands(options...)...)
	app.Commands = append(app.Commands, router.Commands(options...)...)
	app.Commands = append(app.Commands, tunnel.Commands(options...)...)
	app.Commands = append(app.Commands, network.Commands(options...)...)
	app.Commands = append(app.Commands, registry.Commands(options...)...)
	app.Commands = append(app.Commands, runtime.Commands(options...)...)
	app.Commands = append(app.Commands, debug.Commands(options...)...)
	app.Commands = append(app.Commands, server.Commands(options...)...)
	app.Commands = append(app.Commands, service.Commands(options...)...)
	app.Commands = append(app.Commands, store.Commands(options...)...)
	app.Commands = append(app.Commands, token.Commands()...)
	app.Commands = append(app.Commands, new.Commands()...)
	app.Commands = append(app.Commands, build.Commands()...)
	app.Commands = append(app.Commands, web.Commands(options...)...)
	app.Commands = append(app.Commands, config.Commands(options...)...)

	// add the init command for our internal operator
	app.Commands = append(app.Commands, &ccli.Command{
		Name:  "init",
		Usage: "Run the micro operator",
		Action: func(c *ccli.Context) error {
			platform.Init(c)
			return nil
		},
		Flags: []ccli.Flag{},
	})

	// boot micro runtime
	app.Action = platform.Run

	setup(app)
}
<|endoftext|>


<commit_before>// Copyright 2017 The go-ego Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// https://github.com/go-ego/ego/blob/master/LICENSE
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

package cmd

import (
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
	"text/template"

	"go-vgo/go-ego/e/log"
)

const (
	version string = "v0.10.0.1, First!"
)

// Command is the unit of execution
type Command struct {
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string) int

	// PreRun performs an operation before running the command
	PreRun func(cmd *Command, args []string)

	// UsageLine is the one-line usage message.
	// The first word in the line is taken to be the command name.
	UsageLine string

	// Short is the short description shown in the 'go help' output.
	Short string

	// Long is the long message shown in the 'go help <this-command>' output.
	Long string

	// Flag is a set of flags specific to this command.
	Flag flag.FlagSet

	// CustomFlags indicates that the command will do its own
	// flag parsing.
	CustomFlags bool

	// output out writer if set in SetOutput(w)
	output *io.Writer
}

// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// SetOutput sets the destination for usage and error messages.
// If output is nil, os.Stderr is used.
func (c *Command) SetOutput(output io.Writer) {
	c.output = &output
}

// Out returns the out writer of the current command.
// If cmd.output is nil, os.Stderr is used.
func (c *Command) Out() io.Writer {
	if c.output != nil {
		return *c.output
	}
	return log.NewColorWriter(os.Stderr)
}

// Usage puts out the usage for the command.
func (c *Command) Usage() {
	tmpl(cmdUsage, c)
	os.Exit(2)
}

// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as import path.
func (c *Command) Runnable() bool {
	return c.Run != nil
}

func (c *Command) Options() map[string]string {
	options := make(map[string]string)
	c.Flag.VisitAll(func(f *flag.Flag) {
		defaultVal := f.DefValue
		if len(defaultVal) > 0 {
			// if strings.Contains(defaultVal, ":") {
			// 	// Truncate the flag's default value by appending '...' at the end
			// 	options[f.Name+"="+strings.Split(defaultVal, ":")[0]+":..."] = f.Usage
			// } else {
			options[f.Name+"="+defaultVal] = f.Usage
			// }
		} else {
			options[f.Name] = f.Usage
		}
	})
	return options
}

var AvailableCommands = []*Command{
	// cmdGen,
	cmdNew,
	cmdRun,
	cmdPack,
	// cmdApi,
	//cmdRouter,
	//cmdTest,
	cmdBale,
	cmdVersion,
	// cmdGenerate,
	//cmdRundocs,
	cmdMigrate,
	cmdFix,
	// cmdDockerize,
}

var logger = log.GetEgoLogger(os.Stdout)

func IsGenerateDocs(name string, args []string) bool {
	if name != "generate" {
		return false
	}
	for _, a := range args {
		if a == "docs" {
			return true
		}
	}
	return false
}

var usageTemplate = `ego is a Fast and Flexible tool for managing your ego Web Application.

{{"USAGE" | headline}}
    {{"ego command [arguments]" | bold}}

{{"AVAILABLE COMMANDS" | headline}}
{{range .}}{{if .Runnable}}
    {{.Name | printf "%-11s" | bold}} {{.Short}}{{end}}{{end}}

Use {{"ego help [command]" | bold}} for more information about a command.

{{"ADDITIONAL HELP TOPICS" | headline}}
{{range .}}{{if not .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use {{"ego help [topic]" | bold}} for more information about that topic.
`

var helpTemplate = `{{"USAGE" | headline}}
  {{.UsageLine | printf "ego %s" | bold}}
{{if .Options}}{{endline}}{{"OPTIONS" | headline}}{{range $k,$v := .Options}}
  {{$k | printf "-%s" | bold}}
      {{$v}}
  {{end}}{{end}}
{{"DESCRIPTION" | headline}}
  {{tmpltostr .Long . | trim}}
`

var errorTemplate = `ego: %s.
Use {{"ego help" | bold}} for more information.
`

var cmdUsage = `Use {{printf "ego help %s" .Name | bold}} for more information.{{endline}}`

func Usage() {
	tmpl(usageTemplate, AvailableCommands)
	os.Exit(2)
}

func tmpl(text string, data interface{}) {
	output := log.NewColorWriter(os.Stderr)

	t := template.New("usage").Funcs(EgoFuncMap())
	template.Must(t.Parse(text))

	err := t.Execute(output, data)
	MustCheck(err)
}

func Help(args []string) {
	if len(args) == 0 {
		Usage()
	}
	if len(args) != 1 {
		PrintErrorAndExit("Too many arguments")
	}

	arg := args[0]

	for _, cmd := range AvailableCommands {
		if cmd.Name() == arg {
			tmpl(helpTemplate, cmd)
			return
		}
	}
	PrintErrorAndExit("Unknown help topic")
}

func PrintErrorAndExit(message string) {
	tmpl(fmt.Sprintf(errorTemplate, message), nil)
	os.Exit(2)
}
<commit_msg>Update cmd<commit_after>// Copyright 2017 The go-ego Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// https://github.com/go-ego/ego/blob/master/LICENSE
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

package cmd

import (
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
	"text/template"

	"github.com/go-ego/e/log"
)

const (
	version string = "v0.10.0.1, First!"
)

// Command is the unit of execution
type Command struct {
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string) int

	// PreRun performs an operation before running the command
	PreRun func(cmd *Command, args []string)

	// UsageLine is the one-line usage message.
	// The first word in the line is taken to be the command name.
	UsageLine string

	// Short is the short description shown in the 'go help' output.
	Short string

	// Long is the long message shown in the 'go help <this-command>' output.
	Long string

	// Flag is a set of flags specific to this command.
	Flag flag.FlagSet

	// CustomFlags indicates that the command will do its own
	// flag parsing.
	CustomFlags bool

	// output out writer if set in SetOutput(w)
	output *io.Writer
}

// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// SetOutput sets the destination for usage and error messages.
// If output is nil, os.Stderr is used.
func (c *Command) SetOutput(output io.Writer) {
	c.output = &output
}

// Out returns the out writer of the current command.
// If cmd.output is nil, os.Stderr is used.
func (c *Command) Out() io.Writer {
	if c.output != nil {
		return *c.output
	}
	return log.NewColorWriter(os.Stderr)
}

// Usage puts out the usage for the command.
func (c *Command) Usage() {
	tmpl(cmdUsage, c)
	os.Exit(2)
}

// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as import path.
func (c *Command) Runnable() bool {
	return c.Run != nil
}

func (c *Command) Options() map[string]string {
	options := make(map[string]string)
	c.Flag.VisitAll(func(f *flag.Flag) {
		defaultVal := f.DefValue
		if len(defaultVal) > 0 {
			// if strings.Contains(defaultVal, ":") {
			// 	// Truncate the flag's default value by appending '...' at the end
			// 	options[f.Name+"="+strings.Split(defaultVal, ":")[0]+":..."] = f.Usage
			// } else {
			options[f.Name+"="+defaultVal] = f.Usage
			// }
		} else {
			options[f.Name] = f.Usage
		}
	})
	return options
}

var AvailableCommands = []*Command{
	// cmdGen,
	cmdNew,
	cmdRun,
	cmdPack,
	// cmdApi,
	//cmdRouter,
	//cmdTest,
	cmdBale,
	cmdVersion,
	// cmdGenerate,
	//cmdRundocs,
	cmdMigrate,
	cmdFix,
	// cmdDockerize,
}

var logger = log.GetEgoLogger(os.Stdout)

func IsGenerateDocs(name string, args []string) bool {
	if name != "generate" {
		return false
	}
	for _, a := range args {
		if a == "docs" {
			return true
		}
	}
	return false
}

var usageTemplate = `ego is a Fast and Flexible tool for managing your ego Web Application.

{{"USAGE" | headline}}
    {{"ego command [arguments]" | bold}}

{{"AVAILABLE COMMANDS" | headline}}
{{range .}}{{if .Runnable}}
    {{.Name | printf "%-11s" | bold}} {{.Short}}{{end}}{{end}}

Use {{"ego help [command]" | bold}} for more information about a command.

{{"ADDITIONAL HELP TOPICS" | headline}}
{{range .}}{{if not .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use {{"ego help [topic]" | bold}} for more information about that topic.
`

var helpTemplate = `{{"USAGE" | headline}}
  {{.UsageLine | printf "ego %s" | bold}}
{{if .Options}}{{endline}}{{"OPTIONS" | headline}}{{range $k,$v := .Options}}
  {{$k | printf "-%s" | bold}}
      {{$v}}
  {{end}}{{end}}
{{"DESCRIPTION" | headline}}
  {{tmpltostr .Long . | trim}}
`

var errorTemplate = `ego: %s.
Use {{"ego help" | bold}} for more information.
`

var cmdUsage = `Use {{printf "ego help %s" .Name | bold}} for more information.{{endline}}`

func Usage() {
	tmpl(usageTemplate, AvailableCommands)
	os.Exit(2)
}

func tmpl(text string, data interface{}) {
	output := log.NewColorWriter(os.Stderr)

	t := template.New("usage").Funcs(EgoFuncMap())
	template.Must(t.Parse(text))

	err := t.Execute(output, data)
	MustCheck(err)
}

func Help(args []string) {
	if len(args) == 0 {
		Usage()
	}
	if len(args) != 1 {
		PrintErrorAndExit("Too many arguments")
	}

	arg := args[0]

	for _, cmd := range AvailableCommands {
		if cmd.Name() == arg {
			tmpl(helpTemplate, cmd)
			return
		}
	}
	PrintErrorAndExit("Unknown help topic")
}

func PrintErrorAndExit(message string) {
	tmpl(fmt.Sprintf(errorTemplate, message), nil)
	os.Exit(2)
}
<|endoftext|>