<commit_before>package ccg

//go:generate myccg -funcs AstDecls.Filter -package ccg -output utils.go slice ast.Decl AstDecls
//go:generate myccg -funcs AstSpecs.Filter -package ccg -output utils.go slice ast.Spec AstSpecs

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/token"
	"io"

	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/types"
	"golang.org/x/tools/imports"
)

var (
	pt = fmt.Printf
)

type Config struct {
	From        string
	Params      map[string]string
	Renames     map[string]string
	Writer      io.Writer
	Package     string
	Decls       []ast.Decl
	FuncFilters []func(*ast.FuncDecl) bool
}

func Copy(config Config) error {
	// load package
	var loadConf loader.Config
	loadConf.Import(config.From)
	program, err := loadConf.Load()
	if err != nil {
		return fmt.Errorf("ccg: load package %v", err)
	}
	info := program.Imported[config.From]

	// remove param declarations
	for _, f := range info.Files {
		f.Decls = AstDecls(f.Decls).Filter(func(decl ast.Decl) (ret bool) {
			if decl, ok := decl.(*ast.GenDecl); !ok {
				return true
			} else {
				if decl.Tok == token.TYPE {
					decl.Specs = AstSpecs(decl.Specs).Filter(func(spec ast.Spec) bool {
						name := spec.(*ast.TypeSpec).Name.Name
						_, exists := config.Params[name]
						return !exists
					})
					ret = len(decl.Specs) > 0
				} else if decl.Tok == token.VAR {
					decl.Specs = AstSpecs(decl.Specs).Filter(func(sp ast.Spec) bool {
						spec := sp.(*ast.ValueSpec)
						names := []*ast.Ident{}
						values := []ast.Expr{}
						for i, name := range spec.Names {
							if _, exists := config.Params[name.Name]; !exists {
								names = append(names, name)
								if i < len(spec.Values) {
									values = append(values, spec.Values[i])
								}
							}
						}
						spec.Names = names
						if len(values) == 0 {
							spec.Values = nil
						} else {
							spec.Values = values
						}
						return len(spec.Names) > 0
					})
					ret = len(decl.Specs) > 0
				}
			}
			return
		})
	}

	// collect objects to rename
	objects := make(map[types.Object]string)
	collectObjects := func(mapping map[string]string) error {
		for from, to := range mapping {
			obj := info.Pkg.Scope().Lookup(from)
			if obj == nil {
				return fmt.Errorf("ccg: name not found %s", from)
			}
			objects[obj] = to
		}
		return nil
	}
	if err := collectObjects(config.Params); err != nil {
		return err
	}
	if err := collectObjects(config.Renames); err != nil {
		return err
	}

	// rename
	rename := func(defs map[*ast.Ident]types.Object) {
		for id, obj := range defs {
			if to, ok := objects[obj]; ok {
				id.Name = to
			}
		}
	}
	rename(info.Defs)
	rename(info.Uses)

	// collect existing decls
	existingVars := make(map[string]func(expr ast.Expr))
	existingTypes := make(map[string]func(expr ast.Expr))
	existingFuncs := make(map[string]func(fn *ast.FuncDecl))
	for i, decl := range config.Decls {
		switch decl := decl.(type) {
		case *ast.GenDecl:
			switch decl.Tok {
			case token.VAR:
				for _, spec := range decl.Specs {
					spec := spec.(*ast.ValueSpec)
					for i, name := range spec.Names {
						i := i
						spec := spec
						existingVars[name.Name] = func(expr ast.Expr) {
							spec.Values[i] = expr
						}
					}
				}
			case token.TYPE:
				for i, spec := range decl.Specs {
					spec := spec.(*ast.TypeSpec)
					i := i
					decl := decl
					existingTypes[spec.Name.Name] = func(expr ast.Expr) {
						decl.Specs[i].(*ast.TypeSpec).Type = expr
					}
				}
			}
		case *ast.FuncDecl:
			name := decl.Name.Name
			if decl.Recv != nil {
				name = decl.Recv.List[0].Type.(*ast.Ident).Name + "." + name
			}
			i := i
			existingFuncs[name] = func(fndecl *ast.FuncDecl) {
				config.Decls[i] = fndecl
			}
		}
	}

	// collect output declarations
	decls := []ast.Decl{}
	for _, f := range info.Files {
	loopDecl:
		for _, decl := range f.Decls {
			switch decl := decl.(type) {
			case *ast.GenDecl:
				switch decl.Tok {
				// var
				case token.VAR:
					newDecl := &ast.GenDecl{
						Tok: token.VAR,
					}
					for _, spec := range decl.Specs {
						spec := spec.(*ast.ValueSpec)
						for i, name := range spec.Names {
							if mutator, ok := existingVars[name.Name]; ok {
								mutator(spec.Values[i])
							} else {
								newDecl.Specs = append(newDecl.Specs, spec)
							}
						}
					}
					if len(newDecl.Specs) > 0 {
						decls = append(decls, newDecl)
					}
				// type
				case token.TYPE:
					newDecl := &ast.GenDecl{
						Tok: token.TYPE,
					}
					for _, spec := range decl.Specs {
						name := spec.(*ast.TypeSpec).Name.Name
						if mutator, ok := existingTypes[name]; ok {
							mutator(spec.(*ast.TypeSpec).Type)
						} else {
							newDecl.Specs = append(newDecl.Specs, spec)
						}
					}
					if len(newDecl.Specs) > 0 {
						decls = append(decls, newDecl)
					}
				}
			// func
			case *ast.FuncDecl:
				for _, filter := range config.FuncFilters {
					if !filter(decl) {
						continue loopDecl
					}
				}
				name := decl.Name.Name
				if decl.Recv != nil {
					name = decl.Recv.List[0].Type.(*ast.Ident).Name + "." + name
				}
				if mutator, ok := existingFuncs[name]; ok {
					mutator(decl)
				} else {
					config.Decls = append(config.Decls, decl)
				}
			}
		}
	}
	decls = append(decls, config.Decls...)

	var importDecls, newDecls []ast.Decl
	for _, decl := range decls {
		// ensure linebreak between decls
		switch decl := decl.(type) {
		case *ast.FuncDecl:
			if decl.Doc == nil {
				decl.Doc = new(ast.CommentGroup)
			}
		case *ast.GenDecl:
			if decl.Doc == nil {
				decl.Doc = new(ast.CommentGroup)
			}
		}
		// move import decls to beginning
		if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
			importDecls = append(importDecls, decl)
			continue
		}
		newDecls = append(newDecls, decl)
	}
	decls = append(importDecls, newDecls...)

	// output
	if config.Writer != nil {
		if config.Package != "" { // output complete file
			file := &ast.File{
				Name:  ast.NewIdent(config.Package),
				Decls: decls,
			}
			buf := new(bytes.Buffer)
			err = format.Node(buf, program.Fset, file)
			if err != nil { //NOCOVER
				return fmt.Errorf("ccg: format output %v", err)
			}
			bs, err := imports.Process("", buf.Bytes(), nil)
			if err != nil { //NOCOVER
				return fmt.Errorf("ccg: format output %v", err)
			}
			config.Writer.Write(bs)
		} else { // output decls only
			err = format.Node(config.Writer, program.Fset, decls)
			if err != nil { //NOCOVER
				return fmt.Errorf("ccg: format output %v", err)
			}
		}
	}

	return nil
}
<commit_msg>add VarFilters and TypeFilters<commit_after>package ccg

//go:generate myccg -funcs AstDecls.Filter -package ccg -output utils.go slice ast.Decl AstDecls
//go:generate myccg -funcs AstSpecs.Filter -package ccg -output utils.go slice ast.Spec AstSpecs

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/token"
	"io"

	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/types"
	"golang.org/x/tools/imports"
)

var (
	pt = fmt.Printf
)

type Config struct {
	From        string
	Params      map[string]string
	Renames     map[string]string
	Writer      io.Writer
	Package     string
	Decls       []ast.Decl
	FuncFilters []func(*ast.FuncDecl) bool
	VarFilters  []func(*ast.ValueSpec) bool
	TypeFilters []func(*ast.TypeSpec) bool
}

func Copy(config Config) error {
	// load package
	var loadConf loader.Config
	loadConf.Import(config.From)
	program, err := loadConf.Load()
	if err != nil {
		return fmt.Errorf("ccg: load package %v", err)
	}
	info := program.Imported[config.From]

	// remove param declarations
	for _, f := range info.Files {
		f.Decls = AstDecls(f.Decls).Filter(func(decl ast.Decl) (ret bool) {
			if decl, ok := decl.(*ast.GenDecl); !ok {
				return true
			} else {
				if decl.Tok == token.TYPE {
					decl.Specs = AstSpecs(decl.Specs).Filter(func(spec ast.Spec) bool {
						name := spec.(*ast.TypeSpec).Name.Name
						_, exists := config.Params[name]
						return !exists
					})
					ret = len(decl.Specs) > 0
				} else if decl.Tok == token.VAR {
					decl.Specs = AstSpecs(decl.Specs).Filter(func(sp ast.Spec) bool {
						spec := sp.(*ast.ValueSpec)
						names := []*ast.Ident{}
						values := []ast.Expr{}
						for i, name := range spec.Names {
							if _, exists := config.Params[name.Name]; !exists {
								names = append(names, name)
								if i < len(spec.Values) {
									values = append(values, spec.Values[i])
								}
							}
						}
						spec.Names = names
						if len(values) == 0 {
							spec.Values = nil
						} else {
							spec.Values = values
						}
						return len(spec.Names) > 0
					})
					ret = len(decl.Specs) > 0
				}
			}
			return
		})
	}

	// collect objects to rename
	objects := make(map[types.Object]string)
	collectObjects := func(mapping map[string]string) error {
		for from, to := range mapping {
			obj := info.Pkg.Scope().Lookup(from)
			if obj == nil {
				return fmt.Errorf("ccg: name not found %s", from)
			}
			objects[obj] = to
		}
		return nil
	}
	if err := collectObjects(config.Params); err != nil {
		return err
	}
	if err := collectObjects(config.Renames); err != nil {
		return err
	}

	// rename
	rename := func(defs map[*ast.Ident]types.Object) {
		for id, obj := range defs {
			if to, ok := objects[obj]; ok {
				id.Name = to
			}
		}
	}
	rename(info.Defs)
	rename(info.Uses)

	// collect existing decls
	existingVars := make(map[string]func(expr ast.Expr))
	existingTypes := make(map[string]func(expr ast.Expr))
	existingFuncs := make(map[string]func(fn *ast.FuncDecl))
	for i, decl := range config.Decls {
		switch decl := decl.(type) {
		case *ast.GenDecl:
			switch decl.Tok {
			case token.VAR:
				for _, spec := range decl.Specs {
					spec := spec.(*ast.ValueSpec)
					for i, name := range spec.Names {
						i := i
						spec := spec
						existingVars[name.Name] = func(expr ast.Expr) {
							spec.Values[i] = expr
						}
					}
				}
			case token.TYPE:
				for i, spec := range decl.Specs {
					spec := spec.(*ast.TypeSpec)
					i := i
					decl := decl
					existingTypes[spec.Name.Name] = func(expr ast.Expr) {
						decl.Specs[i].(*ast.TypeSpec).Type = expr
					}
				}
			}
		case *ast.FuncDecl:
			name := decl.Name.Name
			if decl.Recv != nil {
				name = decl.Recv.List[0].Type.(*ast.Ident).Name + "." + name
			}
			i := i
			existingFuncs[name] = func(fndecl *ast.FuncDecl) {
				config.Decls[i] = fndecl
			}
		}
	}

	// collect output declarations
	decls := []ast.Decl{}
	for _, f := range info.Files {
	loopDecl:
		for _, decl := range f.Decls {
			switch decl := decl.(type) {
			case *ast.GenDecl:
				switch decl.Tok {
				// var
				case token.VAR:
					newDecl := &ast.GenDecl{
						Tok: token.VAR,
					}
				loopVar:
					for _, spec := range decl.Specs {
						spec := spec.(*ast.ValueSpec)
						for _, filter := range config.VarFilters {
							if !filter(spec) {
								continue loopVar
							}
						}
						for i, name := range spec.Names {
							if mutator, ok := existingVars[name.Name]; ok {
								mutator(spec.Values[i])
							} else {
								newDecl.Specs = append(newDecl.Specs, spec)
							}
						}
					}
					if len(newDecl.Specs) > 0 {
						decls = append(decls, newDecl)
					}
				// type
				case token.TYPE:
					newDecl := &ast.GenDecl{
						Tok: token.TYPE,
					}
				loopType:
					for _, spec := range decl.Specs {
						spec := spec.(*ast.TypeSpec)
						for _, filter := range config.TypeFilters {
							if !filter(spec) {
								continue loopType
							}
						}
						name := spec.Name.Name
						if mutator, ok := existingTypes[name]; ok {
							mutator(spec.Type)
						} else {
							newDecl.Specs = append(newDecl.Specs, spec)
						}
					}
					if len(newDecl.Specs) > 0 {
						decls = append(decls, newDecl)
					}
				}
			// func
			case *ast.FuncDecl:
				for _, filter := range config.FuncFilters {
					if !filter(decl) {
						continue loopDecl
					}
				}
				name := decl.Name.Name
				if decl.Recv != nil {
					name = decl.Recv.List[0].Type.(*ast.Ident).Name + "." + name
				}
				if mutator, ok := existingFuncs[name]; ok {
					mutator(decl)
				} else {
					config.Decls = append(config.Decls, decl)
				}
			}
		}
	}
	decls = append(decls, config.Decls...)

	var importDecls, newDecls []ast.Decl
	for _, decl := range decls {
		// ensure linebreak between decls
		switch decl := decl.(type) {
		case *ast.FuncDecl:
			if decl.Doc == nil {
				decl.Doc = new(ast.CommentGroup)
			}
		case *ast.GenDecl:
			if decl.Doc == nil {
				decl.Doc = new(ast.CommentGroup)
			}
		}
		// move import decls to beginning
		if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
			importDecls = append(importDecls, decl)
			continue
		}
		newDecls = append(newDecls, decl)
	}
	decls = append(importDecls, newDecls...)

	// output
	if config.Writer != nil {
		if config.Package != "" { // output complete file
			file := &ast.File{
				Name:  ast.NewIdent(config.Package),
				Decls: decls,
			}
			buf := new(bytes.Buffer)
			err = format.Node(buf, program.Fset, file)
			if err != nil { //NOCOVER
				return fmt.Errorf("ccg: format output %v", err)
			}
			bs, err := imports.Process("", buf.Bytes(), nil)
			if err != nil { //NOCOVER
				return fmt.Errorf("ccg: format output %v", err)
			}
			config.Writer.Write(bs)
		} else { // output decls only
			err = format.Node(config.Writer, program.Fset, decls)
			if err != nil { //NOCOVER
				return fmt.Errorf("ccg: format output %v", err)
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2015 Victor Antonovich <victor@antonovich.me>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	CFG_FILE        = "kube-template"
	CFG_MASTER      = FLAG_MASTER
	CFG_POLL_TIME   = FLAG_POLL_TIME
	CFG_POLL_PERIOD = FLAG_POLL_PERIOD
)

var cfgFile string

type Config struct {
	// Do not write template output
	DryRun bool
	// Run template processing once and exit
	RunOnce bool
	// Guess Kubernetes API settings from POD environment
	GuessKubeAPISettings bool
	// Kubernetes config file
	KubeConfig string
	// Kubernetes API server address
	Master string
	// Kubernetes API server poll period
	PollPeriod time.Duration

	// Template delimiters
	LeftDelimiter  string
	RightDelimiter string

	// Template paths
	templatePaths map[string]bool

	// Template descriptors
	TemplateDescriptors []*TemplateDescriptor
}

type TemplateDescriptor struct {
	// Template file path
	Path string
	// Template output path
	Output string
	// Optional command to execute after template output updating
	Command string
}

func readConfig(cmd *cobra.Command) error {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile) // specify config file set by flag
	} else {
		viper.SetConfigName(CFG_FILE) // default name of config file (without extension)
		viper.AddConfigPath(".")      // adding home directory as first search path
	}

	viper.AutomaticEnv() // read in environment variables that match

	if err := viper.BindPFlag(CFG_MASTER, cmd.Flags().Lookup(FLAG_MASTER)); err != nil {
		return err
	}

	if err := viper.BindPFlag(CFG_POLL_PERIOD, cmd.Flags().Lookup(FLAG_POLL_PERIOD)); err != nil {
		return err
	}

	err := viper.ReadInConfig()

	if err == nil {
		glog.V(1).Infof("using config file: %s", viper.ConfigFileUsed())
		return nil
	}

	// raise error if config file used but can't be read
	if viper.ConfigFileUsed() != "" {
		return err
	}

	return nil
}

// Parses a string in format 'templatePath:outputPath[:command]' into a TemplateDescriptor struct
func parseTemplateDescriptor(s string) (*TemplateDescriptor, error) {
	if len(strings.TrimSpace(s)) == 0 {
		return nil, errors.New("empty template descriptor string")
	}

	var path, output, command string
	parts := strings.SplitN(s, ":", 3)

	switch len(parts) {
	case 2:
		path, output = parts[0], parts[1]
	case 3:
		path, output, command = parts[0], parts[1], parts[2]
	default:
		return nil, errors.New("invalid template descriptor, should be 'templatePath:outputPath[:command]'")
	}

	return &TemplateDescriptor{
		Path:    path,
		Output:  output,
		Command: command,
	}, nil
}

func newConfig(cmd *cobra.Command) (*Config, error) {
	// Create empty config
	config := new(Config)
	// Get command-line only options
	dryRun, err := cmd.Flags().GetBool(FLAG_DRY_RUN)
	if err != nil {
		return nil, err
	}
	config.DryRun = dryRun
	runOnce, err := cmd.Flags().GetBool(FLAG_RUN_ONCE)
	if err != nil {
		return nil, err
	}
	config.RunOnce = runOnce
	guessKubeAPISettings, err := cmd.Flags().GetBool(FLAG_GUESS_KUBE_API_SETTINGS)
	if err != nil {
		return nil, err
	}
	config.GuessKubeAPISettings = guessKubeAPISettings
	kubeConfig, err := cmd.Flags().GetString(FLAG_KUBE_CONFIG)
	if err != nil {
		return nil, err
	}
	config.KubeConfig = kubeConfig

	leftDelimiter, err := cmd.Flags().GetString(FLAG_LEFT_DELIM)
	if err != nil {
		return nil, err
	}
	config.LeftDelimiter = leftDelimiter

	rightDelimiter, err := cmd.Flags().GetString(FLAG_RIGHT_DELIM)
	if err != nil {
		return nil, err
	}
	config.RightDelimiter = rightDelimiter

	// Read config from file, if present
	err = readConfig(cmd)
	if err != nil {
		return nil, err
	}
	// Get command line / config options
	config.Master = viper.GetString(CFG_MASTER)
	if viper.IsSet(CFG_POLL_TIME) {
		config.PollPeriod = viper.GetDuration(CFG_POLL_TIME)
		glog.Warningf("'%s' parameter is deprecated, use '%s' instead", CFG_POLL_TIME, CFG_POLL_PERIOD)
	} else {
		config.PollPeriod = viper.GetDuration(CFG_POLL_PERIOD)
	}
	glog.V(2).Infof("poll period set to %v", config.PollPeriod)
	// Add template descriptors specified by command line
	cmdTemplates, err := cmd.Flags().GetStringSlice(FLAG_TEMPLATE)
	if err != nil {
		return nil, err
	}
	config.templatePaths = make(map[string]bool)
	config.TemplateDescriptors = make([]*TemplateDescriptor, 0)
	for _, template := range cmdTemplates {
		d, err := parseTemplateDescriptor(template)
		if err != nil {
			glog.Errorf("can't parse '%s': %v", template, err)
		} else {
			glog.V(2).Infof("adding template from command line: %s", d.Path)
			config.appendTemplateDescriptor(d)
		}
	}
	// Merge template descriptors from config file
	if iCfgTemplates := viper.Get("templates"); iCfgTemplates != nil {
		for _, iCfgTemplate := range iCfgTemplates.([]interface{}) {
			cfgTemplate := iCfgTemplate.(map[interface{}](interface{}))
			// Check template path and output path are present
			iPath, pathPresent := cfgTemplate["path"]
			iOutput, outputPresent := cfgTemplate["output"]
			if !pathPresent || !outputPresent {
				glog.Warningf("skipped non-complete template descriptor: %#v", cfgTemplate)
				continue
			}
			path, output := iPath.(string), iOutput.(string)
			// Command is optional
			var cmd string
			if iCmd, cmdPresent := cfgTemplate["command"]; cmdPresent {
				cmd = iCmd.(string)
			}
			// Add template descriptor
			d := &TemplateDescriptor{
				Path:    path,
				Output:  output,
				Command: cmd,
			}
			glog.V(2).Infof("adding template from config file: %s", d.Path)
			config.appendTemplateDescriptor(d)
		}
	}

	return config, nil
}

func (cfg *Config) appendTemplateDescriptor(d *TemplateDescriptor) {
	if _, added := cfg.templatePaths[d.Path]; !added {
		cfg.templatePaths[d.Path] = true
		cfg.TemplateDescriptors = append(cfg.TemplateDescriptors, d)
	} else {
		glog.Warningf("template already added: %s", d.Path)
	}
}

func (cfg *Config) PollingEnabled() bool {
	return !cfg.RunOnce && cfg.PollTime.Nanoseconds() > 0
}
<commit_msg>Fix cfg.PollingEnabled() function<commit_after>// Copyright © 2015 Victor Antonovich <victor@antonovich.me>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	CFG_FILE        = "kube-template"
	CFG_MASTER      = FLAG_MASTER
	CFG_POLL_TIME   = FLAG_POLL_TIME
	CFG_POLL_PERIOD = FLAG_POLL_PERIOD
)

var cfgFile string

type Config struct {
	// Do not write template output
	DryRun bool
	// Run template processing once and exit
	RunOnce bool
	// Guess Kubernetes API settings from POD environment
	GuessKubeAPISettings bool
	// Kubernetes config file
	KubeConfig string
	// Kubernetes API server address
	Master string
	// Kubernetes API server poll period
	PollPeriod time.Duration

	// Template delimiters
	LeftDelimiter  string
	RightDelimiter string

	// Template paths
	templatePaths map[string]bool

	// Template descriptors
	TemplateDescriptors []*TemplateDescriptor
}

type TemplateDescriptor struct {
	// Template file path
	Path string
	// Template output path
	Output string
	// Optional command to execute after template output updating
	Command string
}

func readConfig(cmd *cobra.Command) error {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile) // specify config file set by flag
	} else {
		viper.SetConfigName(CFG_FILE) // default name of config file (without extension)
		viper.AddConfigPath(".")      // adding home directory as first search path
	}

	viper.AutomaticEnv() // read in environment variables that match

	if err := viper.BindPFlag(CFG_MASTER, cmd.Flags().Lookup(FLAG_MASTER)); err != nil {
		return err
	}

	if err := viper.BindPFlag(CFG_POLL_PERIOD, cmd.Flags().Lookup(FLAG_POLL_PERIOD)); err != nil {
		return err
	}

	err := viper.ReadInConfig()

	if err == nil {
		glog.V(1).Infof("using config file: %s", viper.ConfigFileUsed())
		return nil
	}

	// raise error if config file used but can't be read
	if viper.ConfigFileUsed() != "" {
		return err
	}

	return nil
}

// Parses a string in format 'templatePath:outputPath[:command]' into a TemplateDescriptor struct
func parseTemplateDescriptor(s string) (*TemplateDescriptor, error) {
	if len(strings.TrimSpace(s)) == 0 {
		return nil, errors.New("empty template descriptor string")
	}

	var path, output, command string
	parts := strings.SplitN(s, ":", 3)

	switch len(parts) {
	case 2:
		path, output = parts[0], parts[1]
	case 3:
		path, output, command = parts[0], parts[1], parts[2]
	default:
		return nil, errors.New("invalid template descriptor, should be 'templatePath:outputPath[:command]'")
	}

	return &TemplateDescriptor{
		Path:    path,
		Output:  output,
		Command: command,
	}, nil
}

func newConfig(cmd *cobra.Command) (*Config, error) {
	// Create empty config
	config := new(Config)
	// Get command-line only options
	dryRun, err := cmd.Flags().GetBool(FLAG_DRY_RUN)
	if err != nil {
		return nil, err
	}
	config.DryRun = dryRun
	runOnce, err := cmd.Flags().GetBool(FLAG_RUN_ONCE)
	if err != nil {
		return nil, err
	}
	config.RunOnce = runOnce
	guessKubeAPISettings, err := cmd.Flags().GetBool(FLAG_GUESS_KUBE_API_SETTINGS)
	if err != nil {
		return nil, err
	}
	config.GuessKubeAPISettings = guessKubeAPISettings
	kubeConfig, err := cmd.Flags().GetString(FLAG_KUBE_CONFIG)
	if err != nil {
		return nil, err
	}
	config.KubeConfig = kubeConfig

	leftDelimiter, err := cmd.Flags().GetString(FLAG_LEFT_DELIM)
	if err != nil {
		return nil, err
	}
	config.LeftDelimiter = leftDelimiter

	rightDelimiter, err := cmd.Flags().GetString(FLAG_RIGHT_DELIM)
	if err != nil {
		return nil, err
	}
	config.RightDelimiter = rightDelimiter

	// Read config from file, if present
	err = readConfig(cmd)
	if err != nil {
		return nil, err
	}
	// Get command line / config options
	config.Master = viper.GetString(CFG_MASTER)
	if viper.IsSet(CFG_POLL_TIME) {
		config.PollPeriod = viper.GetDuration(CFG_POLL_TIME)
		glog.Warningf("'%s' parameter is deprecated, use '%s' instead", CFG_POLL_TIME, CFG_POLL_PERIOD)
	} else {
		config.PollPeriod = viper.GetDuration(CFG_POLL_PERIOD)
	}
	glog.V(2).Infof("poll period set to %v", config.PollPeriod)
	// Add template descriptors specified by command line
	cmdTemplates, err := cmd.Flags().GetStringSlice(FLAG_TEMPLATE)
	if err != nil {
		return nil, err
	}
	config.templatePaths = make(map[string]bool)
	config.TemplateDescriptors = make([]*TemplateDescriptor, 0)
	for _, template := range cmdTemplates {
		d, err := parseTemplateDescriptor(template)
		if err != nil {
			glog.Errorf("can't parse '%s': %v", template, err)
		} else {
			glog.V(2).Infof("adding template from command line: %s", d.Path)
			config.appendTemplateDescriptor(d)
		}
	}
	// Merge template descriptors from config file
	if iCfgTemplates := viper.Get("templates"); iCfgTemplates != nil {
		for _, iCfgTemplate := range iCfgTemplates.([]interface{}) {
			cfgTemplate := iCfgTemplate.(map[interface{}](interface{}))
			// Check template path and output path are present
			iPath, pathPresent := cfgTemplate["path"]
			iOutput, outputPresent := cfgTemplate["output"]
			if !pathPresent || !outputPresent {
				glog.Warningf("skipped non-complete template descriptor: %#v", cfgTemplate)
				continue
			}
			path, output := iPath.(string), iOutput.(string)
			// Command is optional
			var cmd string
			if iCmd, cmdPresent := cfgTemplate["command"]; cmdPresent {
				cmd = iCmd.(string)
			}
			// Add template descriptor
			d := &TemplateDescriptor{
				Path:    path,
				Output:  output,
				Command: cmd,
			}
			glog.V(2).Infof("adding template from config file: %s", d.Path)
			config.appendTemplateDescriptor(d)
		}
	}

	return config, nil
}

func (cfg *Config) appendTemplateDescriptor(d *TemplateDescriptor) {
	if _, added := cfg.templatePaths[d.Path]; !added {
		cfg.templatePaths[d.Path] = true
		cfg.TemplateDescriptors = append(cfg.TemplateDescriptors, d)
	} else {
		glog.Warningf("template already added: %s", d.Path)
	}
}

func (cfg *Config) PollingEnabled() bool {
	return !cfg.RunOnce && cfg.PollPeriod.Nanoseconds() > 0
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/urfave/cli"
	"golang.org/x/net/context"
)

func main() {
	app := cli.NewApp()
	app.Name = "blocks-gcs-proxy"
	app.Usage = "github.com/groovenauts/blocks-gcs-proxy"
	app.Version = VERSION

	configFlag := cli.StringFlag{
		Name:  "config, c",
		Usage: "Load configuration from `FILE`",
	}
	app.Flags = []cli.Flag{
		configFlag,
	}

	app.Commands = []cli.Command{
		{
			Name:  "check",
			Usage: "Check config file is valid",
			Action: func(c *cli.Context) error {
				LoadAndSetupProcessConfig(c)
				fmt.Println("OK")
				return nil
			},
			Flags: []cli.Flag{
				configFlag,
			},
		},
	}

	app.Action = run

	app.Run(os.Args)
}

func run(c *cli.Context) error {
	config := LoadAndSetupProcessConfig(c)

	ctx := context.Background()

	p := &Process{config: config}
	err := p.setup(ctx)
	if err != nil {
		fmt.Printf("Error to setup Process cause of %v\n", err)
		os.Exit(1)
	}

	err = p.run()
	if err != nil {
		fmt.Printf("Error to run cause of %v\n", err)
		os.Exit(1)
	}
	return nil
}

func LoadAndSetupProcessConfig(c *cli.Context) *ProcessConfig {
	path := configPath(c)
	config, err := LoadProcessConfig(path)
	if err != nil {
		fmt.Printf("Error to load %v cause of %v\n", path, err)
		os.Exit(1)
	}
	err = config.setup(os.Args[1:])
	if err != nil {
		fmt.Printf("Error to setup %v cause of %v\n", path, err)
		os.Exit(1)
	}
	return config
}

func configPath(c *cli.Context) string {
	r := c.String("config")
	if r == "" {
		r = "./config.json"
	}
	return r
}
<commit_msg>:+1: Add upload subcommand<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/urfave/cli"
	"golang.org/x/net/context"
)

func main() {
	app := cli.NewApp()
	app.Name = "blocks-gcs-proxy"
	app.Usage = "github.com/groovenauts/blocks-gcs-proxy"
	app.Version = VERSION

	configFlag := cli.StringFlag{
		Name:  "config, c",
		Usage: "Load configuration from `FILE`",
	}
	app.Flags = []cli.Flag{
		configFlag,
	}

	app.Commands = []cli.Command{
		{
			Name:  "check",
			Usage: "Check config file is valid",
			Action: func(c *cli.Context) error {
				LoadAndSetupProcessConfig(c)
				fmt.Println("OK")
				return nil
			},
			Flags: []cli.Flag{
				configFlag,
			},
		},
		{
			Name:  "upload",
			Usage: "Upload the files under uploads directory",
			Action: func(c *cli.Context) error {
				config := &ProcessConfig{}
				config.setup([]string{})
				config.Command.Uploaders = c.Int("uploaders")
				job := &Job{
					config: config.Command,
					uploads_dir: c.String("uploads_dir"),
				}
				err := job.uploadFiles()
				return err
			},
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "uploads_dir, d",
					Usage: "Path to the directory which has bucket_name/path/to/file",
				},
				cli.IntFlag{
					Name:  "uploaders, n",
					Usage: "Number of uploaders",
					Value: 1,
				},
			},
		},
	}

	app.Action = run

	app.Run(os.Args)
}

func run(c *cli.Context) error {
	config := LoadAndSetupProcessConfig(c)

	ctx := context.Background()

	p := &Process{config: config}
	err := p.setup(ctx)
	if err != nil {
		fmt.Printf("Error to setup Process cause of %v\n", err)
		os.Exit(1)
	}

	err = p.run()
	if err != nil {
		fmt.Printf("Error to run cause of %v\n", err)
		os.Exit(1)
	}
	return nil
}

func LoadAndSetupProcessConfig(c *cli.Context) *ProcessConfig {
	path := configPath(c)
	config, err := LoadProcessConfig(path)
	if err != nil {
		fmt.Printf("Error to load %v cause of %v\n", path, err)
		os.Exit(1)
	}
	err = config.setup(os.Args[1:])
	if err != nil {
		fmt.Printf("Error to setup %v cause of %v\n", path, err)
		os.Exit(1)
	}
	return config
}

func configPath(c *cli.Context) string {
	r := c.String("config")
	if r == "" {
		r = "./config.json"
	}
	return r
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

type Command struct {
	Name, Usage string
	Execute     func([]string) error
}

const (
	cmdSymbol = "~ "
)

var (
	Commands []*Command
)

// GetCommandNames returns a slice of all command names.
func GetCommandNames() []string {
	allNames := make([]string, 0, len(Commands))
	for _, cmd := range Commands {
		allNames = append(allNames, cmd.Name)
	}
	return allNames
}

// GetCommand either returns a command or and error.
func GetCommand(name string) (*Command, error) {
	for _, cmd := range Commands {
		if cmd.Name == name {
			return cmd, nil
		}
	}
	return nil, errors.New("command not found")
}

// printMessage prints out the items with the prefix [CLI].
func printMessage(items ...interface{}) {
	fmt.Print("[CLI] ")
	fmt.Println(items...)
}

// RegisterCommand registers a new command with a name, a usage string and an executable function.
func RegisterCommand(name, usage string, executeFnc func([]string) error) {
	cmd := Command{
		Name:    name,
		Usage:   usage,
		Execute: executeFnc,
	}
	Commands = append(Commands, &cmd)
}

// init loads a standard set of commands.
func init() {
	RegisterCommand("reload", "> reload", func(args []string) error {
		printMessage("reload templates, posts and pages")
		Reload()
		return nil
	})
	RegisterCommand("stop", "> stop", func(args []string) error {
		printMessage("stop the server")
		os.Exit(1)
		return nil
	})
	RegisterCommand("debug", "> debug [mode]", func(args []string) error {
		if len(args) != 1 {
			return errors.New("bad arguments")
		}
		switch args[0] {
		case "on":
			logFlags = log.Ltime | log.Lshortfile
			initLogger(os.Stdout)
			printMessage("activated debug mode")
		case "off":
			logFlags = log.Ldate | log.Ltime
			initLogger(ioutil.Discard)
			printMessage("deactivated debug mode")
		default:
			return errors.New("unknown mode:" + args[0])
		}
		return nil
	})
	RegisterCommand("help", "> help [command]", func(args []string) error {
		if len(args) == 0 {
			printMessage("help:", strings.Join(GetCommandNames(), ", "))
		} else if len(args) == 1 {
			cmd, err := GetCommand(args[0])
			if err != nil {
				return err
			}
			printMessage("help:", cmd.Usage)
		} else {
			return errors.New("bad arguments")
		}
		return nil
	})
}

func startInteractiveMode() {
	printMessage("bloggy", BloggyVersionTag)
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Print(cmdSymbol)
		line, err := reader.ReadString('\n')
		if err != nil {
			break
		}
		// Collect tokens from user input
		tokens := strings.Split(line, " ")
		for i, _ := range tokens {
			tokens[i] = strings.Trim(tokens[i], " \n\t\r")
		}

		// Just continue if no command is given
		if len(tokens) < 1 {
			continue
		} else {
			name := strings.ToLower(tokens[0])
			cmd, err := GetCommand(name)
			if err != nil {
				printMessage("error:", err)
				continue
			}

			err = cmd.Execute(tokens[1:])
			if err != nil {
				printMessage("error:", err)
				continue
			}
		}
	}
}
<commit_msg>Rename commands slice and enhance general quality<commit_after>package main

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

// Command is a CLI command.
type Command struct {
	Name, Usage string
	Execute     func([]string) error
}

const (
	cmdSymbol = "~ "
)

var (
	commands []*Command
)

// GetCommandNames returns a slice of all command names.
func GetCommandNames() []string {
	allNames := make([]string, 0, len(commands))
	for _, cmd := range commands {
		allNames = append(allNames, cmd.Name)
	}
	return allNames
}

// GetCommand either returns a command or and error.
func GetCommand(name string) (*Command, error) {
	for _, cmd := range commands {
		if cmd.Name == name {
			return cmd, nil
		}
	}
	return nil, errors.New("command not found")
}

// printMessage prints out the items with the prefix [CLI].
func printMessage(items ...interface{}) {
	fmt.Print("[CLI] ")
	fmt.Println(items...)
}

// RegisterCommand registers a new command with a name, a usage string and an executable function.
func RegisterCommand(name, usage string, executeFnc func([]string) error) {
	cmd := Command{
		Name:    name,
		Usage:   usage,
		Execute: executeFnc,
	}
	commands = append(commands, &cmd)
}

// init loads a standard set of command.
func init() {
	RegisterCommand("reload", "> reload", func(args []string) error {
		printMessage("reload templates, posts and pages")
		Reload()
		return nil
	})
	RegisterCommand("stop", "> stop", func(args []string) error {
		printMessage("stop the server")
		os.Exit(1)
		return nil
	})
	RegisterCommand("debug", "> debug [mode]", func(args []string) error {
		if len(args) != 1 {
			return errors.New("bad arguments")
		}
		switch args[0] {
		case "on":
			logFlags = log.Ltime | log.Lshortfile
			initLogger(os.Stdout)
			printMessage("activated debug mode")
		case "off":
			logFlags = log.Ldate | log.Ltime
			initLogger(ioutil.Discard)
			printMessage("deactivated debug mode")
		default:
			return errors.New("unknown mode:" + args[0])
		}
		return nil
	})
	RegisterCommand("help", "> help [command]", func(args []string) error {
		if len(args) == 0 {
			printMessage("help:", strings.Join(GetCommandNames(), ", "))
		} else if len(args) == 1 {
			cmd, err := GetCommand(args[0])
			if err != nil {
				return err
			}
			printMessage("help:", cmd.Usage)
		} else {
			return errors.New("bad arguments")
		}
		return nil
	})
}

func startInteractiveMode() {
	printMessage("bloggy", BloggyVersionTag)
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Print(cmdSymbol)
		line, err := reader.ReadString('\n')
		if err != nil {
			break
		}
		// Collect tokens from user input
		tokens := strings.Split(line, " ")
		for i := range tokens {
			tokens[i] = strings.Trim(tokens[i], " \n\t\r")
		}

		// Just continue if no command is given
		if len(tokens) < 1 {
			continue
		} else {
			name := strings.ToLower(tokens[0])
			cmd, err := GetCommand(name)
			if err != nil {
				printMessage("error:", err)
				continue
			}

			err = cmd.Execute(tokens[1:])
			if err != nil {
				printMessage("error:", err)
				continue
			}
		}
	}
}
<|endoftext|>


<commit_before>package ghch

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/template"
	"time"

	"github.com/jessevdk/go-flags"
	"github.com/octokit/go-octokit/octokit"
)

type ghOpts struct {
	RepoPath    string `short:"r" long:"repo" default:"." description:"git repository path"`
	GitPath     string `short:"g" long:"git" default:"git" description:"git path"`
	From        string `short:"f" long:"from" description:"git commit revision range start from"`
	To          string `short:"t" long:"to" description:"git commit revision range end to"`
	Token       string `          long:"token" description:"github token"`
	Verbose     bool   `short:"v" long:"verbose"`
	Remote      string `          long:"remote" default:"origin" description:"default remote name"`
	Format      string `short:"F" long:"format" description:"json or markdown"`
	All         bool   `short:"A" long:"all" description:"output all changes"`
	NextVersion string `short:"N" long:"next-version"`
	Write       bool   `short:"w" description:"write result to file"`
	changelogMd string
	// Tmpl string
}

const (
	exitCodeOK = iota
	exitCodeParseFlagError
	exitCodeErr
)

// CLI is struct for command line tool
type CLI struct {
	OutStream, ErrStream io.Writer
}

// Run the ghch
func (cli *CLI) Run(argv []string) int {
	log.SetOutput(cli.ErrStream)
	p, opts, err := parseArgs(argv)
	if err != nil {
		if ferr, ok := err.(*flags.Error); !ok || ferr.Type != flags.ErrHelp {
			p.WriteHelp(cli.ErrStream)
		}
		return exitCodeParseFlagError
	}

	gh := (&ghch{
		remote:   opts.Remote,
		repoPath: opts.RepoPath,
		gitPath:  opts.GitPath,
		verbose:  opts.Verbose,
		token:    opts.Token,
	}).initialize()

	if opts.All {
		chlog := Changelog{}
		vers := append(gh.versions(), "")
		prevRev := ""
		for _, rev := range vers {
			r, err := gh.getSection(rev, prevRev)
			if err != nil {
				log.Print(err)
				return exitCodeErr
			}
			if prevRev == "" && opts.NextVersion != "" {
				r.ToRevision = opts.NextVersion
			}
			chlog.Sections = append(chlog.Sections, r)
			prevRev = rev
		}

		if opts.Format == "markdown" {
			results := make([]string, len(chlog.Sections))
			for i, v := range chlog.Sections {
				results[i], _ = v.toMkdn()
			}

			if opts.Write {
				content := "# Changelog\n\n" + strings.Join(results, "\n\n")
				err := ioutil.WriteFile(opts.changelogMd, []byte(content), 0644)
				if err != nil {
					log.Print(err)
					return exitCodeErr
				}
			} else {
				fmt.Fprintln(cli.OutStream, strings.Join(results, "\n\n"))
			}
		} else {
			jsn, _ := json.MarshalIndent(chlog, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	} else {
		if opts.From == "" && opts.To == "" {
			opts.From = gh.getLatestSemverTag()
		}
		r, err := gh.getSection(opts.From, opts.To)
		if err != nil {
			log.Print(err)
			return exitCodeErr
		}
		if r.ToRevision == "" && opts.NextVersion != "" {
			r.ToRevision = opts.NextVersion
		}
		if opts.Format == "markdown" {
			str, err := r.toMkdn()
			if err != nil {
				log.Print(err)
				return exitCodeErr
			}
			if opts.Write {
				content := ""
				if exists(opts.changelogMd) {
					byt, err := ioutil.ReadFile(opts.changelogMd)
					if err != nil {
						log.Print(err)
						return exitCodeErr
					}
					content = insertNewChangelog(byt, str)
				} else {
					content = "# Changelog\n\n" + str
				}
				err = ioutil.WriteFile(opts.changelogMd, []byte(content), 0644)
				if err != nil {
					log.Print(err)
					return exitCodeErr
				}
			} else {
				fmt.Fprintln(cli.OutStream, str)
			}
		} else {
			jsn, _ := json.MarshalIndent(r, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	}
	return exitCodeOK
}

func insertNewChangelog(orig []byte, section string) string {
	var bf bytes.Buffer
	lineSnr := bufio.NewScanner(bytes.NewReader(orig))
	inserted := false
	for lineSnr.Scan() {
		line := lineSnr.Text()
		if !inserted && strings.HasPrefix(line, "## ") {
			bf.WriteString(section)
			bf.WriteString("\n\n")
			inserted = true
		}
		bf.WriteString(line)
		bf.WriteString("\n")
	}
	if !inserted {
		bf.WriteString(section)
	}
	return bf.String()
}

func exists(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil
}

func parseArgs(args []string) (*flags.Parser, *ghOpts, error) {
	opts := &ghOpts{}
	p := flags.NewParser(opts, flags.Default)
	p.Usage = fmt.Sprintf("[OPTIONS]\n\nVersion: %s (rev: %s)", version, revision)
	rest, err := p.ParseArgs(args)
	if opts.Write {
		opts.Format = "markdown"
		opts.changelogMd = "CHANGELOG.md"
		if len(rest) > 0 {
			opts.changelogMd = rest[0]
		}
	}
	return p, opts, err
}

func (gh *ghch) getSection(from, to string) (Section, error) {
	if from == "" {
		from, _ = gh.cmd("rev-list", "--max-parents=0", "HEAD")
		from = strings.TrimSpace(from)
		if len(from) > 12 {
			from = from[:12]
		}
	}
	r, err := gh.mergedPRs(from, to)
	if err != nil {
		return Section{}, err
	}
	t, err := gh.getChangedAt(to)
	if err != nil {
		return Section{}, err
	}
	owner, repo := gh.ownerAndRepo()
	return Section{
		PullRequests: r,
		FromRevision: from,
		ToRevision:   to,
		ChangedAt:    t,
		Owner:        owner,
		Repo:         repo,
	}, nil
}

// Changelog contains Sectionst
type Changelog struct {
	Sections []Section `json:"Sections"`
}

// Section contains changes between two revisions
type Section struct {
	PullRequests []*octokit.PullRequest `json:"pull_requests"`
	FromRevision string                 `json:"from_revision"`
	ToRevision   string                 `json:"to_revision"`
	ChangedAt    time.Time              `json:"changed_at"`
	Owner        string                 `json:"owner"`
	Repo         string                 `json:"repo"`
}

var tmplStr = `{{$ret := . -}}
## [{{.ToRevision}}](https://github.com/{{.Owner}}/{{.Repo}}/compare/{{.FromRevision}}...{{.ToRevision}}) ({{.ChangedAt.Format "2006-01-02"}})
{{range .PullRequests}}
* {{.Title}} [#{{.Number}}](https://github.com/{{$ret.Owner}}/{{$ret.Repo}}/pull/{{.Number}}) ([{{.User.Login}}](https://github.com/{{.User.Login}}))
{{- end}}`

var mdTmpl *template.Template

func init() {
	var err error
	mdTmpl, err = template.New("md-changelog").Parse(tmplStr)
	if err != nil {
		log.Fatal(err)
	}
}

func (rs Section) toMkdn() (string, error) {
	var b bytes.Buffer
	err := mdTmpl.Execute(&b, rs)
	if err != nil {
		return "", err
	}
	return b.String(), nil
}
<commit_msg>add --latest option for output changes between two semantic versioned tags<commit_after>package ghch

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"text/template"
	"time"

	"github.com/jessevdk/go-flags"
	"github.com/octokit/go-octokit/octokit"
)

type ghOpts struct {
	RepoPath    string `short:"r" long:"repo" default:"." description:"git repository path"`
	GitPath     string `short:"g" long:"git" default:"git" description:"git path"`
	From        string `short:"f" long:"from" description:"git commit revision range start from"`
	To          string `short:"t" long:"to" description:"git commit revision range end to"`
	Latest      bool   `          long:"latest" description:"output changes between latest two semantic versioned tags"`
	Token       string `          long:"token" description:"github token"`
	Verbose     bool   `short:"v" long:"verbose"`
	Remote      string `          long:"remote" default:"origin" description:"default remote name"`
	Format      string `short:"F" long:"format" description:"json or markdown"`
	All         bool   `short:"A" long:"all" description:"output all changes"`
	NextVersion string `short:"N" long:"next-version"`
	Write       bool   `short:"w" description:"write result to file"`
	changelogMd string
	// Tmpl string
}

const (
	exitCodeOK = iota
	exitCodeParseFlagError
	exitCodeErr
)

// CLI is struct for command line tool
type CLI struct {
	OutStream, ErrStream io.Writer
}

// Run the ghch
func (cli *CLI) Run(argv []string) int {
	log.SetOutput(cli.ErrStream)
	p, opts, err := parseArgs(argv)
	if err != nil {
		if ferr, ok := err.(*flags.Error); !ok || ferr.Type != flags.ErrHelp {
			p.WriteHelp(cli.ErrStream)
		}
		return exitCodeParseFlagError
	}

	gh := (&ghch{
		remote:   opts.Remote,
		repoPath: opts.RepoPath,
		gitPath:  opts.GitPath,
		verbose:  opts.Verbose,
		token:    opts.Token,
	}).initialize()

	if opts.All {
		chlog := Changelog{}
		vers := append(gh.versions(), "")
		prevRev := ""
		for _, rev := range vers {
			r, err := gh.getSection(rev, prevRev)
			if err != nil {
				log.Print(err)
				return exitCodeErr
			}
			if prevRev == "" && opts.NextVersion != "" {
				r.ToRevision = opts.NextVersion
			}
			chlog.Sections = append(chlog.Sections, r)
			prevRev = rev
		}

		if opts.Format == "markdown" {
			results := make([]string, len(chlog.Sections))
			for i, v := range chlog.Sections {
				results[i], _ = v.toMkdn()
			}

			if opts.Write {
				content := "# Changelog\n\n" + strings.Join(results, "\n\n")
				err := ioutil.WriteFile(opts.changelogMd, []byte(content), 0644)
				if err != nil {
					log.Print(err)
					return exitCodeErr
				}
			} else {
				fmt.Fprintln(cli.OutStream, strings.Join(results, "\n\n"))
			}
		} else {
			jsn, _ := json.MarshalIndent(chlog, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	} else {
		if opts.Latest {
			vers := gh.versions()
			if len(vers) > 0 {
				opts.To = vers[0]
			}
			if opts.From == "" && len(vers) > 1 {
				opts.From = vers[1]
			}
		} else if opts.From == "" && opts.To == "" {
			opts.From = gh.getLatestSemverTag()
		}
		r, err := gh.getSection(opts.From, opts.To)
		if err != nil {
			log.Print(err)
			return exitCodeErr
		}
		if r.ToRevision == "" && opts.NextVersion != "" {
			r.ToRevision = opts.NextVersion
		}
		if opts.Format == "markdown" {
			str, err := r.toMkdn()
			if err != nil {
				log.Print(err)
				return exitCodeErr
			}
			if opts.Write {
				content := ""
				if exists(opts.changelogMd) {
					byt, err := ioutil.ReadFile(opts.changelogMd)
					if err != nil {
						log.Print(err)
						return exitCodeErr
					}
					content = insertNewChangelog(byt, str)
				} else {
					content = "# Changelog\n\n" + str
				}
				err = ioutil.WriteFile(opts.changelogMd, []byte(content), 0644)
				if err != nil {
					log.Print(err)
					return exitCodeErr
				}
			} else {
				fmt.Fprintln(cli.OutStream, str)
			}
		} else {
			jsn, _ := json.MarshalIndent(r, "", "  ")
			fmt.Fprintln(cli.OutStream, string(jsn))
		}
	}
	return exitCodeOK
}

func insertNewChangelog(orig []byte, section string) string {
	var bf bytes.Buffer
	lineSnr := bufio.NewScanner(bytes.NewReader(orig))
	inserted := false
	for lineSnr.Scan() {
		line := lineSnr.Text()
		if !inserted && strings.HasPrefix(line, "## ") {
			bf.WriteString(section)
			bf.WriteString("\n\n")
			inserted = true
		}
		bf.WriteString(line)
		bf.WriteString("\n")
	}
	if !inserted {
		bf.WriteString(section)
	}
	return bf.String()
}

func exists(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil
}

func parseArgs(args []string) (*flags.Parser, *ghOpts, error) {
	opts := &ghOpts{}
	p := flags.NewParser(opts, flags.Default)
	p.Usage = fmt.Sprintf("[OPTIONS]\n\nVersion: %s (rev: %s)", version, revision)
	rest, err := p.ParseArgs(args)
	if opts.Write {
		opts.Format = "markdown"
		opts.changelogMd = "CHANGELOG.md"
		if len(rest) > 0 {
			opts.changelogMd = rest[0]
		}
	}
	return p, opts, err
}

func (gh *ghch) getSection(from, to string) (Section, error) {
	if from == "" {
		from, _ = gh.cmd("rev-list", "--max-parents=0", "HEAD")
		from = strings.TrimSpace(from)
		if len(from) > 12 {
			from = from[:12]
		}
	}
	r, err := gh.mergedPRs(from, to)
	if err != nil {
		return Section{}, err
	}
	t, err := gh.getChangedAt(to)
	if err != nil {
		return Section{}, err
	}
	owner, repo := gh.ownerAndRepo()
	return Section{
		PullRequests: r,
		FromRevision: from,
		ToRevision:   to,
		ChangedAt:    t,
		Owner:        owner,
		Repo:         repo,
	}, nil
}

// Changelog contains Sectionst
type Changelog struct {
	Sections []Section `json:"Sections"`
}

// Section contains changes between two revisions
type Section struct {
	PullRequests []*octokit.PullRequest `json:"pull_requests"`
	FromRevision string                 `json:"from_revision"`
	ToRevision   string                 `json:"to_revision"`
	ChangedAt    time.Time              `json:"changed_at"`
	Owner        string                 `json:"owner"`
	Repo         string                 `json:"repo"`
}

var tmplStr = `{{$ret := . -}}
## [{{.ToRevision}}](https://github.com/{{.Owner}}/{{.Repo}}/compare/{{.FromRevision}}...{{.ToRevision}}) ({{.ChangedAt.Format "2006-01-02"}})
{{range .PullRequests}}
* {{.Title}} [#{{.Number}}](https://github.com/{{$ret.Owner}}/{{$ret.Repo}}/pull/{{.Number}}) ([{{.User.Login}}](https://github.com/{{.User.Login}}))
{{- end}}`

var mdTmpl *template.Template

func init() {
	var err error
	mdTmpl, err = template.New("md-changelog").Parse(tmplStr)
	if err != nil {
		log.Fatal(err)
	}
}

func (rs Section) toMkdn() (string, error) {
	var b bytes.Buffer
	err := mdTmpl.Execute(&b, rs)
	if err != nil {
		return "", err
	}
	return b.String(), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/blang/semver"
	"github.com/codegangsta/cli"
	"github.com/google/go-github/github"
	"github.com/mitchellh/go-homedir"
	. "github.com/tj/go-debug"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
)

var debug = Debug("main")

func main() {
	app := cli.NewApp()
	app.Name = NAME
	app.Usage = "tool to manage project, user, and global Git hooks"
	app.Version = VERSION
	app.EnableBashCompletion = true
	app.Action = bind(list)
	app.Commands = []cli.Command{
		{
			Name:      "install",
			ShortName: "i",
			Usage:     "Tell repo to use git-hooks by replace existing hooks with a call to git-hooks. Old hooks will be reserved in hooks.old",
			Action:    bind(install, true),
		},
		{
			Name:   "uninstall",
			Usage:  "Stop using git-hooks and restore old hooks",
			Action: bind(uninstall),
		},
		{
			Name:   "install-global",
			Usage:  "Whenever a git repository is created or cloned user will be remind to install git-hooks",
			Action: bind(installGlobal),
		},
		{
			Name:   "uninstall-global",
			Usage:  "Turn off the global reminder",
			Action: bind(uninstallGlobal),
		},
		{
			Name:   "update",
			Usage:  "Check and update git-hooks",
			Action: bind(update),
		},
		{
			Name:  "run",
			Usage: "Run hooks",
			Action: func(c *cli.Context) {
				run(c.Args()...)
			},
		},
		{
			Name:      "identity",
			ShortName: "id",
			Usage:     "Repo identity",
			Action:    bind(identity),
		},
	}

	app.Run(os.Args)
}

// List directory base hooks and configuration file based hooks
func list() {
	root, err := getGitRepoRoot()
	if err != nil {
		logger.Infoln("Current directory is not a git repo")
	} else {
		preCommitHook := filepath.Join(root, ".git/hooks/pre-commit")
		hook, err := ioutil.ReadFile(preCommitHook)
		if err == nil && strings.EqualFold(string(hook), tplPostInstall) {
			logger.Infoln("Git hooks ARE installed in this repository.")
		} else {
			logger.Infoln("Git hooks are NOT installed in this repository. (Run 'git hooks install' to install it)")
		}
	}

	for scope, dir := range hookDirs() {
		logger.Infoln(scope + " hooks")
		config, err := listHooksInDir(scope, dir)
		if err == nil {
			for trigger, hooks := range config {
				logger.Infoln("  " + trigger)
				for _, hook := range hooks {
					logger.Infoln("    - " + hook)
				}
			}
			logger.Infoln()
		}
	}

	logger.Infoln("Community hooks")
	for scope, configPath := range hookConfigs() {
		logger.Infoln(scope + " hooks")
		config, err := listHooksInConfig(configPath)
		if err == nil {
			for trigger, repo := range config {
				logger.Infoln("  " + trigger)
				for repoName, hooks := range repo {
					logger.Infoln("  " + repoName)
					for _, hook := range hooks {
						logger.Infoln("    - " + hook)
					}
				}
			}
		}
	}
}

// Install git-hook into current git repo
func install(isInstall bool) {
	dirPath, err := getGitDirPath()
	if err != nil {
		logger.Errorln("Current directory is not a git repo")
	}

	if isInstall {
		isExist, _ := exists(filepath.Join(dirPath, "hooks.old"))
		if isExist {
			logger.Errorln("@rhooks.old already exists, perhaps you already installed?")
		}
		installInto(dirPath, tplPostInstall)
	} else {
		isExist, _ := exists(filepath.Join(dirPath, "hooks.old"))
		if !isExist {
			logger.Errorln("Error, hooks.old doesn't exists, aborting uninstall to not destroy something")
		}
		os.RemoveAll(filepath.Join(dirPath, "hooks"))
		os.Rename(filepath.Join(dirPath, "hooks.old"), filepath.Join(dirPath, "hooks"))
		logger.Infoln("Restore hooks.old")
	}
}

// Uninstall git-hooks from current git repo
func uninstall() {
	install(false)
}

// Install git-hooks global by setup init.tempdir in ~/.gitconfig
func installGlobal() {
	templatedir := ".git-template-with-git-hooks"
	home, err := homedir.Dir()
	if err == nil {
		templatedir = filepath.Join(home, templatedir)
	}
	isExist, _ := exists(templatedir)
	if !isExist {
		defaultdir := "/usr/share/git-core/templates"
		isExist, _ = exists(defaultdir)
		if isExist {
			os.Link(defaultdir, templatedir)
		} else {
			os.Mkdir(filepath.Join(templatedir, "hooks"), 0755)
		}
		installInto(templatedir, tplPreInstall)
	}
	gitExec("config --global init.templatedir " + templatedir)
	os.Rename(filepath.Join(templatedir, "hooks.old"), filepath.Join(templatedir, "hooks.original"))
	logger.Infoln("Git global config init.templatedir is now set to " + templatedir)
}

// Reset init.tempdir
func uninstallGlobal() {
	gitExec("config --global --unset init.templatedir")
}

// Check latest version of git-hooks by github release
// If there are new version of git-hooks, download and replace the current one
func update() {
	logger.Infoln("Current git-hooks version is " + VERSION)
	logger.Infoln("Check latest version...")

	client := github.NewClient(nil)
	releases, _, _ := client.Repositories.ListReleases(
		"git-hooks", "git-hooks", &github.ListOptions{})
	release := releases[0]
	version := *release.TagName
	logger.Infoln("Latest version is " + version)

	// compare version
	current, err := semver.New(VERSION[1:])
	if err != nil {
		logger.Errorln("Semver parse error ", err)
	}
	latest, err := semver.New(version[1:])
	if err != nil {
		logger.Errorln("Semver parse error ", err)
	}
	debug("Current version %s, latest version %s", current, latest)

	if latest.GT(current) {
		logger.Infoln("Download latest version...")
		target := fmt.Sprintf("git-hooks_%s_%s", runtime.GOOS, runtime.GOARCH)
		for _, asset := range release.Assets {
			if *asset.Name == target {
				// download
				tmpFileName, err := downloadFromUrl(*asset.BrowserDownloadUrl)
				if err != nil {
					logger.Errorln("Download error", err)
				}
				logger.Infoln("Download complete")

				// uncompress
				tmpFileName, err = extract(tmpFileName)
				if err != nil {
					logger.Errorln("Download error", err)
				}
				logger.Infoln("Extract complete")

				// replace current version
				fileName, err := absExePath(os.Args[0])
				if err != nil {
					logger.Errorln(err)
				}

				debug("Replace %s with temp file %s", fileName, tmpFileName)
				out, err := os.Create(fileName)
				if err != nil {
					logger.Errorln("Create error ", err)
				}
				defer out.Close()

				err = out.Chmod(0755)
				if err != nil {
					logger.Errorln("Create error ", err)
				}

				in, err := os.Open(tmpFileName)
				if err != nil {
					logger.Errorln("Open error ", err)
				}
				defer in.Close()

				_, err = io.Copy(out, in)
				if err != nil {
					logger.Errorln("Copy error ", err)
				}
				logger.Infoln(NAME + " update to " + version)

				break
			}
		}
	} else {
		logger.Infoln("Your " + NAME + " is update to date")
	}
}

func identity() {
	identity, err := gitExec("rev-list --max-parents=0 HEAD")
	if err != nil {
		logger.Errorln(err)
	}

	logger.Infoln(identity)
}

// Execute project, semi, user and global scope hooks
func run(cmds ...string) {
	t := filepath.Base(cmds[0])
	args := cmds[1:]
	for scope, dir := range hookDirs() {
		config, err := listHooksInDir(scope, dir)
		if err == nil {
			for trigger, hooks := range config {
				// semi scope
				if trigger == t || trigger == ("_"+t) {
					for _, hook := range hooks {
						status, err := runHook(filepath.Join(dir, trigger, hook), args...)
						if err != nil {
							logger.Errorsln(status, err)
						}
					}
				}
			}
		}
	}

	// find contrib directory
	contrib, err := gitExec("config --get hooks.contrib")
	isExist, _ := exists(contrib)
	if err != nil || !isExist {
		// default to use ~/.githooks-contrib
		home, err := homedir.Dir()
		if err != nil {
			// fallback
			home = "~"
		}
		contrib = filepath.Join(home, "."+CONTRIB_DIRNAME)
	} else {
		contrib = filepath.Join(contrib, CONTRIB_DIRNAME)
	}
	for _, configPath := range hookConfigs() {
		config, err := listHooksInConfig(configPath)
		if err == nil {
			for trigger, repo := range config {
				if trigger == t {
					for repoName, hooks := range repo {
						// check if repo exist in local file system
						isExist, _ := exists(filepath.Join(contrib, repoName))
						if !isExist {
							logger.Infoln("Cloning repo " + repoName)
							_, err := gitExec(fmt.Sprintf("clone https://%s %s", repoName, filepath.Join(contrib, repoName)))
							if err != nil {
								fmt.Printf("clone https://%s %s", repoName, filepath.Join(contrib, repoName))
								fmt.Println(err)
								continue
							}
						}
						// execute hook
						for _, hook := range hooks {
							status, err := runHook(filepath.Join(contrib, repoName, hook), args...)
							if err != nil {
								logger.Errorsln(status, err)
							}
						}
					}
				}
			}
		}
	}
}

// Execute specific hook with arguments
// Return error message as out if error occured
func runHook(hook string, args ...string) (status int, err error) {
	debug("Execute contrib hook %s %s", hook, args)

	cmd := exec.Command(hook, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()

	if err != nil {
		if msg, ok := err.(*exec.ExitError); ok {
			status = msg.Sys().(syscall.WaitStatus).ExitStatus()
		}
	} else {
		status = 0
	}
	return status, err
}

func installInto(dir string, template string) {
	// backup
	os.Rename(filepath.Join(dir, "hooks"), filepath.Join(dir, "hooks.old"))
	os.Mkdir(filepath.Join(dir, "hooks"), 0755)
	for _, hook := range TRIGGERS {
		logger.Infoln("Install ", hook)
		f, _ := os.Create(filepath.Join(dir, "hooks", hook))
		f.WriteString(template)
		f.Sync()
		f.Chmod(0755)
	}
}
<commit_msg>Check version compability before update<commit_after>package main

import (
	"fmt"
	"github.com/blang/semver"
	"github.com/codegangsta/cli"
	"github.com/google/go-github/github"
	"github.com/mitchellh/go-homedir"
	. "github.com/tj/go-debug"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
)

var debug = Debug("main")

func main() {
	app := cli.NewApp()
	app.Name = NAME
	app.Usage = "tool to manage project, user, and global Git hooks"
	app.Version = VERSION
	app.EnableBashCompletion = true
	app.Action = bind(list)
	app.Commands = []cli.Command{
		{
			Name:      "install",
			ShortName: "i",
			Usage:     "Tell repo to use git-hooks by replace existing hooks with a call to git-hooks. Old hooks will be reserved in hooks.old",
			Action:    bind(install, true),
		},
		{
			Name:   "uninstall",
			Usage:  "Stop using git-hooks and restore old hooks",
			Action: bind(uninstall),
		},
		{
			Name:   "install-global",
			Usage:  "Whenever a git repository is created or cloned user will be remind to install git-hooks",
			Action: bind(installGlobal),
		},
		{
			Name:   "uninstall-global",
			Usage:  "Turn off the global reminder",
			Action: bind(uninstallGlobal),
		},
		{
			Name:   "update",
			Usage:  "Check and update git-hooks",
			Action: bind(update),
		},
		{
			Name:  "run",
			Usage: "Run hooks",
			Action: func(c *cli.Context) {
				run(c.Args()...)
			},
		},
		{
			Name:      "identity",
			ShortName: "id",
			Usage:     "Repo identity",
			Action:    bind(identity),
		},
	}

	app.Run(os.Args)
}

// List directory base hooks and configuration file based hooks
func list() {
	root, err := getGitRepoRoot()
	if err != nil {
		logger.Infoln("Current directory is not a git repo")
	} else {
		preCommitHook := filepath.Join(root, ".git/hooks/pre-commit")
		hook, err := ioutil.ReadFile(preCommitHook)
		if err == nil && strings.EqualFold(string(hook), tplPostInstall) {
			logger.Infoln("Git hooks ARE installed in this repository.")
		} else {
			logger.Infoln("Git hooks are NOT installed in this repository. (Run 'git hooks install' to install it)")
		}
	}

	for scope, dir := range hookDirs() {
		logger.Infoln(scope + " hooks")
		config, err := listHooksInDir(scope, dir)
		if err == nil {
			for trigger, hooks := range config {
				logger.Infoln("  " + trigger)
				for _, hook := range hooks {
					logger.Infoln("    - " + hook)
				}
			}
			logger.Infoln()
		}
	}

	logger.Infoln("Community hooks")
	for scope, configPath := range hookConfigs() {
		logger.Infoln(scope + " hooks")
		config, err := listHooksInConfig(configPath)
		if err == nil {
			for trigger, repo := range config {
				logger.Infoln("  " + trigger)
				for repoName, hooks := range repo {
					logger.Infoln("  " + repoName)
					for _, hook := range hooks {
						logger.Infoln("    - " + hook)
					}
				}
			}
		}
	}
}

// Install git-hook into current git repo
func install(isInstall bool) {
	dirPath, err := getGitDirPath()
	if err != nil {
		logger.Errorln("Current directory is not a git repo")
	}

	if isInstall {
		isExist, _ := exists(filepath.Join(dirPath, "hooks.old"))
		if isExist {
			logger.Errorln("@rhooks.old already exists, perhaps you already installed?")
		}
		installInto(dirPath, tplPostInstall)
	} else {
		isExist, _ := exists(filepath.Join(dirPath, "hooks.old"))
		if !isExist {
			logger.Errorln("Error, hooks.old doesn't exists, aborting uninstall to not destroy something")
		}
		os.RemoveAll(filepath.Join(dirPath, "hooks"))
		os.Rename(filepath.Join(dirPath, "hooks.old"), filepath.Join(dirPath, "hooks"))
		logger.Infoln("Restore hooks.old")
	}
}

// Uninstall git-hooks from current git repo
func uninstall() {
	install(false)
}

// Install git-hooks global by setup init.tempdir in ~/.gitconfig
func installGlobal() {
	templatedir := ".git-template-with-git-hooks"
	home, err := homedir.Dir()
	if err == nil {
		templatedir = filepath.Join(home, templatedir)
	}
	isExist, _ := exists(templatedir)
	if !isExist {
		defaultdir := "/usr/share/git-core/templates"
		isExist, _ = exists(defaultdir)
		if isExist {
			os.Link(defaultdir, templatedir)
		} else {
			os.Mkdir(filepath.Join(templatedir, "hooks"), 0755)
		}
		installInto(templatedir, tplPreInstall)
	}
	gitExec("config --global init.templatedir " + templatedir)
	os.Rename(filepath.Join(templatedir, "hooks.old"), filepath.Join(templatedir, "hooks.original"))
	logger.Infoln("Git global config init.templatedir is now set to " + templatedir)
}

// Reset init.tempdir
func uninstallGlobal() {
	gitExec("config --global --unset init.templatedir")
}

// Check latest version of git-hooks by github release
// If there are new version of git-hooks, download and replace the current one
func update() {
	logger.Infoln("Current git-hooks version is " + VERSION)
	logger.Infoln("Check latest version...")

	client := github.NewClient(nil)
	releases, _, _ := client.Repositories.ListReleases(
		"git-hooks", "git-hooks", &github.ListOptions{})
	release := releases[0]
	version := *release.TagName
	logger.Infoln("Latest version is " + version)

	// compare version
	current, err := semver.New(VERSION[1:])
	if err != nil {
		logger.Errorln("Semver parse error ", err)
	}
	latest, err := semver.New(version[1:])
	if err != nil {
		logger.Errorln("Semver parse error ", err)
	}
	debug("Current version %s, latest version %s", current, latest)

	if !latest.GT(current) {
		logger.Infoln("Your " + NAME + " is update to date")
		return
	}

	// version compability
	if latest.Major != current.Major {
		logger.Infoln("Current version is ", current)
		logger.Infoln("Latest version is ", latest)
		logger.Infoln("Version incompatible, manually update please")
		return
	}

	logger.Infoln("Download latest version...")
	target := fmt.Sprintf("git-hooks_%s_%s", runtime.GOOS, runtime.GOARCH)
	for _, asset := range release.Assets {
		if *asset.Name == target {
			// download
			tmpFileName, err := downloadFromUrl(*asset.BrowserDownloadUrl)
			if err != nil {
				logger.Errorln("Download error", err)
			}
			logger.Infoln("Download complete")

			// uncompress
			tmpFileName, err = extract(tmpFileName)
			if err != nil {
				logger.Errorln("Download error", err)
			}
			logger.Infoln("Extract complete")

			// replace current version
			fileName, err := absExePath(os.Args[0])
			if err != nil {
				logger.Errorln(err)
			}

			debug("Replace %s with temp file %s", fileName, tmpFileName)
			out, err := os.Create(fileName)
			if err != nil {
				logger.Errorln("Create error ", err)
			}
			defer out.Close()

			err = out.Chmod(0755)
			if err != nil {
				logger.Errorln("Create error ", err)
			}

			in, err := os.Open(tmpFileName)
			if err != nil {
				logger.Errorln("Open error ", err)
			}
			defer in.Close()

			_, err = io.Copy(out, in)
			if err != nil {
				logger.Errorln("Copy error ", err)
			}
			logger.Infoln(NAME + " update to " + version)

			break
		}
	}
}

func identity() {
	identity, err := gitExec("rev-list --max-parents=0 HEAD")
	if err != nil {
		logger.Errorln(err)
	}

	logger.Infoln(identity)
}

// Execute project, semi, user and global scope hooks
func run(cmds ...string) {
	t := filepath.Base(cmds[0])
	args := cmds[1:]
	for scope, dir := range hookDirs() {
		config, err := listHooksInDir(scope, dir)
		if err == nil {
			for trigger, hooks := range config {
				// semi scope
				if trigger == t || trigger == ("_"+t) {
					for _, hook := range hooks {
						status, err := runHook(filepath.Join(dir, trigger, hook), args...)
						if err != nil {
							logger.Errorsln(status, err)
						}
					}
				}
			}
		}
	}

	// find contrib directory
	contrib, err := gitExec("config --get hooks.contrib")
	isExist, _ := exists(contrib)
	if err != nil || !isExist {
		// default to use ~/.githooks-contrib
		home, err := homedir.Dir()
		if err != nil {
			// fallback
			home = "~"
		}
		contrib = filepath.Join(home, "."+CONTRIB_DIRNAME)
	} else {
		contrib = filepath.Join(contrib, CONTRIB_DIRNAME)
	}
	for _, configPath := range hookConfigs() {
		config, err := listHooksInConfig(configPath)
		if err == nil {
			for trigger, repo := range config {
				if trigger == t {
					for repoName, hooks := range repo {
						// check if repo exist in local file system
						isExist, _ := exists(filepath.Join(contrib, repoName))
						if !isExist {
							logger.Infoln("Cloning repo " + repoName)
							_, err := gitExec(fmt.Sprintf("clone https://%s %s", repoName, filepath.Join(contrib, repoName)))
							if err != nil {
								fmt.Printf("clone https://%s %s", repoName, filepath.Join(contrib, repoName))
								fmt.Println(err)
								continue
							}
						}
						// execute hook
						for _, hook := range hooks {
							status, err := runHook(filepath.Join(contrib, repoName, hook), args...)
							if err != nil {
								logger.Errorsln(status, err)
							}
						}
					}
				}
			}
		}
	}
}

// Execute specific hook with arguments
// Return error message as out if error occured
func runHook(hook string, args ...string) (status int, err error) {
	debug("Execute contrib hook %s %s", hook, args)

	cmd := exec.Command(hook, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()

	if err != nil {
		if msg, ok := err.(*exec.ExitError); ok {
			status = msg.Sys().(syscall.WaitStatus).ExitStatus()
		}
	} else {
		status = 0
	}
	return status, err
}

func installInto(dir string, template string) {
	// backup
	os.Rename(filepath.Join(dir, "hooks"), filepath.Join(dir, "hooks.old"))
	os.Mkdir(filepath.Join(dir, "hooks"), 0755)
	for _, hook := range TRIGGERS {
		logger.Infoln("Install ", hook)
		f, _ := os.Create(filepath.Join(dir, "hooks", hook))
		f.WriteString(template)
		f.Sync()
		f.Chmod(0755)
	}
}
<|endoftext|>


<commit_before>package runner

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
)

var (
	chunkSize = 4096
)

type WrappedCommand struct {
	Name      string
	Cmd       *exec.Cmd
	LogSource *LogSource
	ChunkChan chan LogChunk
}

// A wrapped command will ensure that all stdin/out/err gets piped
// into a buffer that can then be reported upstream to the Changes
// master server
func NewWrappedCommand(cmd *exec.Cmd) (*WrappedCommand, error) {
	return &WrappedCommand{
		Cmd:       cmd,
		ChunkChan: make(chan LogChunk),
	}, nil
}

// Build a new WrappedCommand out of an arbitrary script
// The script is written to disk and then executed ensuring that it can
// be fairly arbitrary and provide its own shebang
func NewWrappedScriptCommand(script string, name string) (*WrappedCommand, error) {
	f, err := ioutil.TempFile("", "script-")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	_, err = f.WriteString(script)
	if err != nil {
		return nil, err
	}

	info, err := f.Stat()
	if err != nil {
		return nil, err
	}

	err = f.Chmod((info.Mode() & os.ModePerm) | 0111)
	if err != nil {
		return nil, err
	}

	wc, err := NewWrappedCommand(exec.Command(f.Name()))
	wc.Name = name
	return wc, err
}

func (wc *WrappedCommand) CombinedOutputPipe() (io.ReadCloser, io.WriteCloser, error) {
	c := wc.Cmd

	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, nil, err
	}

	c.Stdout = pw
	c.Stderr = pw

	return pr, pw, err
}

func (wc *WrappedCommand) GetLabel() string {
	if wc.Name != "" {
		return wc.Name
	} else {
		return strings.Join(wc.Cmd.Args, " ")
	}
}

func (wc *WrappedCommand) Run() (*os.ProcessState, error) {
	defer close(wc.ChunkChan)

	cmdreader, cmdwriter, err := wc.CombinedOutputPipe()
	if err != nil {
		return nil, err
	}

	cmdname := wc.GetLabel()
	log.Printf("[cmd] Executing %s", cmdname)
	processMessage(wc.ChunkChan, fmt.Sprintf(">> %s", cmdname))

	err = wc.Cmd.Start()
	// per the internal exec.Cmd implementation, close the writer
	// immediately after Start()
	cmdwriter.Close()

	if err != nil {
		log.Printf("[cmd] Start failed %s %s", wc.Cmd.Args, err.Error())
		processMessage(wc.ChunkChan, err.Error())
		return nil, err
	}

	// Start chunking from stdin and stdout and close stdin
	wg := sync.WaitGroup{}

	wg.Add(1)
	go func() {
		processChunks(wc.ChunkChan, cmdreader)
		log.Printf("[cmd] Stdout processed %s", wc.Cmd.Args)
		wg.Done()
	}()

	wg.Wait()

	err = wc.Cmd.Wait()

	// per the internal exec.Cmd implementation, close the reader only
	// after Cmd.Wait() (and after we're done reading)
	cmdreader.Close()

	if err != nil {
		return nil, err
	}

	return wc.Cmd.ProcessState, nil
}

func processMessage(out chan LogChunk, payload string) {
	out <- LogChunk{
		Length:  len(payload),
		Payload: []byte(fmt.Sprintf("%s\n", payload)),
	}
}

type IOBuffer struct {
	inputs []io.Reader
	output io.Reader
}

func (i *IOBuffer) AddInput(pipe io.Reader) {
	i.inputs = append(i.inputs, pipe)
}

func (i *IOBuffer) ProcessChunks(out chan LogChunk) {

}

func processChunks(out chan LogChunk, pipe io.Reader) {
	r := bufio.NewReader(pipe)

	finished := false
	for !finished {
		var payload []byte
		for len(payload) < chunkSize {
			line, err := r.ReadBytes('\n')
			payload = append(payload, line...)

			if err == nil {
				continue
			} else if err == io.EOF {
				finished = true
				break
			} else {
				finished = true
				line = []byte(fmt.Sprintf("%s", err))
				payload = append(payload, line...)
				break
			}
		}

		if len(payload) > 0 {
			l := LogChunk{
				Length:  len(payload),
				Payload: payload,
			}

			out <- l
		}
	}
}

func init() {
	flag.IntVar(&chunkSize, "log_chunk_size", 4096, "Size of log chunks to send to http server")
}
<commit_msg>Switch to io.Pipe<commit_after>package runner

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
)

var (
	chunkSize = 4096
)

type WrappedCommand struct {
	Name      string
	Cmd       *exec.Cmd
	LogSource *LogSource
	ChunkChan chan LogChunk
}

// A wrapped command will ensure that all stdin/out/err gets piped
// into a buffer that can then be reported upstream to the Changes
// master server
func NewWrappedCommand(cmd *exec.Cmd) (*WrappedCommand, error) {
	return &WrappedCommand{
		Cmd:       cmd,
		ChunkChan: make(chan LogChunk),
	}, nil
}

// Build a new WrappedCommand out of an arbitrary script
// The script is written to disk and then executed ensuring that it can
// be fairly arbitrary and provide its own shebang
func NewWrappedScriptCommand(script string, name string) (*WrappedCommand, error) {
	f, err := ioutil.TempFile("", "script-")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	_, err = f.WriteString(script)
	if err != nil {
		return nil, err
	}

	info, err := f.Stat()
	if err != nil {
		return nil, err
	}

	err = f.Chmod((info.Mode() & os.ModePerm) | 0111)
	if err != nil {
		return nil, err
	}

	wc, err := NewWrappedCommand(exec.Command(f.Name()))
	wc.Name = name
	return wc, err
}

func (wc *WrappedCommand) CombinedOutputPipe() (io.ReadCloser, io.WriteCloser) {
	c := wc.Cmd

	pr, pw := io.Pipe()

	c.Stdout = pw
	c.Stderr = pw

	return pr, pw
}

func (wc *WrappedCommand) GetLabel() string {
	if wc.Name != "" {
		return wc.Name
	} else {
		return strings.Join(wc.Cmd.Args, " ")
	}
}

func (wc *WrappedCommand) Run() (*os.ProcessState, error) {
	defer close(wc.ChunkChan)

	cmdreader, cmdwriter := wc.CombinedOutputPipe()

	cmdname := wc.GetLabel()
	log.Printf("[cmd] Executing %s", cmdname)
	processMessage(wc.ChunkChan, fmt.Sprintf(">> %s", cmdname))

	err := wc.Cmd.Start()

	if err != nil {
		log.Printf("[cmd] Start failed %s %s", wc.Cmd.Args, err.Error())
		processMessage(wc.ChunkChan, err.Error())
		return nil, err
	}

	// Start chunking from stdin and stdout and close stdin
	wg := sync.WaitGroup{}

	wg.Add(1)
	go func() {
		processChunks(wc.ChunkChan, cmdreader)
		log.Printf("[cmd] Stdout processed %s", wc.Cmd.Args)
		wg.Done()
	}()

	err = wc.Cmd.Wait()

	cmdwriter.Close()

	wg.Wait()

	cmdreader.Close()

	if err != nil {
		return nil, err
	}

	return wc.Cmd.ProcessState, nil
}

func processMessage(out chan LogChunk, payload string) {
	out <- LogChunk{
		Length:  len(payload),
		Payload: []byte(fmt.Sprintf("%s\n", payload)),
	}
}

type IOBuffer struct {
	inputs []io.Reader
	output io.Reader
}

func (i *IOBuffer) AddInput(pipe io.Reader) {
	i.inputs = append(i.inputs, pipe)
}

func (i *IOBuffer) ProcessChunks(out chan LogChunk) {

}

func processChunks(out chan LogChunk, pipe io.Reader) {
	r := bufio.NewReader(pipe)

	finished := false
	for !finished {
		var payload []byte
		for len(payload) < chunkSize {
			line, err := r.ReadBytes('\n')
			payload = append(payload, line...)

			if err == nil {
				continue
			} else if err == io.EOF {
				finished = true
				break
			} else {
				finished = true
				line = []byte(fmt.Sprintf("%s", err))
				payload = append(payload, line...)
				break
			}
		}

		if len(payload) > 0 {
			l := LogChunk{
				Length:  len(payload),
				Payload: payload,
			}

			out <- l
		}
	}
}

func init() {
	flag.IntVar(&chunkSize, "log_chunk_size", 4096, "Size of log chunks to send to http server")
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/axw/gocov"
	"io/ioutil"
	"path/filepath"
)

func ParseCov(cov []byte, wd string) []*File {
	var input struct {
		Packages []*gocov.Package
	}
	err := json.Unmarshal(cov, &input)
	if err != nil {
		panic(err)
	}

	files := make(map[string]*File)
	for _, pkg := range input.Packages {
		for _, fun := range pkg.Functions {
			rel, err := filepath.Rel(wd, fun.File)
			if err != nil {
				panic(err)
			}
			file, ok := files[rel]
			if !ok {
				contents, err := ioutil.ReadFile(fun.File)
				if err != nil {
					panic(err)
				}

				file = &File{
					Name:   rel,
					Source: string(contents),
				}
				for offset, b := range contents {
					if b == '\n' {
						file.offsets = append(file.offsets, offset)
					}
				}
				file.offsets = append(file.offsets, len(contents))
				file.Coverage = make([]*int64, len(file.offsets))

				files[rel] = file
			}

			for _, stmt := range fun.Statements {
				for line, offset := range file.offsets {
					if stmt.Start < offset {
						if file.Coverage[line] == nil {
							file.Coverage[line] = new(int64)
						}
						*file.Coverage[line] += stmt.Reached
						break
					}
				}
			}
		}
	}
	fileSlice := make([]*File, 0, len(files))
	for _, f := range files {
		fileSlice = append(fileSlice, f)
	}
	return fileSlice
}
<commit_msg>use relative path to allow test cases to run on systems other than my own<commit_after>package main

import (
	"encoding/json"
	"github.com/axw/gocov"
	"io/ioutil"
	"path/filepath"
)

func ParseCov(cov []byte, wd string) []*File {
	var input struct {
		Packages []*gocov.Package
	}
	err := json.Unmarshal(cov, &input)
	if err != nil {
		panic(err)
	}

	files := make(map[string]*File)
	for _, pkg := range input.Packages {
		for _, fun := range pkg.Functions {
			rel, err := filepath.Rel(wd, fun.File)
			if err != nil {
				panic(err)
			}
			file, ok := files[rel]
			if !ok {
				contents, err := ioutil.ReadFile(rel)
				if err != nil {
					panic(err)
				}

				file = &File{
					Name:   rel,
					Source: string(contents),
				}
				for offset, b := range contents {
					if b == '\n' {
						file.offsets = append(file.offsets, offset)
					}
				}
				file.offsets = append(file.offsets, len(contents))
				file.Coverage = make([]*int64, len(file.offsets))

				files[rel] = file
			}

			for _, stmt := range fun.Statements {
				for line, offset := range file.offsets {
					if stmt.Start < offset {
						if file.Coverage[line] == nil {
							file.Coverage[line] = new(int64)
						}
						*file.Coverage[line] += stmt.Reached
						break
					}
				}
			}
		}
	}
	fileSlice := make([]*File, 0, len(files))
	for _, f := range files {
		fileSlice = append(fileSlice, f)
	}
	return fileSlice
}
<|endoftext|>


<commit_before>package zkteco

import (
	"encoding/csv"
	"os"
)

func GetCSVRecords(file string) (records [][]string, err error) {
	var f *os.File

	f, err = os.Open(file)
	if err != nil {
		debugPrintf("os.Open() err: %v\n", err)
		return [][]string{}, err
	}
	defer f.Close()

	r := csv.NewReader(f)
	records, err = r.ReadAll()
	if err != nil {
		debugPrintf("GetCSVRecords error: %v\n", err)
		return [][]string{}, err
	}
	return records, nil
}
<commit_msg>Rename file Param from file to name.<commit_after>package zkteco

import (
	"encoding/csv"
	"os"
)

func GetCSVRecords(name string) (records [][]string, err error) {
	var f *os.File

	f, err = os.Open(name)
	if err != nil {
		debugPrintf("os.Open() err: %v\n", err)
		return [][]string{}, err
	}
	defer f.Close()

	r := csv.NewReader(f)
	records, err = r.ReadAll()
	if err != nil {
		debugPrintf("GetCSVRecords error: %v\n", err)
		return [][]string{}, err
	}
	return records, nil
}
<|endoftext|>


<commit_before>package peco

import (
	"io"
	"sync"
	"time"
)

func (c *Ctx) CaretPos() int {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	return c.caretPosition
}

func (c *Ctx) SetCaretPos(where int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.caretPosition = where
}

func (c *Ctx) MoveCaretPos(offset int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.caretPosition = c.caretPosition + offset
}

func (q FilterQuery) Query() []rune {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	return q.query[:]
}

func (q FilterQuery) SavedQuery() []rune {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	return q.savedQuery[:]
}

func (q FilterQuery) QueryString() string {
	qbytes := q.Query()
	return string(qbytes)
}

func (q FilterQuery) QueryLen() int {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	return len(q.query)
}

func (q *FilterQuery) AppendQuery(r rune) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	q.query = append(q.query, r)
}

func (q *FilterQuery) InsertQueryAt(ch rune, where int) {
	q.mutex.Lock()
	defer q.mutex.Unlock()

	sq := q.query
	buf := make([]rune, len(sq)+1)
	copy(buf, sq[:where])
	buf[where] = ch
	copy(buf[where+1:], sq[where:])
	q.query = buf
}

func NewCtx(o CtxOptions) *Ctx {
	return newCtx(o, 5)
}

func newCtx(o CtxOptions, hubBufferSize int) *Ctx {
	c := &Ctx{
		Hub:                 NewHub(hubBufferSize),
		FilterQuery:         &FilterQuery{[]rune{}, []rune{}, newMutex()},
		filters:             FilterSet{},
		caretPosition:       0,
		resultCh:            nil,
		mutex:               newMutex(),
		currentCol:          0,
		currentPage:         &PageInfo{},
		selection:           NewSelection(),
		activeLineBuffer:    nil,
		rawLineBuffer:       NewRawLineBuffer(),
		lines:               []Line{},
		linesMutex:          newMutex(),
		current:             nil,
		currentMutex:        newMutex(),
		config:              NewConfig(),
		selectionRangeStart: invalidSelectionRange,
		wait:                &sync.WaitGroup{},
		layoutType:          "top-down",
		singleKeyJumpMode:   false,
	}

	if o != nil {
		// XXX Pray this is really nil :)
		c.enableSep = o.EnableNullSep()
		c.currentLine = o.InitialIndex()

		c.rawLineBuffer.SetCapacity(o.BufferSize())

		if v := o.LayoutType(); v != "" {
			c.layoutType = v
		}
	}

	c.filters.Add(NewIgnoreCaseFilter())
	c.filters.Add(NewCaseSensitiveFilter())
	c.filters.Add(NewSmartCaseFilter())
	c.filters.Add(NewRegexpFilter())

	return c
}

const invalidSelectionRange = -1

func (c *Ctx) ReadConfig(file string) error {
	if err := c.config.ReadFilename(file); err != nil {
		return err
	}

	if err := c.LoadCustomFilter(); err != nil {
		return err
	}

	c.SetCurrentFilterByName(c.config.InitialFilter)

	if c.layoutType == "" { // Not set yet
		if c.config.Layout != "" {
			c.layoutType = c.config.Layout
		}
	}

	c.config.SingleKeyJumpMap = make(map[rune]uint)
	chrs := "asdfghjklzxcvbnmqwertyuiop"
	for i := 0; i < len(chrs); i++ {
		c.config.SingleKeyJumpMap[rune(chrs[i])] = uint(i)
	}
	c.config.SingleKeyJumpList = make([]rune, len(c.config.SingleKeyJumpMap))
	for k, v := range c.config.SingleKeyJumpMap {
		c.config.SingleKeyJumpList[v] = k
	}

	return nil
}

func (c *Ctx) IsRangeMode() bool {
	return c.selectionRangeStart != invalidSelectionRange
}

func (c *Ctx) SelectionLen() int {
	return c.selection.Len()
}

func (c *Ctx) SelectionAdd(x int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if l, err := c.GetCurrentLineBuffer().LineAt(x); err == nil {
		c.selection.Add(l)
	}
}

func (c *Ctx) SelectionRemove(x int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if l, err := c.GetCurrentLineBuffer().LineAt(x); err == nil {
		c.selection.Delete(l)
	}
}

func (c *Ctx) SelectionClear() {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.selection = NewSelection()
}

func (c *Ctx) SelectionContains(n int) bool {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if l, err := c.GetCurrentLineBuffer().LineAt(n); err == nil {
		return c.selection.Has(l)
	}
	return false
}

func (c *Ctx) ResultCh() <-chan Line {
	return c.resultCh
}

func (c *Ctx) AddWaitGroup(v int) {
	c.wait.Add(v)
}

func (c *Ctx) ReleaseWaitGroup() {
	c.wait.Done()
}

func (c *Ctx) WaitDone() {
	c.wait.Wait()
}

var execQueryLock = newMutex()
var execQueryTimer *time.Timer

func (c *Ctx) ExecQuery() bool {
	trace("Ctx.ExecQuery: START")
	defer trace("Ctx.ExecQuery: END")

	if c.QueryLen() <= 0 {
		if c.activeLineBuffer != nil {
			c.ResetActiveLineBuffer()
			return true
		}
		return false
	}

	delay := c.config.QueryExecutionDelay

	if delay <= 0 {
		c.SendQuery(c.QueryString())
		return true
	}

	go func() {
		// Wait $delay millisecs before sending the query
		// if a new input comes in, batch them up
		execQueryLock.Lock()
		defer execQueryLock.Unlock()
		if execQueryTimer != nil {
			return
		}
		execQueryTimer = time.AfterFunc(time.Duration(delay)*time.Millisecond, func() {
			trace("Ctx.ExecQuery: Sending Query!")
			c.SendQuery(c.QueryString())

			execQueryLock.Lock()
			defer execQueryLock.Unlock()
			execQueryTimer = nil
		})
	}()
	return true
}

func (c *Ctx) DrawPrompt() {
	c.SendDrawPrompt()
}

func (c *Ctx) NewBufferReader(r io.ReadCloser) *BufferReader {
	return &BufferReader{c, r, make(chan struct{}, 1)}
}

func (c *Ctx) NewView() *View {
	var layout Layout
	switch c.layoutType {
	case "bottom-up":
		layout = NewBottomUpLayout(c)
	default:
		layout = NewDefaultLayout(c)
	}
	return &View{c, newMutex(), layout}
}

func (c *Ctx) NewFilter() *Filter {
	return &Filter{c}
}

func (c *Ctx) NewInput() *Input {
	// Create a new keymap object
	k := NewKeymap(c.config.Keymap, c.config.Action)
	k.ApplyKeybinding()
	return &Input{c, newMutex(), nil, k, []string{}}
}

func (c *Ctx) SetSavedQuery(q []rune) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.FilterQuery.savedQuery = q
}

func (c *Ctx) SetQuery(q []rune) {
	trace("Ctx.SetQuery: START")
	defer trace("Ctx.SetQuery: END")
	c.mutex.Lock()
	trace("Ctx.SetQuery: setting query to '%s'", string(q))
	c.FilterQuery.query = q
	c.mutex.Unlock()
	c.SetCaretPos(c.QueryLen())
}

func (c *Ctx) Filter() QueryFilterer {
	return c.filters.GetCurrent()
}

func (c *Ctx) LoadCustomFilter() error {
	if len(c.config.CustomFilter) == 0 {
		return nil
	}

	for name, cfg := range c.config.CustomFilter {
		f := NewExternalCmdFilter(name, cfg.Cmd, cfg.Args, cfg.BufferThreshold, c.enableSep)
		if err := c.filters.Add(f); err != nil {
			return err
		}
	}
	return nil
}

func (c *Ctx) Error() error {
	return c.err
}

func (c *Ctx) ExitWith(err error) {
	c.err = err
	c.Stop()
}

func (c *Ctx) SetPrompt(p string) {
	c.config.Prompt = p
}

func (c *Ctx) AddRawLine(l *RawLine) {
	c.rawLineBuffer.AppendLine(l)
}

func (c Ctx) GetRawLineBufferSize() int {
	return c.rawLineBuffer.Size()
}

func (c *Ctx) ResetActiveLineBuffer() {
	c.rawLineBuffer.Replay()
	c.SetActiveLineBuffer(c.rawLineBuffer, false)
}

func (c *Ctx) SetActiveLineBuffer(l *RawLineBuffer, runningQuery bool) {
	c.activeLineBuffer = l

	go func(l *RawLineBuffer) {
		prev := time.Time{}
		for _ = range l.OutputCh() {
			if time.Since(prev) > time.Millisecond {
				c.SendDraw(runningQuery)
				prev = time.Now()
			}
		}
		c.SendDraw(runningQuery)
	}(l)
}

func (c Ctx) GetCurrentLineBuffer() LineBuffer {
	if b := c.activeLineBuffer; b != nil {
		return b
	}
	return c.rawLineBuffer
}

func (c *Ctx) RotateFilter() {
	c.filters.Rotate()
}

func (c *Ctx) ResetSelectedFilter() {
	c.filters.Reset()
}

func (c *Ctx) SetCurrentFilterByName(name string) error {
	return c.filters.SetCurrentByName(name)
}

func (c *Ctx) startInput() {
	c.AddWaitGroup(1)
	go c.NewInput().Loop()
}

func (c *Ctx) ToggleSingleKeyJumpMode() {
	c.singleKeyJumpMode = !c.singleKeyJumpMode
	c.SendPurgeDisplayCache()
	c.SendDraw(false)
}

func (c *Ctx) IsSingleKeyJumpMode() bool {
	return c.singleKeyJumpMode
}
<commit_msg>Populate things at the right locations<commit_after>package peco

import (
	"io"
	"sync"
	"time"
)

func (c *Ctx) CaretPos() int {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	return c.caretPosition
}

func (c *Ctx) SetCaretPos(where int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.caretPosition = where
}

func (c *Ctx) MoveCaretPos(offset int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.caretPosition = c.caretPosition + offset
}

func (q FilterQuery) Query() []rune {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	return q.query[:]
}

func (q FilterQuery) SavedQuery() []rune {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	return q.savedQuery[:]
}

func (q FilterQuery) QueryString() string {
	qbytes := q.Query()
	return string(qbytes)
}

func (q FilterQuery) QueryLen() int {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	return len(q.query)
}

func (q *FilterQuery) AppendQuery(r rune) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	q.query = append(q.query, r)
}

func (q *FilterQuery) InsertQueryAt(ch rune, where int) {
	q.mutex.Lock()
	defer q.mutex.Unlock()

	sq := q.query
	buf := make([]rune, len(sq)+1)
	copy(buf, sq[:where])
	buf[where] = ch
	copy(buf[where+1:], sq[where:])
	q.query = buf
}

func NewCtx(o CtxOptions) *Ctx {
	return newCtx(o, 5)
}

func newCtx(o CtxOptions, hubBufferSize int) *Ctx {
	c := &Ctx{
		Hub:                 NewHub(hubBufferSize),
		FilterQuery:         &FilterQuery{[]rune{}, []rune{}, newMutex()},
		filters:             FilterSet{},
		caretPosition:       0,
		resultCh:            nil,
		mutex:               newMutex(),
		currentCol:          0,
		currentPage:         &PageInfo{},
		selection:           NewSelection(),
		activeLineBuffer:    nil,
		rawLineBuffer:       NewRawLineBuffer(),
		lines:               []Line{},
		linesMutex:          newMutex(),
		current:             nil,
		currentMutex:        newMutex(),
		config:              NewConfig(),
		selectionRangeStart: invalidSelectionRange,
		wait:                &sync.WaitGroup{},
		layoutType:          "top-down",
		singleKeyJumpMode:   false,
	}

	if o != nil {
		// XXX Pray this is really nil :)
		c.enableSep = o.EnableNullSep()
		c.currentLine = o.InitialIndex()

		c.rawLineBuffer.SetCapacity(o.BufferSize())

		if v := o.LayoutType(); v != "" {
			c.layoutType = v
		}
	}

	c.filters.Add(NewIgnoreCaseFilter())
	c.filters.Add(NewCaseSensitiveFilter())
	c.filters.Add(NewSmartCaseFilter())
	c.filters.Add(NewRegexpFilter())

	jumpMap := make(map[rune]uint)
	chrs := "asdfghjklzxcvbnmqwertyuiop"
	for i := 0; i < len(chrs); i++ {
		jumpMap[rune(chrs[i])] = uint(i)
	}
	c.config.SingleKeyJumpMap = jumpMap
	c.populateSingleKeyJumpList()

	return c
}

func (c *Ctx) populateSingleKeyJumpList() {
	c.config.SingleKeyJumpList = make([]rune, len(c.config.SingleKeyJumpMap))
	for k, v := range c.config.SingleKeyJumpMap {
		c.config.SingleKeyJumpList[v] = k
	}
}

const invalidSelectionRange = -1

func (c *Ctx) ReadConfig(file string) error {
	if err := c.config.ReadFilename(file); err != nil {
		return err
	}

	if err := c.LoadCustomFilter(); err != nil {
		return err
	}

	c.SetCurrentFilterByName(c.config.InitialFilter)

	if c.layoutType == "" { // Not set yet
		if c.config.Layout != "" {
			c.layoutType = c.config.Layout
		}
	}

	c.populateSingleKeyJumpList()

	return nil
}

func (c *Ctx) IsRangeMode() bool {
	return c.selectionRangeStart != invalidSelectionRange
}

func (c *Ctx) SelectionLen() int {
	return c.selection.Len()
}

func (c *Ctx) SelectionAdd(x int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if l, err := c.GetCurrentLineBuffer().LineAt(x); err == nil {
		c.selection.Add(l)
	}
}

func (c *Ctx) SelectionRemove(x int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if l, err := c.GetCurrentLineBuffer().LineAt(x); err == nil {
		c.selection.Delete(l)
	}
}

func (c *Ctx) SelectionClear() {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.selection = NewSelection()
}

func (c *Ctx) SelectionContains(n int) bool {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if l, err := c.GetCurrentLineBuffer().LineAt(n); err == nil {
		return c.selection.Has(l)
	}
	return false
}

func (c *Ctx) ResultCh() <-chan Line {
	return c.resultCh
}

func (c *Ctx) AddWaitGroup(v int) {
	c.wait.Add(v)
}

func (c *Ctx) ReleaseWaitGroup() {
	c.wait.Done()
}

func (c *Ctx) WaitDone() {
	c.wait.Wait()
}

var execQueryLock = newMutex()
var execQueryTimer *time.Timer

func (c *Ctx) ExecQuery() bool {
	trace("Ctx.ExecQuery: START")
	defer trace("Ctx.ExecQuery: END")

	if c.QueryLen() <= 0 {
		if c.activeLineBuffer != nil {
			c.ResetActiveLineBuffer()
			return true
		}
		return false
	}

	delay := c.config.QueryExecutionDelay

	if delay <= 0 {
		c.SendQuery(c.QueryString())
		return true
	}

	go func() {
		// Wait $delay millisecs before sending the query
		// if a new input comes in, batch them up
		execQueryLock.Lock()
		defer execQueryLock.Unlock()
		if execQueryTimer != nil {
			return
		}
		execQueryTimer = time.AfterFunc(time.Duration(delay)*time.Millisecond, func() {
			trace("Ctx.ExecQuery: Sending Query!")
			c.SendQuery(c.QueryString())

			execQueryLock.Lock()
			defer execQueryLock.Unlock()
			execQueryTimer = nil
		})
	}()
	return true
}

func (c *Ctx) DrawPrompt() {
	c.SendDrawPrompt()
}

func (c *Ctx) NewBufferReader(r io.ReadCloser) *BufferReader {
	return &BufferReader{c, r, make(chan struct{}, 1)}
}

func (c *Ctx) NewView() *View {
	var layout Layout
	switch c.layoutType {
	case "bottom-up":
		layout = NewBottomUpLayout(c)
	default:
		layout = NewDefaultLayout(c)
	}
	return &View{c, newMutex(), layout}
}

func (c *Ctx) NewFilter() *Filter {
	return &Filter{c}
}

func (c *Ctx) NewInput() *Input {
	// Create a new keymap object
	k := NewKeymap(c.config.Keymap, c.config.Action)
	k.ApplyKeybinding()
	return &Input{c, newMutex(), nil, k, []string{}}
}

func (c *Ctx) SetSavedQuery(q []rune) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.FilterQuery.savedQuery = q
}

func (c *Ctx) SetQuery(q []rune) {
	trace("Ctx.SetQuery: START")
	defer trace("Ctx.SetQuery: END")
	c.mutex.Lock()
	trace("Ctx.SetQuery: setting query to '%s'", string(q))
	c.FilterQuery.query = q
	c.mutex.Unlock()
	c.SetCaretPos(c.QueryLen())
}

func (c *Ctx) Filter() QueryFilterer {
	return c.filters.GetCurrent()
}

func (c *Ctx) LoadCustomFilter() error {
	if len(c.config.CustomFilter) == 0 {
		return nil
	}

	for name, cfg := range c.config.CustomFilter {
		f := NewExternalCmdFilter(name, cfg.Cmd, cfg.Args, cfg.BufferThreshold, c.enableSep)
		if err := c.filters.Add(f); err != nil {
			return err
		}
	}
	return nil
}

func (c *Ctx) Error() error {
	return c.err
}

func (c *Ctx) ExitWith(err error) {
	c.err = err
	c.Stop()
}

func (c *Ctx) SetPrompt(p string) {
	c.config.Prompt = p
}

func (c *Ctx) AddRawLine(l *RawLine) {
	c.rawLineBuffer.AppendLine(l)
}

func (c Ctx) GetRawLineBufferSize() int {
	return c.rawLineBuffer.Size()
}

func (c *Ctx) ResetActiveLineBuffer() {
	c.rawLineBuffer.Replay()
	c.SetActiveLineBuffer(c.rawLineBuffer, false)
}

func (c *Ctx) SetActiveLineBuffer(l *RawLineBuffer, runningQuery bool) {
	c.activeLineBuffer = l

	go func(l *RawLineBuffer) {
		prev := time.Time{}
		for _ = range l.OutputCh() {
			if time.Since(prev) > time.Millisecond {
				c.SendDraw(runningQuery)
				prev = time.Now()
			}
		}
		c.SendDraw(runningQuery)
	}(l)
}

func (c Ctx) GetCurrentLineBuffer() LineBuffer {
	if b := c.activeLineBuffer; b != nil {
		return b
	}
	return c.rawLineBuffer
}

func (c *Ctx) RotateFilter() {
	c.filters.Rotate()
}

func (c *Ctx) ResetSelectedFilter() {
	c.filters.Reset()
}

func (c *Ctx) SetCurrentFilterByName(name string) error {
	return c.filters.SetCurrentByName(name)
}

func (c *Ctx) startInput() {
	c.AddWaitGroup(1)
	go c.NewInput().Loop()
}

func (c *Ctx) ToggleSingleKeyJumpMode() {
	c.singleKeyJumpMode = !c.singleKeyJumpMode
	c.SendPurgeDisplayCache()
	c.SendDraw(false)
}

func (c *Ctx) IsSingleKeyJumpMode() bool {
	return c.singleKeyJumpMode
}
<|endoftext|>