<commit_before>package vc

import (
	"bytes"
	"crypto/rand"
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"strings"

	"github.com/hashicorp/vault/api"
	"github.com/mitchellh/cli"
)

// TemplateCommand renders (multiple) secret(s) into a templated file.
type TemplateCommand struct {
	baseCommand
	fs     *flag.FlagSet
	mod    string
	lookup map[string]map[string]string
	decode map[string]string
}

func (cmd *TemplateCommand) Help() string {
	return "Usage: vc template [<options>] <file>\n\nOptions:\n" + defaults(cmd.fs)
}

func (cmd *TemplateCommand) Synopsis() string {
	return "render a template"
}

func (cmd *TemplateCommand) Run(args []string) int {
	if err := cmd.fs.Parse(args); err != nil {
		return 1
	}
	if args = cmd.fs.Args(); len(args) != 1 {
		return cli.RunResultHelp
	}

	if mode, err := strconv.ParseInt(cmd.mod, 8, 32); err != nil {
		cmd.ui.Error("error: invalid mode: " + err.Error())
		return 1
	} else {
		cmd.mode = os.FileMode(mode)
	}

	t, err := cmd.parseTemplate(args[0])
	if err != nil {
		cmd.ui.Error("error: " + err.Error())
		return 1
	}

	s, err := cmd.executeTemplate(t)
	if err != nil {
		cmd.ui.Error("error: " + err.Error())
		return 1
	}

	if _, err = cmd.Write([]byte(s)); err != nil {
		cmd.ui.Error("error: " + err.Error())
		return 1
	}

	return 0
}

func (cmd *TemplateCommand) parseTemplate(name string) (*template.Template, error) {
	b, err := ioutil.ReadFile(name)
	if err != nil {
		return nil, err
	}

	// Parse template, add a function "secret"
	return template.New(name).Funcs(template.FuncMap{
		"decode": cmd.templateDecode,
		"secret": cmd.templateSecret,
		"nested": cmd.templateNested,
	}).Parse(string(b))
}

func (cmd *TemplateCommand) executeTemplate(t *template.Template) (content string, err error) {
	// Prepare lookup tables
	cmd.lookup = make(map[string]map[string]string)
	cmd.decode = make(map[string]string)

	// Execute template: first run; here we make an inventory of what secrets are
	// required. The secret lookups will be replaced by placeholders in the
	// templateSecret function.
	w := new(bytes.Buffer)
	if err = t.Execute(w, struct{}{}); err != nil {
		return
	}
	content = w.String()

	// Time to branch out to Vault
	var client *Client
	if client, err = cmd.Client(); err != nil {
		return
	}

	if content, err = cmd.executeTemplateDecodes(client, content); err != nil {
		return
	}
	if content, err = cmd.executeTemplateSecrets(client, content); err != nil {
		return
	}
	if content, err = cmd.executeTemplateNested(client, content); err != nil {
		return
	}

	return
}

func (cmd *TemplateCommand) executeTemplateDecodes(client *Client, input string) (content string, err error) {
	content = input

	for path, k := range cmd.decode {
		var secret *api.Secret
		if secret, err = client.Logical().Read(path); err != nil {
			return
		}
		if secret == nil || secret.Data == nil {
			return "", fmt.Errorf("decode %s: not found", path)
		}

		encoderType, ok := secret.Data[CodecTypeKey].(string)
		if !ok {
			return "", fmt.Errorf("decode %s: key %s not found", path, CodecTypeKey)
		}
		delete(secret.Data, CodecTypeKey)

		c, err := CodecFor(encoderType)
		if err != nil {
			return "", err
		}

		var b []byte
		if b, err = c.Marshal(path, secret.Data); err != nil {
			return "", err
		}

		content = strings.Replace(content, k, string(b), -1)
	}

	return
}

func (cmd *TemplateCommand) executeTemplateSecrets(client *Client, input string) (content string, err error) {
	content = input

	// For each of the secret paths, lookup the secret
	for path, kv := range cmd.lookup {
		var secret *api.Secret
		if secret, err = client.Logical().Read(strings.TrimLeft(path, "/")); err != nil {
			return
		}
		if secret == nil {
			return "", fmt.Errorf("secret %s: not found", path)
		}

		// For each of the secret keys, lookup the value
		for k, placeholder := range kv {
			if strings.HasPrefix(placeholder, "_VAULT_STRING_") {
				if v, ok := secret.Data[k].(string); ok {
					content = strings.Replace(content, placeholder, v, -1)
				} else {
					return "", fmt.Errorf("secret %s: key %q not found", path, k)
				}
			}
		}
	}

	return
}

func (cmd *TemplateCommand) executeTemplateNested(client *Client, input string) (content string, err error) {
	content = input

	// For each of the secret paths, lookup the secret
	for path, kv := range cmd.lookup {
		var secret *api.Secret
		if secret, err = client.Logical().Read(strings.TrimLeft(path, "/")); err != nil {
			return
		}
		if secret == nil {
			return "", fmt.Errorf("nested %s: not found", path)
		}

		// For each of the secret keys, lookup the value
		for k, placeholder := range kv {
			if strings.HasPrefix(placeholder, "_VAULT_NESTED_") {
				keys := strings.Split(k, ".")

				var nestedData map[string]interface{}
				if err := json.Unmarshal([]byte(secret.Data[keys[0]].(string)), &nestedData); err != nil {
					return "", fmt.Errorf("nested %s/%s: failed to parse JSON", path, keys[0])
				}

				mydata := nestedData
				levels := len(keys) - 1
				for _, nestedkey := range keys[1:] {
					if levels > 1 {
						if mydata[nestedkey] == nil {
							return "", fmt.Errorf("nested %s: key %q not found", path, nestedkey)
						}
						mydata = mydata[nestedkey].(map[string]interface{})
					} else {
						if reflect.TypeOf(mydata[nestedkey]).Kind() == reflect.String {
							content = strings.Replace(content, placeholder, mydata[nestedkey].(string), -1)
						} else {
							return "", fmt.Errorf("nested %s: key %q is not a string", path, nestedkey)
						}
					}
					levels--
				}
			}

		}
	}

	return
}

func (cmd *TemplateCommand) templateDecode(path string) string {
	if _, ok := cmd.decode[path]; !ok {
		cmd.decode[path] = cmd.randomIdentifier("decode")
	}
	return cmd.decode[path]
}

func (cmd *TemplateCommand) templateSecret(path string, key string) string {
	kv, ok := cmd.lookup[path]
	if !ok {
		cmd.lookup[path] = make(map[string]string)
		kv = cmd.lookup[path]
	}

	if _, ok = kv[key]; !ok {
		kv[key] = cmd.randomIdentifier("string")
	}

	return kv[key]
}

func (cmd *TemplateCommand) templateNested(path string, key string) string {
	// keys := strings.Split(key, ".")

	kv, ok := cmd.lookup[path]
	if !ok {
		cmd.lookup[path] = make(map[string]string)
		kv = cmd.lookup[path]
	}

	if _, ok = kv[key]; !ok {
		kv[key] = cmd.randomIdentifier("nested")
	}

	return kv[key]
}

func (cmd *TemplateCommand) randomIdentifier(t string) string {
	r := make([]byte, 8)
	io.ReadFull(rand.Reader, r)
	return fmt.Sprintf("_VAULT_%s_%x_", strings.ToUpper(t), r)
}

func TemplateCommandFactory(ui cli.Ui) cli.CommandFactory {
	return func() (cli.Command, error) {
		cmd := &TemplateCommand{
			baseCommand: baseCommand{
				ui: ui,
			},
		}

		cmd.fs = flag.NewFlagSet("template", flag.ContinueOnError)
		cmd.fs.StringVar(&cmd.mod, "m", "0600", "output mode")
		cmd.fs.StringVar(&cmd.out, "o", "", "output (default: stdout)")
		cmd.fs.Usage = func() {
			fmt.Print(cmd.Help())
		}

		return cmd, nil
	}
}
<commit_msg>Avoid  based on comment by @luisyonaldo<commit_after>package vc

import (
	"bytes"
	"crypto/rand"
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"

	"github.com/hashicorp/vault/api"
	"github.com/mitchellh/cli"
)

// TemplateCommand renders (multiple) secret(s) into a templated file.
type TemplateCommand struct {
	baseCommand
	fs     *flag.FlagSet
	mod    string
	lookup map[string]map[string]string
	decode map[string]string
}

func (cmd *TemplateCommand) Help() string {
	return "Usage: vc template [<options>] <file>\n\nOptions:\n" + defaults(cmd.fs)
}

func (cmd *TemplateCommand) Synopsis() string {
	return "render a template"
}

func (cmd *TemplateCommand) Run(args []string) int {
	if err := cmd.fs.Parse(args); err != nil {
		return 1
	}
	if args = cmd.fs.Args(); len(args) != 1 {
		return cli.RunResultHelp
	}

	if mode, err := strconv.ParseInt(cmd.mod, 8, 32); err != nil {
		cmd.ui.Error("error: invalid mode: " + err.Error())
		return 1
	} else {
		cmd.mode = os.FileMode(mode)
	}

	t, err := cmd.parseTemplate(args[0])
	if err != nil {
		cmd.ui.Error("error: " + err.Error())
		return 1
	}

	s, err := cmd.executeTemplate(t)
	if err != nil {
		cmd.ui.Error("error: " + err.Error())
		return 1
	}

	if _, err = cmd.Write([]byte(s)); err != nil {
		cmd.ui.Error("error: " + err.Error())
		return 1
	}

	return 0
}

func (cmd *TemplateCommand) parseTemplate(name string) (*template.Template, error) {
	b, err := ioutil.ReadFile(name)
	if err != nil {
		return nil, err
	}

	// Parse template, add a function "secret"
	return template.New(name).Funcs(template.FuncMap{
		"decode": cmd.templateDecode,
		"secret": cmd.templateSecret,
		"nested": cmd.templateNested,
	}).Parse(string(b))
}

func (cmd *TemplateCommand) executeTemplate(t *template.Template) (content string, err error) {
	// Prepare lookup tables
	cmd.lookup = make(map[string]map[string]string)
	cmd.decode = make(map[string]string)

	// Execute template: first run; here we make an inventory of what secrets are
	// required. The secret lookups will be replaced by placeholders in the
	// templateSecret function.
	w := new(bytes.Buffer)
	if err = t.Execute(w, struct{}{}); err != nil {
		return
	}
	content = w.String()

	// Time to branch out to Vault
	var client *Client
	if client, err = cmd.Client(); err != nil {
		return
	}

	if content, err = cmd.executeTemplateDecodes(client, content); err != nil {
		return
	}
	if content, err = cmd.executeTemplateSecrets(client, content); err != nil {
		return
	}
	if content, err = cmd.executeTemplateNested(client, content); err != nil {
		return
	}

	return
}

func (cmd *TemplateCommand) executeTemplateDecodes(client *Client, input string) (content string, err error) {
	content = input

	for path, k := range cmd.decode {
		var secret *api.Secret
		if secret, err = client.Logical().Read(path); err != nil {
			return
		}
		if secret == nil || secret.Data == nil {
			return "", fmt.Errorf("decode %s: not found", path)
		}

		encoderType, ok := secret.Data[CodecTypeKey].(string)
		if !ok {
			return "", fmt.Errorf("decode %s: key %s not found", path, CodecTypeKey)
		}
		delete(secret.Data, CodecTypeKey)

		c, err := CodecFor(encoderType)
		if err != nil {
			return "", err
		}

		var b []byte
		if b, err = c.Marshal(path, secret.Data); err != nil {
			return "", err
		}

		content = strings.Replace(content, k, string(b), -1)
	}

	return
}

func (cmd *TemplateCommand) executeTemplateSecrets(client *Client, input string) (content string, err error) {
	content = input

	// For each of the secret paths, lookup the secret
	for path, kv := range cmd.lookup {
		var secret *api.Secret
		if secret, err = client.Logical().Read(strings.TrimLeft(path, "/")); err != nil {
			return
		}
		if secret == nil {
			return "", fmt.Errorf("secret %s: not found", path)
		}

		// For each of the secret keys, lookup the value
		for k, placeholder := range kv {
			if strings.HasPrefix(placeholder, "_VAULT_STRING_") {
				if v, ok := secret.Data[k].(string); ok {
					content = strings.Replace(content, placeholder, v, -1)
				} else {
					return "", fmt.Errorf("secret %s: key %q not found", path, k)
				}
			}
		}
	}

	return
}

func (cmd *TemplateCommand) executeTemplateNested(client *Client, input string) (content string, err error) {
	content = input

	// For each of the secret paths, lookup the secret
	for path, kv := range cmd.lookup {
		var secret *api.Secret
		if secret, err = client.Logical().Read(strings.TrimLeft(path, "/")); err != nil {
			return
		}
		if secret == nil {
			return "", fmt.Errorf("nested %s: not found", path)
		}

		// For each of the secret keys, lookup the value
		for k, placeholder := range kv {
			if strings.HasPrefix(placeholder, "_VAULT_NESTED_") {
				keys := strings.Split(k, ".")

				var nestedData map[string]interface{}
				if err := json.Unmarshal([]byte(secret.Data[keys[0]].(string)), &nestedData); err != nil {
					return "", fmt.Errorf("nested %s/%s: failed to parse JSON", path, keys[0])
				}

				mydata := nestedData
				levels := len(keys) - 1
				for _, nestedkey := range keys[1:] {
					if levels > 1 {
						if mydata[nestedkey] == nil {
							return "", fmt.Errorf("nested %s: key %q not found", path, nestedkey)
						}
						mydata = mydata[nestedkey].(map[string]interface{})
					} else {
						if _, ok := mydata[nestedkey].(string); ok {
							content = strings.Replace(content, placeholder, mydata[nestedkey].(string), -1)
						} else {
							return "", fmt.Errorf("nested %s: key %q is not a string", path, nestedkey)
						}
					}
					levels--
				}
			}

		}
	}

	return
}

func (cmd *TemplateCommand) templateDecode(path string) string {
	if _, ok := cmd.decode[path]; !ok {
		cmd.decode[path] = cmd.randomIdentifier("decode")
	}
	return cmd.decode[path]
}

func (cmd *TemplateCommand) templateSecret(path string, key string) string {
	kv, ok := cmd.lookup[path]
	if !ok {
		cmd.lookup[path] = make(map[string]string)
		kv = cmd.lookup[path]
	}

	if _, ok = kv[key]; !ok {
		kv[key] = cmd.randomIdentifier("string")
	}

	return kv[key]
}

func (cmd *TemplateCommand) templateNested(path string, key string) string {
	// keys := strings.Split(key, ".")

	kv, ok := cmd.lookup[path]
	if !ok {
		cmd.lookup[path] = make(map[string]string)
		kv = cmd.lookup[path]
	}

	if _, ok = kv[key]; !ok {
		kv[key] = cmd.randomIdentifier("nested")
	}

	return kv[key]
}

func (cmd *TemplateCommand) randomIdentifier(t string) string {
	r := make([]byte, 8)
	io.ReadFull(rand.Reader, r)
	return fmt.Sprintf("_VAULT_%s_%x_", strings.ToUpper(t), r)
}

func TemplateCommandFactory(ui cli.Ui) cli.CommandFactory {
	return func() (cli.Command, error) {
		cmd := &TemplateCommand{
			baseCommand: baseCommand{
				ui: ui,
			},
		}

		cmd.fs = flag.NewFlagSet("template", flag.ContinueOnError)
		cmd.fs.StringVar(&cmd.mod, "m", "0600", "output mode")
		cmd.fs.StringVar(&cmd.out, "o", "", "output (default: stdout)")
		cmd.fs.Usage = func() {
			fmt.Print(cmd.Help())
		}

		return cmd, nil
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"text/template"
)

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func groupByMulti(entries []*RuntimeContainer, key, sep string) map[string][]*RuntimeContainer {
	groups := make(map[string][]*RuntimeContainer)
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			items := strings.Split(value.(string), sep)
			for _, item := range items {
				groups[item] = append(groups[item], v)
			}

		}
	}
	return groups
}

// groupBy groups a list of *RuntimeContainers by the path property key
func groupBy(entries []*RuntimeContainer, key string) map[string][]*RuntimeContainer {
	groups := make(map[string][]*RuntimeContainer)
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			groups[value.(string)] = append(groups[value.(string)], v)
		}
	}
	return groups
}

// groupByKeys is the same as groupBy but only returns a list of keys
func groupByKeys(entries []*RuntimeContainer, key string) []string {
	groups := groupBy(entries, key)
	ret := []string{}
	for k, _ := range groups {
		ret = append(ret, k)
	}
	return ret
}

// selects entries based on key
func where(entries interface{}, key string, cmp interface{}) (interface{}, error) {
	entriesVal := reflect.ValueOf(entries)

	switch entriesVal.Kind() {
	case reflect.Array, reflect.Slice:
		break
	default:
		return nil, fmt.Errorf("Must pass an array or slice to 'where'; received %v", entries)
	}

	selection := make([]interface{}, 0)
	for i := 0; i < entriesVal.Len(); i++ {
		v := reflect.Indirect(entriesVal.Index(i)).Interface()

		value := deepGet(v, key)
		if reflect.DeepEqual(value, cmp) {
			selection = append(selection, v)
		}
	}

	return selection, nil
}

// selects entries where a key exists
func whereExist(entries []*RuntimeContainer, key string) []*RuntimeContainer {
	selection := []*RuntimeContainer{}
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			selection = append(selection, v)
		}
	}
	return selection
}

// selects entries where a key does not exist
func whereNotExist(entries []*RuntimeContainer, key string) []*RuntimeContainer {
	selection := []*RuntimeContainer{}
	for _, v := range entries {
		value := deepGet(*v, key)
		if value == nil {
			selection = append(selection, v)
		}
	}
	return selection
}

// selects entries based on key.  Assumes key is delimited and breaks it apart before comparing
func whereAny(entries []*RuntimeContainer, key, sep string, cmp []string) []*RuntimeContainer {
	selection := []*RuntimeContainer{}
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			items := strings.Split(value.(string), sep)
			if len(intersect(cmp, items)) > 0 {
				selection = append(selection, v)
			}
		}
	}
	return selection
}

// selects entries based on key.  Assumes key is delimited and breaks it apart before comparing
func whereAll(entries []*RuntimeContainer, key, sep string, cmp []string) []*RuntimeContainer {
	selection := []*RuntimeContainer{}
	req_count := len(cmp)
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			items := strings.Split(value.(string), sep)
			if len(intersect(cmp, items)) == req_count {
				selection = append(selection, v)
			}
		}
	}
	return selection
}

// hasPrefix returns whether a given string is a prefix of another string
func hasPrefix(prefix, s string) bool {
	return strings.HasPrefix(s, prefix)
}

// hasSuffix returns whether a given string is a suffix of another string
func hasSuffix(suffix, s string) bool {
	return strings.HasSuffix(s, suffix)
}

func keys(input interface{}) (interface{}, error) {
	if input == nil {
		return nil, nil
	}

	val := reflect.ValueOf(input)
	if val.Kind() != reflect.Map {
		return nil, fmt.Errorf("Cannot call keys on a non-map value: %v", input)
	}

	vk := val.MapKeys()
	k := make([]interface{}, val.Len())
	for i, _ := range k {
		k[i] = vk[i].Interface()
	}

	return k, nil
}

func intersect(l1, l2 []string) []string {
	m := make(map[string]bool)
	m2 := make(map[string]bool)
	for _, v := range l2 {
		m2[v] = true
	}
	for _, v := range l1 {
		if m2[v] {
			m[v] = true
		}
	}
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

func contains(item map[string]string, key string) bool {
	if _, ok := item[key]; ok {
		return true
	}
	return false
}

func dict(values ...interface{}) (map[string]interface{}, error) {
	if len(values)%2 != 0 {
		return nil, errors.New("invalid dict call")
	}
	dict := make(map[string]interface{}, len(values)/2)
	for i := 0; i < len(values); i += 2 {
		key, ok := values[i].(string)
		if !ok {
			return nil, errors.New("dict keys must be strings")
		}
		dict[key] = values[i+1]
	}
	return dict, nil
}

func hashSha1(input string) string {
	h := sha1.New()
	io.WriteString(h, input)
	return fmt.Sprintf("%x", h.Sum(nil))
}

func marshalJson(input interface{}) (string, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	if err := enc.Encode(input); err != nil {
		return "", err
	}
	return strings.TrimSuffix(buf.String(), "\n"), nil
}

func unmarshalJson(input string) (interface{}, error) {
	var v interface{}
	if err := json.Unmarshal([]byte(input), &v); err != nil {
		return nil, err
	}
	return v, nil
}

// arrayFirst returns first item in the array or nil if the
// input is nil or empty
func arrayFirst(input interface{}) interface{} {
	if input == nil {
		return nil
	}

	arr := reflect.ValueOf(input)

	if arr.Len() == 0 {
		return nil
	}

	return arr.Index(0).Interface()
}

// arrayLast returns last item in the array
func arrayLast(input interface{}) interface{} {
	arr := reflect.ValueOf(input)
	return arr.Index(arr.Len() - 1).Interface()
}

// arrayClosest find the longest matching substring in values
// that matches input
func arrayClosest(values []string, input string) string {
	best := ""
	for _, v := range values {
		if strings.Contains(input, v) && len(v) > len(best) {
			best = v
		}
	}
	return best
}

// dirList returns a list of files in the specified path
func dirList(path string) ([]string, error) {
	names := []string{}
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return names, err
	}
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names, nil
}

// coalesce returns the first non nil argument
func coalesce(input ...interface{}) interface{} {
	for _, v := range input {
		if v != nil {
			return v
		}
	}
	return nil
}

// trimPrefix returns whether a given string is a prefix of another string
func trimPrefix(prefix, s string) string {
	return strings.TrimPrefix(s, prefix)
}

// trimSuffix returns whether a given string is a suffix of another string
func trimSuffix(suffix, s string) string {
	return strings.TrimSuffix(s, suffix)
}

func newTemplate(name string) *template.Template {
	tmpl := template.New(name).Funcs(template.FuncMap{
		"closest":       arrayClosest,
		"coalesce":      coalesce,
		"contains":      contains,
		"dict":          dict,
		"dir":           dirList,
		"exists":        exists,
		"first":         arrayFirst,
		"groupBy":       groupBy,
		"groupByKeys":   groupByKeys,
		"groupByMulti":  groupByMulti,
		"hasPrefix":     hasPrefix,
		"hasSuffix":     hasSuffix,
		"json":          marshalJson,
		"intersect":     intersect,
		"keys":          keys,
		"last":          arrayLast,
		"replace":       strings.Replace,
		"parseJson":     unmarshalJson,
		"queryEscape":   url.QueryEscape,
		"sha1":          hashSha1,
		"split":         strings.Split,
		"trimPrefix":    trimPrefix,
		"trimSuffix":    trimSuffix,
		"where":         where,
		"whereExist":    whereExist,
		"whereNotExist": whereNotExist,
		"whereAny":      whereAny,
		"whereAll":      whereAll,
	})
	return tmpl
}

func generateFile(config Config, containers Context) bool {
	templatePath := config.Template
	tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
	if err != nil {
		log.Fatalf("unable to parse template: %s", err)
	}

	filteredContainers := Context{}
	if config.OnlyPublished {
		for _, container := range containers {
			if len(container.PublishedAddresses()) > 0 {
				filteredContainers = append(filteredContainers, container)
			}
		}
	} else if config.OnlyExposed {
		for _, container := range containers {
			if len(container.Addresses) > 0 {
				filteredContainers = append(filteredContainers, container)
			}
		}
	} else {
		filteredContainers = containers
	}

	dest := os.Stdout
	if config.Dest != "" {
		dest, err = ioutil.TempFile(filepath.Dir(config.Dest), "docker-gen")
		defer func() {
			dest.Close()
			os.Remove(dest.Name())
		}()
		if err != nil {
			log.Fatalf("unable to create temp file: %s\n", err)
		}
	}

	var buf bytes.Buffer
	multiwriter := io.MultiWriter(dest, &buf)
	err = tmpl.ExecuteTemplate(multiwriter, filepath.Base(templatePath), &filteredContainers)
	if err != nil {
		log.Fatalf("template error: %s\n", err)
	}

	if config.Dest != "" {

		contents := []byte{}
		if fi, err := os.Stat(config.Dest); err == nil {
			if err := dest.Chmod(fi.Mode()); err != nil {
				log.Fatalf("unable to chmod temp file: %s\n", err)
			}
			if err := dest.Chown(int(fi.Sys().(*syscall.Stat_t).Uid), int(fi.Sys().(*syscall.Stat_t).Gid)); err != nil {
				log.Fatalf("unable to chown temp file: %s\n", err)
			}
			contents, err = ioutil.ReadFile(config.Dest)
			if err != nil {
				log.Fatalf("unable to compare current file contents: %s: %s\n", config.Dest, err)
			}
		}

		if bytes.Compare(contents, buf.Bytes()) != 0 {
			err = os.Rename(dest.Name(), config.Dest)
			if err != nil {
				log.Fatalf("unable to create dest file %s: %s\n", config.Dest, err)
			}
			log.Printf("Generated '%s' from %d containers", config.Dest, len(filteredContainers))
			return true
		}
		return false
	}
	return true
}
<commit_msg>Use generalizedWhere for all where* functions<commit_after>package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"text/template"
)

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func groupByMulti(entries []*RuntimeContainer, key, sep string) map[string][]*RuntimeContainer {
	groups := make(map[string][]*RuntimeContainer)
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			items := strings.Split(value.(string), sep)
			for _, item := range items {
				groups[item] = append(groups[item], v)
			}

		}
	}
	return groups
}

// groupBy groups a list of *RuntimeContainers by the path property key
func groupBy(entries []*RuntimeContainer, key string) map[string][]*RuntimeContainer {
	groups := make(map[string][]*RuntimeContainer)
	for _, v := range entries {
		value := deepGet(*v, key)
		if value != nil {
			groups[value.(string)] = append(groups[value.(string)], v)
		}
	}
	return groups
}

// groupByKeys is the same as groupBy but only returns a list of keys
func groupByKeys(entries []*RuntimeContainer, key string) []string {
	groups := groupBy(entries, key)
	ret := []string{}
	for k, _ := range groups {
		ret = append(ret, k)
	}
	return ret
}

// Generalized where function
func generalizedWhere(entries interface{}, key string, test func(interface{}) bool) (interface{}, error) {
	entriesVal := reflect.ValueOf(entries)

	switch entriesVal.Kind() {
	case reflect.Array, reflect.Slice:
		break
	default:
		return nil, fmt.Errorf("Must pass an array or slice to 'where'; received %v", entries)
	}

	selection := make([]interface{}, 0)
	for i := 0; i < entriesVal.Len(); i++ {
		v := reflect.Indirect(entriesVal.Index(i)).Interface()

		value := deepGet(v, key)
		if test(value) {
			selection = append(selection, v)
		}
	}

	return selection, nil
}

// selects entries based on key
func where(entries interface{}, key string, cmp interface{}) (interface{}, error) {
	return generalizedWhere(entries, key, func(value interface{}) bool {
		return reflect.DeepEqual(value, cmp)
	})
}

// selects entries where a key exists
func whereExist(entries interface{}, key string) (interface{}, error) {
	return generalizedWhere(entries, key, func(value interface{}) bool {
		return value != nil
	})
}

// selects entries where a key does not exist
func whereNotExist(entries interface{}, key string) (interface{}, error) {
	return generalizedWhere(entries, key, func(value interface{}) bool {
		return value == nil
	})
}

// selects entries based on key.  Assumes key is delimited and breaks it apart before comparing
func whereAny(entries interface{}, key, sep string, cmp []string) (interface{}, error) {
	return generalizedWhere(entries, key, func(value interface{}) bool {
		if value == nil {
			return false
		} else {
			items := strings.Split(value.(string), sep)
			return len(intersect(cmp, items)) > 0
		}
	})
}

// selects entries based on key.  Assumes key is delimited and breaks it apart before comparing
func whereAll(entries interface{}, key, sep string, cmp []string) (interface{}, error) {
	req_count := len(cmp)
	return generalizedWhere(entries, key, func(value interface{}) bool {
		if value == nil {
			return false
		} else {
			items := strings.Split(value.(string), sep)
			return len(intersect(cmp, items)) == req_count
		}
	})
}

// hasPrefix returns whether a given string is a prefix of another string
func hasPrefix(prefix, s string) bool {
	return strings.HasPrefix(s, prefix)
}

// hasSuffix returns whether a given string is a suffix of another string
func hasSuffix(suffix, s string) bool {
	return strings.HasSuffix(s, suffix)
}

func keys(input interface{}) (interface{}, error) {
	if input == nil {
		return nil, nil
	}

	val := reflect.ValueOf(input)
	if val.Kind() != reflect.Map {
		return nil, fmt.Errorf("Cannot call keys on a non-map value: %v", input)
	}

	vk := val.MapKeys()
	k := make([]interface{}, val.Len())
	for i, _ := range k {
		k[i] = vk[i].Interface()
	}

	return k, nil
}

func intersect(l1, l2 []string) []string {
	m := make(map[string]bool)
	m2 := make(map[string]bool)
	for _, v := range l2 {
		m2[v] = true
	}
	for _, v := range l1 {
		if m2[v] {
			m[v] = true
		}
	}
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

func contains(item map[string]string, key string) bool {
	if _, ok := item[key]; ok {
		return true
	}
	return false
}

func dict(values ...interface{}) (map[string]interface{}, error) {
	if len(values)%2 != 0 {
		return nil, errors.New("invalid dict call")
	}
	dict := make(map[string]interface{}, len(values)/2)
	for i := 0; i < len(values); i += 2 {
		key, ok := values[i].(string)
		if !ok {
			return nil, errors.New("dict keys must be strings")
		}
		dict[key] = values[i+1]
	}
	return dict, nil
}

func hashSha1(input string) string {
	h := sha1.New()
	io.WriteString(h, input)
	return fmt.Sprintf("%x", h.Sum(nil))
}

func marshalJson(input interface{}) (string, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	if err := enc.Encode(input); err != nil {
		return "", err
	}
	return strings.TrimSuffix(buf.String(), "\n"), nil
}

func unmarshalJson(input string) (interface{}, error) {
	var v interface{}
	if err := json.Unmarshal([]byte(input), &v); err != nil {
		return nil, err
	}
	return v, nil
}

// arrayFirst returns first item in the array or nil if the
// input is nil or empty
func arrayFirst(input interface{}) interface{} {
	if input == nil {
		return nil
	}

	arr := reflect.ValueOf(input)

	if arr.Len() == 0 {
		return nil
	}

	return arr.Index(0).Interface()
}

// arrayLast returns last item in the array
func arrayLast(input interface{}) interface{} {
	arr := reflect.ValueOf(input)
	return arr.Index(arr.Len() - 1).Interface()
}

// arrayClosest find the longest matching substring in values
// that matches input
func arrayClosest(values []string, input string) string {
	best := ""
	for _, v := range values {
		if strings.Contains(input, v) && len(v) > len(best) {
			best = v
		}
	}
	return best
}

// dirList returns a list of files in the specified path
func dirList(path string) ([]string, error) {
	names := []string{}
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return names, err
	}
	for _, f := range files {
		names = append(names, f.Name())
	}
	return names, nil
}

// coalesce returns the first non nil argument
func coalesce(input ...interface{}) interface{} {
	for _, v := range input {
		if v != nil {
			return v
		}
	}
	return nil
}

// trimPrefix returns whether a given string is a prefix of another string
func trimPrefix(prefix, s string) string {
	return strings.TrimPrefix(s, prefix)
}

// trimSuffix returns whether a given string is a suffix of another string
func trimSuffix(suffix, s string) string {
	return strings.TrimSuffix(s, suffix)
}

func newTemplate(name string) *template.Template {
	tmpl := template.New(name).Funcs(template.FuncMap{
		"closest":       arrayClosest,
		"coalesce":      coalesce,
		"contains":      contains,
		"dict":          dict,
		"dir":           dirList,
		"exists":        exists,
		"first":         arrayFirst,
		"groupBy":       groupBy,
		"groupByKeys":   groupByKeys,
		"groupByMulti":  groupByMulti,
		"hasPrefix":     hasPrefix,
		"hasSuffix":     hasSuffix,
		"json":          marshalJson,
		"intersect":     intersect,
		"keys":          keys,
		"last":          arrayLast,
		"replace":       strings.Replace,
		"parseJson":     unmarshalJson,
		"queryEscape":   url.QueryEscape,
		"sha1":          hashSha1,
		"split":         strings.Split,
		"trimPrefix":    trimPrefix,
		"trimSuffix":    trimSuffix,
		"where":         where,
		"whereExist":    whereExist,
		"whereNotExist": whereNotExist,
		"whereAny":      whereAny,
		"whereAll":      whereAll,
	})
	return tmpl
}

func generateFile(config Config, containers Context) bool {
	templatePath := config.Template
	tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath)
	if err != nil {
		log.Fatalf("unable to parse template: %s", err)
	}

	filteredContainers := Context{}
	if config.OnlyPublished {
		for _, container := range containers {
			if len(container.PublishedAddresses()) > 0 {
				filteredContainers = append(filteredContainers, container)
			}
		}
	} else if config.OnlyExposed {
		for _, container := range containers {
			if len(container.Addresses) > 0 {
				filteredContainers = append(filteredContainers, container)
			}
		}
	} else {
		filteredContainers = containers
	}

	dest := os.Stdout
	if config.Dest != "" {
		dest, err = ioutil.TempFile(filepath.Dir(config.Dest), "docker-gen")
		defer func() {
			dest.Close()
			os.Remove(dest.Name())
		}()
		if err != nil {
			log.Fatalf("unable to create temp file: %s\n", err)
		}
	}

	var buf bytes.Buffer
	multiwriter := io.MultiWriter(dest, &buf)
	err = tmpl.ExecuteTemplate(multiwriter, filepath.Base(templatePath), &filteredContainers)
	if err != nil {
		log.Fatalf("template error: %s\n", err)
	}

	if config.Dest != "" {

		contents := []byte{}
		if fi, err := os.Stat(config.Dest); err == nil {
			if err := dest.Chmod(fi.Mode()); err != nil {
				log.Fatalf("unable to chmod temp file: %s\n", err)
			}
			if err := dest.Chown(int(fi.Sys().(*syscall.Stat_t).Uid), int(fi.Sys().(*syscall.Stat_t).Gid)); err != nil {
				log.Fatalf("unable to chown temp file: %s\n", err)
			}
			contents, err = ioutil.ReadFile(config.Dest)
			if err != nil {
				log.Fatalf("unable to compare current file contents: %s: %s\n", config.Dest, err)
			}
		}

		if bytes.Compare(contents, buf.Bytes()) != 0 {
			err = os.Rename(dest.Name(), config.Dest)
			if err != nil {
				log.Fatalf("unable to create dest file %s: %s\n", config.Dest, err)
			}
			log.Printf("Generated '%s' from %d containers", config.Dest, len(filteredContainers))
			return true
		}
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package terminfo

import (
	"errors"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/nhooyr/terminfo/caps"
)

// Terminfo describes a terminal's capabilities.
type Terminfo struct {
	Names      []string
	Bools      [caps.BoolCount]bool
	Numbers    [caps.NumberCount]int16
	Strings    [caps.StringCount]string
	ExtBools   map[string]bool
	ExtNumbers map[string]int16
	ExtStrings map[string]string
}

// Terminfo cache.
var (
	db      = make(map[string]*Terminfo)
	dbMutex = new(sync.RWMutex)
)

// LoadEnv calls Load with the name as $TERM.
func LoadEnv() (*Terminfo, error) {
	return Load(os.Getenv("TERM"))
}

var ErrEmptyTerm = errors.New("terminfo: empty term name")

// Load follows the behavior described in terminfo(5) to find correct the terminfo file
// using the name, reads the file and then returns a Terminfo struct that describes the file.
func Load(name string) (ti *Terminfo, err error) {
	if name == "" {
		return nil, ErrEmptyTerm
	}
	dbMutex.RLock()
	ti, ok := db[name]
	dbMutex.RUnlock()
	if ok {
		return
	}
	if terminfo := os.Getenv("TERMINFO"); terminfo != "" {
		return openDir(terminfo, name)
	}
	if home := os.Getenv("HOME"); home != "" {
		ti, err = openDir(home+"/.terminfo", name)
		if err == nil {
			return
		}
	}
	if dirs := os.Getenv("TERMINFO_DIRS"); dirs != "" {
		for _, dir := range strings.Split(dirs, ":") {
			if dir == "" {
				dir = "/usr/share/terminfo"
			}
			ti, err = openDir(dir, name)
			if err == nil {
				return
			}
		}
	}
	return openDir("/usr/share/terminfo", name)
}

// openDir reads the Terminfo file specified by the dir and name.
func openDir(dir, name string) (*Terminfo, error) {
	// Try typical *nix path.
	b, err := ioutil.ReadFile(dir + "/" + name[0:1] + "/" + name)
	if err != nil {
		// Fallback to the darwin specific path.
		b, err = ioutil.ReadFile(dir + "/" + strconv.FormatUint(uint64(name[0]), 16) + "/" + name)
		if err != nil {
			return nil, err
		}
	}
	r := &decoder{buf: b}
	if err = r.unmarshal(); err != nil {
		return nil, err
	}
	// Cache the Terminfo struct.
	dbMutex.Lock()
	for i := range r.ti.Names {
		db[r.ti.Names[i]] = r.ti
	}
	dbMutex.Unlock()
	return r.ti, nil
}

// Color takes a foreground and background color and returns string
// that sets them for this terminal.
func (ti *Terminfo) Color(fg, bg int) (rv string) {
	maxColors := int(ti.Numbers[caps.MaxColors])
	// Map bright colors to lower versions if color table only holds 8.
	if maxColors == 8 {
		if fg > 7 && fg < 16 {
			fg -= 8
		}
		if bg > 7 && bg < 16 {
			bg -= 8
		}
	}
	if maxColors > fg && fg >= 0 {
		rv += Parm(ti.Strings[caps.SetAForeground], fg)
	}
	if maxColors > bg && bg >= 0 {
		rv += Parm(ti.Strings[caps.SetABackground], bg)
	}
	return
}
<commit_msg>comment on ErrEmptyTerm<commit_after>package terminfo

import (
	"errors"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/nhooyr/terminfo/caps"
)

// Terminfo describes a terminal's capabilities.
type Terminfo struct {
	Names      []string
	Bools      [caps.BoolCount]bool
	Numbers    [caps.NumberCount]int16
	Strings    [caps.StringCount]string
	ExtBools   map[string]bool
	ExtNumbers map[string]int16
	ExtStrings map[string]string
}

// Terminfo cache.
var (
	db      = make(map[string]*Terminfo)
	dbMutex = new(sync.RWMutex)
)

// LoadEnv calls Load with the name as $TERM.
func LoadEnv() (*Terminfo, error) {
	return Load(os.Getenv("TERM"))
}

// ErrEmptyTerm is returned when no name is provided to Load.
var ErrEmptyTerm = errors.New("terminfo: empty term name")

// Load follows the behavior described in terminfo(5) to find correct the terminfo file
// using the name, reads the file and then returns a Terminfo struct that describes the file.
func Load(name string) (ti *Terminfo, err error) {
	if name == "" {
		return nil, ErrEmptyTerm
	}
	dbMutex.RLock()
	ti, ok := db[name]
	dbMutex.RUnlock()
	if ok {
		return
	}
	if terminfo := os.Getenv("TERMINFO"); terminfo != "" {
		return openDir(terminfo, name)
	}
	if home := os.Getenv("HOME"); home != "" {
		ti, err = openDir(home+"/.terminfo", name)
		if err == nil {
			return
		}
	}
	if dirs := os.Getenv("TERMINFO_DIRS"); dirs != "" {
		for _, dir := range strings.Split(dirs, ":") {
			if dir == "" {
				dir = "/usr/share/terminfo"
			}
			ti, err = openDir(dir, name)
			if err == nil {
				return
			}
		}
	}
	return openDir("/usr/share/terminfo", name)
}

// openDir reads the Terminfo file specified by the dir and name.
func openDir(dir, name string) (*Terminfo, error) {
	// Try typical *nix path.
	b, err := ioutil.ReadFile(dir + "/" + name[0:1] + "/" + name)
	if err != nil {
		// Fallback to the darwin specific path.
		b, err = ioutil.ReadFile(dir + "/" + strconv.FormatUint(uint64(name[0]), 16) + "/" + name)
		if err != nil {
			return nil, err
		}
	}
	r := &decoder{buf: b}
	if err = r.unmarshal(); err != nil {
		return nil, err
	}
	// Cache the Terminfo struct.
	dbMutex.Lock()
	for i := range r.ti.Names {
		db[r.ti.Names[i]] = r.ti
	}
	dbMutex.Unlock()
	return r.ti, nil
}

// Color takes a foreground and background color and returns string
// that sets them for this terminal.
func (ti *Terminfo) Color(fg, bg int) (rv string) {
	maxColors := int(ti.Numbers[caps.MaxColors])
	// Map bright colors to lower versions if color table only holds 8.
	if maxColors == 8 {
		if fg > 7 && fg < 16 {
			fg -= 8
		}
		if bg > 7 && bg < 16 {
			bg -= 8
		}
	}
	if maxColors > fg && fg >= 0 {
		rv += Parm(ti.Strings[caps.SetAForeground], fg)
	}
	if maxColors > bg && bg >= 0 {
		rv += Parm(ti.Strings[caps.SetABackground], bg)
	}
	return
}
<|endoftext|>


<commit_before>package mock

type eventbusMock struct {
}

func New() *eventbusMock {
	e := new(eventbusMock)
	return e
}

func (e *eventbusMock) RegisterHandler(fn interface{}) error {
	return nil
}

func (e *eventbusMock) UnregisterHandler(fn interface{}) error {
	return nil
}

func (e *eventbusMock) Publish(event interface{}) error {
	return nil
}
<commit_msg>test eventhandler registration<commit_after>package mock

import "fmt"

type eventbusMock struct {
	RegisterHandlerFunc   func(fn interface{}) error
	UnregisterHandlerFunc func(fn interface{}) error
	PublishFunc           func(fn interface{}) error
}

func New() *eventbusMock {
	e := new(eventbusMock)
	return e
}

func (e *eventbusMock) RegisterHandler(fn interface{}) error {
	if e.RegisterHandlerFunc == nil {
		return fmt.Errorf("RegisterHandlerFunc not defined")
	}
	return e.RegisterHandlerFunc(fn)
}

func (e *eventbusMock) UnregisterHandler(fn interface{}) error {
	if e.UnregisterHandlerFunc == nil {
		return fmt.Errorf("UnregisterHandlerFunc not defined")
	}
	return e.UnregisterHandlerFunc(fn)
}

func (e *eventbusMock) Publish(event interface{}) error {
	if e.PublishFunc == nil {
		return fmt.Errorf("PublishFunc not defined")
	}
	return e.PublishFunc(event)
}
<|endoftext|>


<commit_before>package models

import "time"

// Publication Main structure for a publication
type Publication struct {
	Context   []string `json:"@context,omitempty"`
	Metadata  Metadata `json:"metadata"`
	Links     []Link   `json:"links"`
	Spine     []Link   `json:"spine"`
	Resources []Link   `json:"resources,omitempty"` //Replaces the manifest but less redundant
	TOC       []Link   `json:"toc,omitempty"`
	PageList  []Link   `json:"page-list,omitempty"`
	Landmarks []Link   `json:"landmarks,omitempty"`
	LOI       []Link   `json:"loi,omitempty"` //List of illustrations
	LOA       []Link   `json:"loa,omitempty"` //List of audio files
	LOV       []Link   `json:"lov,omitempty"` //List of videos
	LOT       []Link   `json:"lot,omitempty"` //List of tables

	OtherLinks       []Link                  `json:"-"` //Extension point for links that shouldn't show up in the manifest
	OtherCollections []PublicationCollection `json:"-"` //Extension point for collections that shouldn't show up in the manifest
	Internal         []Internal              `json:"-"`
}

// Internal TODO
type Internal struct {
	Name  string
	Value interface{}
}

// Metadata for the default context
type Metadata struct {
	RDFType         string        `json:"@type,omitempty"` //Defaults to schema.org for EBook
	Title           string        `json:"title"`
	Identifier      string        `json:"identifier"`
	Author          []Contributor `json:"author,omitempty"`
	Translator      []Contributor `json:"translator,omitempty"`
	Editor          []Contributor `json:"editor,omitempty"`
	Artist          []Contributor `json:"artist,omitempty"`
	Illustrator     []Contributor `json:"illustrator,omitempty"`
	Letterer        []Contributor `json:"letterer,omitempty"`
	Penciler        []Contributor `json:"penciler,omitempty"`
	Colorist        []Contributor `json:"colorist,omitempty"`
	Inker           []Contributor `json:"inker,omitempty"`
	Narrator        []Contributor `json:"narrator,omitempty"`
	Contributor     []Contributor `json:"contributor,omitempty"`
	Publisher       []Contributor `json:"publisher,omitempty"`
	Imprint         []Contributor `json:"imprint,omitempty"`
	Language        []string      `json:"language,omitempty"`
	Modified        *time.Time    `json:"modified,omitempty"`
	PublicationDate *time.Time    `json:"published,omitempty"`
	Description     string        `json:"description,omitempty"`
	Direction       string        `json:"direction,omitempty"`
	Rendition       *Rendition    `json:"rendition,omitempty"`
	Source          string        `json:"source,omitempty"`
	EpubType        []string      `json:"epub-type,omitempty"`
	Right           string        `json:"right,omitempty"`
	Subject         []Subject     `json:"subject,omitempty"`

	OtherMetadata []Meta `json:"-"` //Extension point for other metadata
}

// Meta is a generic structure for other metadata
type Meta struct {
	property string
	value    string
	children []Meta
}

// Link object used in collections and links
type Link struct {
	Href       string         `json:"href"`
	TypeLink   string         `json:"type"`
	Rel        []string       `json:"rel,omitempty"`
	Height     int            `json:"height,omitempty"`
	Width      int            `json:"width,omitempty"`
	Title      string         `json:"title,omitempty"`
	Properties []string       `json:"properties,omitempty"`
	Duration   *time.Duration `json:"duration,omitempty"`
	Templated  bool           `json:"templated,omitempty"`
}

// Contributor construct used internally for all contributors
type Contributor struct {
	Name       string `json:"name"`
	SortAs     string `json:"sort_as,omitempty"`
	Identifier string `json:"identifier,omitempty"`
	Role       string `json:"role,omitempty"`
}

// Rendition object for reflow/FXL
type Rendition struct {
	Flow        string `json:"flow,omitempty"`
	Layout      string `json:"layout,omitempty"`
	Orientation string `json:"orientation,omitempty"`
	Spread      string `json:"spread,omitempty"`
}

// Subject as based on EPUB 3.1 and Webpub
type Subject struct {
	Name   string `json:"name"`
	SortAs string `json:"sort_as,omitempty"`
	Scheme string `json:"scheme,omitempty"`
	Code   string `json:"code,omitempty"`
}

// BelongsTo is a list of collections that a publication belongs to
type BelongsTo struct {
	Series     []Collection `json:"series,omitempty"`
	Collection []Collection `json:"collection,omitempty"`
}

// Collection construct used for collection/serie metadata
type Collection struct {
	Name       string  `json:"name"`
	SortAs     string  `json:"sort_as,omitempty"`
	Identifier string  `json:"identifier,omitempty"`
	Position   float32 `json:"position,omitempty"`
}

// PublicationCollection is used as an extension points for other collections in a Publication
type PublicationCollection struct {
	Role     string
	Metadata []Meta
	Links    []Link
	Children []PublicationCollection
}

// GetCover return the link for the cover
func (publication *Publication) GetCover() Link {
	for _, resource := range publication.Resources {
		for _, rel := range resource.Rel {
			if rel == "cover" {
				return resource
			}
		}
	}

	return Link{}
}

// GetNavDoc return the link for the navigation document
func (publication *Publication) GetNavDoc() Link {
	// returns the link object for the navigation doc (EPUB 3.x)
	return Link{}
}
<commit_msg>search cover in resource and spine<commit_after>package models

import "time"

// Publication Main structure for a publication
type Publication struct {
	Context   []string `json:"@context,omitempty"`
	Metadata  Metadata `json:"metadata"`
	Links     []Link   `json:"links"`
	Spine     []Link   `json:"spine"`
	Resources []Link   `json:"resources,omitempty"` //Replaces the manifest but less redundant
	TOC       []Link   `json:"toc,omitempty"`
	PageList  []Link   `json:"page-list,omitempty"`
	Landmarks []Link   `json:"landmarks,omitempty"`
	LOI       []Link   `json:"loi,omitempty"` //List of illustrations
	LOA       []Link   `json:"loa,omitempty"` //List of audio files
	LOV       []Link   `json:"lov,omitempty"` //List of videos
	LOT       []Link   `json:"lot,omitempty"` //List of tables

	OtherLinks       []Link                  `json:"-"` //Extension point for links that shouldn't show up in the manifest
	OtherCollections []PublicationCollection `json:"-"` //Extension point for collections that shouldn't show up in the manifest
	Internal         []Internal              `json:"-"`
}

// Internal TODO
type Internal struct {
	Name  string
	Value interface{}
}

// Metadata for the default context
type Metadata struct {
	RDFType         string        `json:"@type,omitempty"` //Defaults to schema.org for EBook
	Title           string        `json:"title"`
	Identifier      string        `json:"identifier"`
	Author          []Contributor `json:"author,omitempty"`
	Translator      []Contributor `json:"translator,omitempty"`
	Editor          []Contributor `json:"editor,omitempty"`
	Artist          []Contributor `json:"artist,omitempty"`
	Illustrator     []Contributor `json:"illustrator,omitempty"`
	Letterer        []Contributor `json:"letterer,omitempty"`
	Penciler        []Contributor `json:"penciler,omitempty"`
	Colorist        []Contributor `json:"colorist,omitempty"`
	Inker           []Contributor `json:"inker,omitempty"`
	Narrator        []Contributor `json:"narrator,omitempty"`
	Contributor     []Contributor `json:"contributor,omitempty"`
	Publisher       []Contributor `json:"publisher,omitempty"`
	Imprint         []Contributor `json:"imprint,omitempty"`
	Language        []string      `json:"language,omitempty"`
	Modified        *time.Time    `json:"modified,omitempty"`
	PublicationDate *time.Time    `json:"published,omitempty"`
	Description     string        `json:"description,omitempty"`
	Direction       string        `json:"direction,omitempty"`
	Rendition       *Rendition    `json:"rendition,omitempty"`
	Source          string        `json:"source,omitempty"`
	EpubType        []string      `json:"epub-type,omitempty"`
	Right           string        `json:"right,omitempty"`
	Subject         []Subject     `json:"subject,omitempty"`

	OtherMetadata []Meta `json:"-"` //Extension point for other metadata
}

// Meta is a generic structure for other metadata
type Meta struct {
	property string
	value    string
	children []Meta
}

// Link object used in collections and links
type Link struct {
	Href       string         `json:"href"`
	TypeLink   string         `json:"type"`
	Rel        []string       `json:"rel,omitempty"`
	Height     int            `json:"height,omitempty"`
	Width      int            `json:"width,omitempty"`
	Title      string         `json:"title,omitempty"`
	Properties []string       `json:"properties,omitempty"`
	Duration   *time.Duration `json:"duration,omitempty"`
	Templated  bool           `json:"templated,omitempty"`
}

// Contributor construct used internally for all contributors
type Contributor struct {
	Name       string `json:"name"`
	SortAs     string `json:"sort_as,omitempty"`
	Identifier string `json:"identifier,omitempty"`
	Role       string `json:"role,omitempty"`
}

// Rendition object for reflow/FXL
type Rendition struct {
	Flow        string `json:"flow,omitempty"`
	Layout      string `json:"layout,omitempty"`
	Orientation string `json:"orientation,omitempty"`
	Spread      string `json:"spread,omitempty"`
}

// Subject as based on EPUB 3.1 and Webpub
type Subject struct {
	Name   string `json:"name"`
	SortAs string `json:"sort_as,omitempty"`
	Scheme string `json:"scheme,omitempty"`
	Code   string `json:"code,omitempty"`
}

// BelongsTo is a list of collections that a publication belongs to
type BelongsTo struct {
	Series     []Collection `json:"series,omitempty"`
	Collection []Collection `json:"collection,omitempty"`
}

// Collection construct used for collection/serie metadata
type Collection struct {
	Name       string  `json:"name"`
	SortAs     string  `json:"sort_as,omitempty"`
	Identifier string  `json:"identifier,omitempty"`
	Position   float32 `json:"position,omitempty"`
}

// PublicationCollection is used as an extension points for other collections in a Publication
type PublicationCollection struct {
	Role     string
	Metadata []Meta
	Links    []Link
	Children []PublicationCollection
}

// GetCover return the link for the cover
func (publication *Publication) GetCover() Link {
	for _, resource := range publication.Resources {
		for _, rel := range resource.Rel {
			if rel == "cover" {
				return resource
			}
		}
	}

	for _, item := range publication.Spine {
		for _, rel := range item.Rel {
			if rel == "cover" {
				return item
			}
		}
	}

	return Link{}
}

// GetNavDoc return the link for the navigation document
func (publication *Publication) GetNavDoc() Link {
	// returns the link object for the navigation doc (EPUB 3.x)
	return Link{}
}
<|endoftext|>


<commit_before>package test

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"

	"github.com/op/go-logging"

	"build"
	"core"
)

var log = logging.MustGetLogger("test")

const dummyOutput = "=== RUN DummyTest\n--- PASS: DummyTest (0.00s)\nPASS\n"
const dummyCoverage = "<?xml version=\"1.0\" ?><coverage></coverage>"

func Test(tid int, state *core.BuildState, label core.BuildLabel) {
	state.LogBuildResult(tid, label, core.TargetTesting, "Testing...")
	startTime := time.Now()
	target := state.Graph.TargetOrDie(label)
	hash, err := build.RuntimeHash(state, target)
	if err != nil {
		state.LogBuildError(tid, label, core.TargetTestFailed, err, "Failed to calculate target hash")
		return
	}
	// Check the cached output files if the target wasn't rebuilt.
	hashStr := base64.RawURLEncoding.EncodeToString(core.CollapseHash(hash))
	resultsFileName := fmt.Sprintf(".test_results_%s_%s", label.Name, hashStr)
	coverageFileName := fmt.Sprintf(".test_coverage_%s_%s", label.Name, hashStr)
	outputFile := path.Join(target.TestDir(), "test.results")
	coverageFile := path.Join(target.TestDir(), "test.coverage")
	cachedOutputFile := path.Join(target.OutDir(), resultsFileName)
	cachedCoverageFile := path.Join(target.OutDir(), coverageFileName)
	needCoverage := state.NeedCoverage && !target.NoTestOutput

	cachedTest := func() {
		log.Debug("Not re-running test %s; got cached results.", label)
		coverage := parseCoverageFile(target, cachedCoverageFile)
		results, err := parseTestResults(target, cachedOutputFile, 0, true)
		target.Results.Duration = time.Since(startTime).Seconds()
		target.Results.Cached = true
		if err != nil {
			state.LogBuildError(tid, label, core.TargetTestFailed, err, "Failed to parse cached test file %s", cachedOutputFile)
		} else if results.Failed > 0 {
			panic("Test results with failures shouldn't be cached.")
		} else {
			logTestSuccess(state, tid, label, results, coverage)
		}
	}

	moveAndCacheOutputFiles := func(results core.TestResults, coverage core.TestCoverage) bool {
		// Never cache test results when given arguments; the results may be incomplete.
		if len(state.TestArgs) > 0 {
			log.Debug("Not caching results for %s, we passed it arguments", label)
			return true
		}
		if err := moveAndCacheOutputFile(state, target, hash, outputFile, cachedOutputFile, resultsFileName, dummyOutput); err != nil {
			state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err, "Failed to move test output file")
			return false
		}
		if needCoverage || core.PathExists(coverageFile) {
			if err := moveAndCacheOutputFile(state, target, hash, coverageFile, cachedCoverageFile, coverageFileName, dummyCoverage); err != nil {
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err, "Failed to move test coverage file")
				return false
			}
		}
		for _, output := range target.TestOutputs {
			tmpFile := path.Join(target.TestDir(), output)
			outFile := path.Join(target.OutDir(), output)
			if err := moveAndCacheOutputFile(state, target, hash, tmpFile, outFile, output, ""); err != nil {
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err, "Failed to move test output file")
				return false
			}
		}
		return true
	}

	needToRun := func() bool {
		if target.State() == core.Unchanged && core.PathExists(cachedOutputFile) {
			// Output file exists already and appears to be valid. We might still need to rerun though
			// if the coverage files aren't available.
			if needCoverage && !core.PathExists(cachedCoverageFile) {
				return true
			}
			return false
		}
		// Check the cache for these artifacts.
		if state.Cache == nil {
			return true
		}
		cache := *state.Cache
		if !cache.RetrieveExtra(target, hash, resultsFileName) {
			return true
		}
		if needCoverage && !cache.RetrieveExtra(target, hash, coverageFileName) {
			return true
		}
		for _, output := range target.TestOutputs {
			if !cache.RetrieveExtra(target, hash, output) {
				return true
			}
		}
		return false
	}

	// Don't cache when doing multiple runs, presumably the user explicitly wants to check it.
	if state.NumTestRuns <= 1 && !needToRun() {
		cachedTest()
		return
	}
	// Remove any cached test result file.
	if err := RemoveCachedTestFiles(target); err != nil {
		state.LogBuildError(tid, label, core.TargetTestFailed, err, "Failed to remove cached test files")
		return
	}
	numSucceeded := 0
	numRuns, successesRequired := calcNumRuns(state.NumTestRuns, target.Flakiness)
	for i := 0; i < numRuns && numSucceeded < successesRequired; i++ {
		if numRuns > 1 {
			state.LogBuildResult(tid, label, core.TargetTesting, fmt.Sprintf("Testing (%d of %d)...", i, numRuns))
		}
		out, err, flakes := runTestWithRetries(tid, state, target)
		duration := time.Since(startTime).Seconds()
		startTime = time.Now() // reset this for next time

		// This is all pretty involved; there are lots of different possibilities of what could happen.
		// The contract is that the test must return zero on success or non-zero on failure (Unix FTW).
		// If it's successful, it must produce a parseable file named "test.results" in its temp folder.
		// (alternatively, this can be a directory containing parseable files).
		// Tests can opt out of the file requirement individually, in which case they're judged only
		// by their return value.
		// But of course, we still have to consider all the alternatives here and handle them nicely.
		target.Results.Output = string(out)
		if err != nil && target.Results.Output == "" {
			target.Results.Output = err.Error()
		}
		if err != nil {
			_, target.Results.TimedOut = err.(core.TimeoutError)
		}
		coverage := parseCoverageFile(target, coverageFile)
		if !core.PathExists(outputFile) {
			target.Results.Duration += duration
			if err == nil && target.NoTestOutput {
				results := core.TestResults{NumTests: 1, Passed: 1, Flakes: flakes}
				if moveAndCacheOutputFiles(results, coverage) {
					target.Results.NumTests = 1
					target.Results.Passed = 1
					logTestSuccess(state, tid, label, results, coverage)
				}
			} else if err == nil {
				target.Results.NumTests++
				target.Results.Failed++
				err = fmt.Errorf("Test failed to produce output results file")
				state.LogBuildError(tid, label, core.TargetTestFailed, err,
					"Test apparently succeeded but failed to produce %s. Output: %s", outputFile, string(out))
			} else {
				target.Results.NumTests++
				target.Results.Failed++
				state.LogBuildError(tid, label, core.TargetTestFailed, err,
					fmt.Sprintf("Test failed with no results. Output: %s", string(out)))
			}
		} else {
			results, err2 := parseTestResults(target, outputFile, flakes, false)
			target.Results.Duration += duration
			if err2 != nil {
				state.LogBuildError(tid, label, core.TargetTestFailed, err2,
					"Couldn't parse test output file: %s. Stdout: %s", err2, string(out))
			} else if err != nil && results.Failed == 0 {
				// Add a failure result to the test so it shows up in the final aggregation.
				results.Failed = 1
				results.Failures = append(results.Failures, core.TestFailure{
					Name:   "Return value",
					Type:   fmt.Sprintf("%s", err),
					Stdout: string(out),
				})
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err,
					"Test returned nonzero but reported no errors: %s. Output: %s", err, string(out))
			} else if err == nil && results.Failed != 0 {
				err = fmt.Errorf("Test returned 0 but still reported failures")
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err,
					"Test returned 0 but still reported failures. Stdout: %s", string(out))
			} else if results.Failed != 0 {
				err = fmt.Errorf("Tests failed")
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err,
					fmt.Sprintf("Tests failed. Stdout: %s", string(out)))
			} else {
				logTestSuccess(state, tid, label, results, coverage)
				numSucceeded++
				// Cache only on the last run.
				if numSucceeded >= successesRequired {
					moveAndCacheOutputFiles(results, coverage)
				}
				// Clean up the test directory.
				if state.CleanWorkdirs {
					if err := os.RemoveAll(target.TestDir()); err != nil {
						log.Warning("Failed to remove test directory for %s: %s", target.Label, err)
					}
				}
			}
		}
	}
}

func logTestSuccess(state *core.BuildState, tid int, label core.BuildLabel, results core.TestResults, coverage core.TestCoverage) {
	var description string
	tests := pluralise("test", results.NumTests)
	if results.Skipped != 0 || results.ExpectedFailures != 0 {
		failures := pluralise("failure", results.ExpectedFailures)
		description = fmt.Sprintf("%d %s passed. %d skipped, %d expected %s",
			results.NumTests, tests, results.Skipped, results.ExpectedFailures, failures)
	} else {
		description = fmt.Sprintf("%d %s passed.", results.NumTests, tests)
	}
	state.LogTestResult(tid, label, core.TargetTested, results, coverage, nil, description)
}

func pluralise(word string, quantity int) string {
	if quantity == 1 {
		return word
	}
	return word + "s"
}

func prepareTestDir(graph *core.BuildGraph, target *core.BuildTarget) error {
	if err := os.RemoveAll(target.TestDir()); err != nil {
		return err
	}
	if err := os.MkdirAll(target.TestDir(), core.DirPermissions); err != nil {
		return err
	}
	for out := range core.IterRuntimeFiles(graph, target, true) {
		if err := core.PrepareSourcePair(out); err != nil {
			return err
		}
	}
	return nil
}

func runTest(state *core.BuildState, target *core.BuildTarget, timeout int) ([]byte, error) {
	replacedCmd := build.ReplaceTestSequences(target, target.TestCommand)
	env := core.BuildEnvironment(state, target, true)
	if len(state.TestArgs) > 0 {
		args := strings.Join(state.TestArgs, " ")
		replacedCmd += " " + args
		env = append(env, "TESTS="+args)
	}
	cmd := exec.Command("bash", "-c", replacedCmd)
	cmd.Dir = target.TestDir()
	cmd.Env = env
	log.Debug("Running test %s\nENVIRONMENT:\n%s\n%s", target.Label, strings.Join(cmd.Env, "\n"), replacedCmd)
	if state.PrintCommands {
		log.Notice("Running test %s: %s", target.Label, replacedCmd)
	}
	return core.ExecWithTimeout(cmd, target.TestTimeout, timeout)
}

// Runs a test some number of times as indicated by its flakiness.
func runTestWithRetries(tid int, state *core.BuildState, target *core.BuildTarget) (out []byte, err error, flakiness int) {
	flakiness = target.Flakiness
	if flakiness == 0 {
		flakiness = 1
	}
	for i := 0; i < flakiness; i++ {
		// Re-prepare test directory between each attempt so they can't accidentally contaminate each other.
		if err = prepareTestDir(state.Graph, target); err != nil {
			state.LogBuildError(tid, target.Label, core.TargetTestFailed, err, "Failed to prepare test directory for %s: %s", target.Label, err)
			return []byte{}, err, i
		}
		out, err = runPossiblyContainerisedTest(state, target)
		if err == nil {
			return out, err, i
		} else if i < flakiness-1 {
			log.Warning("%s failed on attempt %d (%d more to go).", target.Label, i+1, flakiness-i-1)
		}
	}
	if target.Flakiness == 0 {
		flakiness = 0
	} // Reset this again so non-flaky targets don't appear so.
	return out, err, flakiness
}

// Parses the coverage output for a single target.
func parseCoverageFile(target *core.BuildTarget, coverageFile string) core.TestCoverage {
	coverage, err := parseTestCoverage(target, coverageFile)
	if err != nil {
		log.Errorf("Failed to parse coverage file for %s: %s", target.Label, err)
	}
	return coverage
}

// RemoveCachedTestFiles removes any cached test or coverage result files for a target.
func RemoveCachedTestFiles(target *core.BuildTarget) error {
	if err := removeAnyFilesWithPrefix(target.OutDir(), ".test_results_"+target.Label.Name); err != nil {
		return err
	}
	if err := removeAnyFilesWithPrefix(target.OutDir(), ".test_coverage_"+target.Label.Name); err != nil {
		return err
	}
	for _, output := range target.TestOutputs {
		if err := os.RemoveAll(path.Join(target.OutDir(), output)); err != nil {
			return err
		}
	}
	return nil
}

// removeAnyFilesWithPrefix deletes any files in a directory matching a given prefix.
func removeAnyFilesWithPrefix(dir, prefix string) error {
	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, info := range infos {
		if strings.HasPrefix(info.Name(), prefix) {
			if err := os.RemoveAll(path.Join(dir, info.Name())); err != nil {
				return err
			}
		}
	}
	return nil
}

// Attempt to write a dummy coverage file to record that it's been done for a test.
func moveAndCacheOutputFile(state *core.BuildState, target *core.BuildTarget, hash []byte, from, to, filename, dummy string) error {
	if !core.PathExists(from) {
		if dummy == "" {
			return nil
		}
		if err := ioutil.WriteFile(to, []byte(dummy), 0644); err != nil {
			return err
		}
	} else if err := os.Rename(from, to); err != nil {
		return err
	}
	if state.Cache != nil {
		(*state.Cache).StoreExtra(target, hash, filename)
	}
	return nil
}

// calcNumRuns works out how many total runs we should have for a test, and how many successes
// are required for it to count as success.
func calcNumRuns(numRuns, flakiness int) (int, int) {
	if numRuns > 0 && flakiness > 0 { // If flag is passed we run exactly that many times with proportionate flakiness.
		return numRuns, int(math.Ceil(float64(numRuns) * (1.0 / float64(flakiness))))
	} else if numRuns > 0 {
		return numRuns, numRuns
	} else if flakiness > 0 { // Test is flaky, run that many times
		return flakiness, 1
	}
	return 1, 1
}
<commit_msg>cleaning up other logic<commit_after>package test

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"

	"github.com/op/go-logging"

	"build"
	"core"
)

var log = logging.MustGetLogger("test")

const dummyOutput = "=== RUN DummyTest\n--- PASS: DummyTest (0.00s)\nPASS\n"
const dummyCoverage = "<?xml version=\"1.0\" ?><coverage></coverage>"

func Test(tid int, state *core.BuildState, label core.BuildLabel) {
	state.LogBuildResult(tid, label, core.TargetTesting, "Testing...")
	startTime := time.Now()
	target := state.Graph.TargetOrDie(label)
	hash, err := build.RuntimeHash(state, target)
	if err != nil {
		state.LogBuildError(tid, label, core.TargetTestFailed, err, "Failed to calculate target hash")
		return
	}
	// Check the cached output files if the target wasn't rebuilt.
	hashStr := base64.RawURLEncoding.EncodeToString(core.CollapseHash(hash))
	resultsFileName := fmt.Sprintf(".test_results_%s_%s", label.Name, hashStr)
	coverageFileName := fmt.Sprintf(".test_coverage_%s_%s", label.Name, hashStr)
	outputFile := path.Join(target.TestDir(), "test.results")
	coverageFile := path.Join(target.TestDir(), "test.coverage")
	cachedOutputFile := path.Join(target.OutDir(), resultsFileName)
	cachedCoverageFile := path.Join(target.OutDir(), coverageFileName)
	needCoverage := state.NeedCoverage && !target.NoTestOutput

	cachedTest := func() {
		log.Debug("Not re-running test %s; got cached results.", label)
		coverage := parseCoverageFile(target, cachedCoverageFile)
		results, err := parseTestResults(target, cachedOutputFile, 0, true)
		target.Results.Duration = time.Since(startTime).Seconds()
		target.Results.Cached = true
		if err != nil {
			state.LogBuildError(tid, label, core.TargetTestFailed, err, "Failed to parse cached test file %s", cachedOutputFile)
		} else if results.Failed > 0 {
			panic("Test results with failures shouldn't be cached.")
		} else {
			logTestSuccess(state, tid, label, results, coverage)
		}
	}

	moveAndCacheOutputFiles := func(results core.TestResults, coverage core.TestCoverage) bool {
		// Never cache test results when given arguments; the results may be incomplete.
		if len(state.TestArgs) > 0 {
			log.Debug("Not caching results for %s, we passed it arguments", label)
			return true
		}
		if err := moveAndCacheOutputFile(state, target, hash, outputFile, cachedOutputFile, resultsFileName, dummyOutput); err != nil {
			state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err, "Failed to move test output file")
			return false
		}
		if needCoverage || core.PathExists(coverageFile) {
			if err := moveAndCacheOutputFile(state, target, hash, coverageFile, cachedCoverageFile, coverageFileName, dummyCoverage); err != nil {
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err, "Failed to move test coverage file")
				return false
			}
		}
		for _, output := range target.TestOutputs {
			tmpFile := path.Join(target.TestDir(), output)
			outFile := path.Join(target.OutDir(), output)
			if err := moveAndCacheOutputFile(state, target, hash, tmpFile, outFile, output, ""); err != nil {
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err, "Failed to move test output file")
				return false
			}
		}
		return true
	}

	needToRun := func() bool {
		if target.State() == core.Unchanged && core.PathExists(cachedOutputFile) {
			// Output file exists already and appears to be valid. We might still need to rerun though
			// if the coverage files aren't available.
			if needCoverage && !core.PathExists(cachedCoverageFile) {
				return true
			}
			return false
		}
		// Check the cache for these artifacts.
		if state.Cache == nil {
			return true
		}
		cache := *state.Cache
		if !cache.RetrieveExtra(target, hash, resultsFileName) {
			return true
		}
		if needCoverage && !cache.RetrieveExtra(target, hash, coverageFileName) {
			return true
		}
		for _, output := range target.TestOutputs {
			if !cache.RetrieveExtra(target, hash, output) {
				return true
			}
		}
		return false
	}

	// Don't cache when doing multiple runs, presumably the user explicitly wants to check it.
	if state.NumTestRuns <= 1 && !needToRun() {
		cachedTest()
		return
	}
	// Remove any cached test result file.
	if err := RemoveCachedTestFiles(target); err != nil {
		state.LogBuildError(tid, label, core.TargetTestFailed, err, "Failed to remove cached test files")
		return
	}
	numSucceeded := 0
	numRuns, successesRequired := calcNumRuns(state.NumTestRuns, target.Flakiness)
	for i := 0; i < numRuns && numSucceeded < successesRequired; i++ {
		if numRuns > 1 {
			state.LogBuildResult(tid, label, core.TargetTesting, fmt.Sprintf("Testing (%d of %d)...", i, numRuns))
		}
		out, err := prepareAndRunTest(tid, state, target)
		duration := time.Since(startTime).Seconds()
		startTime = time.Now() // reset this for next time

		// This is all pretty involved; there are lots of different possibilities of what could happen.
		// The contract is that the test must return zero on success or non-zero on failure (Unix FTW).
		// If it's successful, it must produce a parseable file named "test.results" in its temp folder.
		// (alternatively, this can be a directory containing parseable files).
		// Tests can opt out of the file requirement individually, in which case they're judged only
		// by their return value.
		// But of course, we still have to consider all the alternatives here and handle them nicely.
		target.Results.Output = string(out)
		if err != nil && target.Results.Output == "" {
			target.Results.Output = err.Error()
		}
		if err != nil {
			_, target.Results.TimedOut = err.(core.TimeoutError)
		}
		coverage := parseCoverageFile(target, coverageFile)
		if !core.PathExists(outputFile) {
			target.Results.Duration += duration
			if err == nil && target.NoTestOutput {
				results := core.TestResults{NumTests: 1, Passed: 1, Flakes: flakes}
				if moveAndCacheOutputFiles(results, coverage) {
					target.Results.NumTests = 1
					target.Results.Passed = 1
					logTestSuccess(state, tid, label, results, coverage)
				}
			} else if err == nil {
				target.Results.NumTests++
				target.Results.Failed++
				err = fmt.Errorf("Test failed to produce output results file")
				state.LogBuildError(tid, label, core.TargetTestFailed, err,
					"Test apparently succeeded but failed to produce %s. Output: %s", outputFile, string(out))
			} else {
				target.Results.NumTests++
				target.Results.Failed++
				state.LogBuildError(tid, label, core.TargetTestFailed, err,
					fmt.Sprintf("Test failed with no results. Output: %s", string(out)))
			}
		} else {
			results, err2 := parseTestResults(target, outputFile, flakes, false)
			target.Results.Duration += duration
			if err2 != nil {
				state.LogBuildError(tid, label, core.TargetTestFailed, err2,
					"Couldn't parse test output file: %s. Stdout: %s", err2, string(out))
			} else if err != nil && results.Failed == 0 {
				// Add a failure result to the test so it shows up in the final aggregation.
				results.Failed = 1
				results.Failures = append(results.Failures, core.TestFailure{
					Name:   "Return value",
					Type:   fmt.Sprintf("%s", err),
					Stdout: string(out),
				})
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err,
					"Test returned nonzero but reported no errors: %s. Output: %s", err, string(out))
			} else if err == nil && results.Failed != 0 {
				err = fmt.Errorf("Test returned 0 but still reported failures")
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err,
					"Test returned 0 but still reported failures. Stdout: %s", string(out))
			} else if results.Failed != 0 {
				err = fmt.Errorf("Tests failed")
				state.LogTestResult(tid, label, core.TargetTestFailed, results, coverage, err,
					fmt.Sprintf("Tests failed. Stdout: %s", string(out)))
			} else {
				logTestSuccess(state, tid, label, results, coverage)
				numSucceeded++
				// Cache only on the last run.
				if numSucceeded >= successesRequired {
					moveAndCacheOutputFiles(results, coverage)
				}
				// Clean up the test directory.
				if state.CleanWorkdirs {
					if err := os.RemoveAll(target.TestDir()); err != nil {
						log.Warning("Failed to remove test directory for %s: %s", target.Label, err)
					}
				}
			}
		}
	}
}

func logTestSuccess(state *core.BuildState, tid int, label core.BuildLabel, results core.TestResults, coverage core.TestCoverage) {
	var description string
	tests := pluralise("test", results.NumTests)
	if results.Skipped != 0 || results.ExpectedFailures != 0 {
		failures := pluralise("failure", results.ExpectedFailures)
		description = fmt.Sprintf("%d %s passed. %d skipped, %d expected %s",
			results.NumTests, tests, results.Skipped, results.ExpectedFailures, failures)
	} else {
		description = fmt.Sprintf("%d %s passed.", results.NumTests, tests)
	}
	state.LogTestResult(tid, label, core.TargetTested, results, coverage, nil, description)
}

func pluralise(word string, quantity int) string {
	if quantity == 1 {
		return word
	}
	return word + "s"
}

func prepareTestDir(graph *core.BuildGraph, target *core.BuildTarget) error {
	if err := os.RemoveAll(target.TestDir()); err != nil {
		return err
	}
	if err := os.MkdirAll(target.TestDir(), core.DirPermissions); err != nil {
		return err
	}
	for out := range core.IterRuntimeFiles(graph, target, true) {
		if err := core.PrepareSourcePair(out); err != nil {
			return err
		}
	}
	return nil
}

func runTest(state *core.BuildState, target *core.BuildTarget, timeout int) ([]byte, error) {
	replacedCmd := build.ReplaceTestSequences(target, target.TestCommand)
	env := core.BuildEnvironment(state, target, true)
	if len(state.TestArgs) > 0 {
		args := strings.Join(state.TestArgs, " ")
		replacedCmd += " " + args
		env = append(env, "TESTS="+args)
	}
	cmd := exec.Command("bash", "-c", replacedCmd)
	cmd.Dir = target.TestDir()
	cmd.Env = env
	log.Debug("Running test %s\nENVIRONMENT:\n%s\n%s", target.Label, strings.Join(cmd.Env, "\n"), replacedCmd)
	if state.PrintCommands {
		log.Notice("Running test %s: %s", target.Label, replacedCmd)
	}
	return core.ExecWithTimeout(cmd, target.TestTimeout, timeout)
}

// prepareAndRunTest sets up a test directory and runs the test.
func prepareAndRunTest(tid int, state *core.BuildState, target *core.BuildTarget) (out []byte, err error) {
	if err = prepareTestDir(state.Graph, target); err != nil {
		state.LogBuildError(tid, target.Label, core.TargetTestFailed, err, "Failed to prepare test directory for %s: %s", target.Label, err)
		return []byte{}, err
	}
	return runPossiblyContainerisedTest(state, target)
}

// Parses the coverage output for a single target.
func parseCoverageFile(target *core.BuildTarget, coverageFile string) core.TestCoverage {
	coverage, err := parseTestCoverage(target, coverageFile)
	if err != nil {
		log.Errorf("Failed to parse coverage file for %s: %s", target.Label, err)
	}
	return coverage
}

// RemoveCachedTestFiles removes any cached test or coverage result files for a target.
func RemoveCachedTestFiles(target *core.BuildTarget) error {
	if err := removeAnyFilesWithPrefix(target.OutDir(), ".test_results_"+target.Label.Name); err != nil {
		return err
	}
	if err := removeAnyFilesWithPrefix(target.OutDir(), ".test_coverage_"+target.Label.Name); err != nil {
		return err
	}
	for _, output := range target.TestOutputs {
		if err := os.RemoveAll(path.Join(target.OutDir(), output)); err != nil {
			return err
		}
	}
	return nil
}

// removeAnyFilesWithPrefix deletes any files in a directory matching a given prefix.
func removeAnyFilesWithPrefix(dir, prefix string) error {
	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, info := range infos {
		if strings.HasPrefix(info.Name(), prefix) {
			if err := os.RemoveAll(path.Join(dir, info.Name())); err != nil {
				return err
			}
		}
	}
	return nil
}

// Attempt to write a dummy coverage file to record that it's been done for a test.
func moveAndCacheOutputFile(state *core.BuildState, target *core.BuildTarget, hash []byte, from, to, filename, dummy string) error {
	if !core.PathExists(from) {
		if dummy == "" {
			return nil
		}
		if err := ioutil.WriteFile(to, []byte(dummy), 0644); err != nil {
			return err
		}
	} else if err := os.Rename(from, to); err != nil {
		return err
	}
	if state.Cache != nil {
		(*state.Cache).StoreExtra(target, hash, filename)
	}
	return nil
}

// calcNumRuns works out how many total runs we should have for a test, and how many successes
// are required for it to count as success.
func calcNumRuns(numRuns, flakiness int) (int, int) {
	if numRuns > 0 && flakiness > 0 { // If flag is passed we run exactly that many times with proportionate flakiness.
		return numRuns, int(math.Ceil(float64(numRuns) * (1.0 / float64(flakiness))))
	} else if numRuns > 0 {
		return numRuns, numRuns
	} else if flakiness > 0 { // Test is flaky, run that many times
		return flakiness, 1
	}
	return 1, 1
}
<|endoftext|>


<commit_before>package ed2k

import (
	"bytes"
	"testing"
)

func TestSmallFile(t *testing.T) {
	test := bytes.NewReader(make([]byte, 600))
	validHash := "a5b489c18c5bdc1f711a8edff22c13ff"
	testHash, _ := Hash(test, false)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}

func TestSmallFileOld(t *testing.T) {
	test := bytes.NewReader(make([]byte, 600))
	validHash := "a5b489c18c5bdc1f711a8edff22c13ff"
	testHash, _ := Hash(test, true)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}

func TestEqualFile(t *testing.T) {
	test := bytes.NewReader(make([]byte, 9728000))
	validHash := "d7def262a127cd79096a108e7a9fc138"
	testHash, _ := Hash(test, false)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}

func TestEqualFileOld(t *testing.T) {
	test := bytes.NewReader(make([]byte, 9728000))
	validHash := "fc21d9af828f92a8df64beac3357425d"
	testHash, _ := Hash(test, true)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}

func TestMultipleFile(t *testing.T) {
	test := bytes.NewReader(make([]byte, 19456000))
	validHash := "194ee9e4fa79b2ee9f8829284c466051"
	testHash, _ := Hash(test, false)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}

func TestMultipleFileOld(t *testing.T) {
	test := bytes.NewReader(make([]byte, 19456000))
	validHash := "114b21c63a74b6ca922291a11177dd5c"
	testHash, _ := Hash(test, true)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}

func TestLargeFile(t *testing.T) {
	test := bytes.NewReader(make([]byte, 19457000))
	validHash := "345da2ffa0f63eae5638b908f187bfb1"
	testHash, _ := Hash(test, false)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}

func TestLargeFileOld(t *testing.T) {
	test := bytes.NewReader(make([]byte, 19457000))
	validHash := "345da2ffa0f63eae5638b908f187bfb1"
	testHash, _ := Hash(test, true)
	if testHash != validHash {
		t.Error("Expected ", validHash, " got ", testHash)
	}
}
<commit_msg>Wrap repeated code in a function<commit_after>package ed2k

import (
	"bytes"
	"testing"
)

func HashWrapper(t *testing.T, size int, hash string, old bool) {
	test := bytes.NewReader(make([]byte, size))
	testHash, err := Hash(test, old)

	if err != nil {
		t.Error("Got error ", err)
	}

	if testHash != hash {
		t.Error("Expected ", hash, " got ", testHash)
	}
}

func TestSmallFile(t *testing.T) {
	HashWrapper(t, 600, "a5b489c18c5bdc1f711a8edff22c13ff", false)
}

func TestSmallFileOld(t *testing.T) {
	HashWrapper(t, 600, "a5b489c18c5bdc1f711a8edff22c13ff", true)
}

func TestEqualFile(t *testing.T) {
	HashWrapper(t, 9728000, "d7def262a127cd79096a108e7a9fc138", false)
}

func TestEqualFileOld(t *testing.T) {
	HashWrapper(t, 9728000, "fc21d9af828f92a8df64beac3357425d", true)
}

func TestMultipleFile(t *testing.T) {
	HashWrapper(t, 19456000, "194ee9e4fa79b2ee9f8829284c466051", false)
}

func TestMultipleFileOld(t *testing.T) {
	HashWrapper(t, 19456000, "114b21c63a74b6ca922291a11177dd5c", true)
}

func TestLargeFile(t *testing.T) {
	HashWrapper(t, 19457000, "345da2ffa0f63eae5638b908f187bfb1", false)
}

func TestLargeFileOld(t *testing.T) {
	HashWrapper(t, 19457000, "345da2ffa0f63eae5638b908f187bfb1", true)
}
<|endoftext|>


<commit_before>// Copyright 2018 Google Inc.
//
// 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.

// +build !windows

package renameio

import (
	"io/ioutil"
	"os"
	"path/filepath"
)

// TempDir checks whether os.TempDir() can be used as a temporary directory for
// later atomically replacing files within dest. If no (os.TempDir() resides on
// a different mount point), dest is returned.
//
// Note that the returned value ceases to be valid once either os.TempDir()
// changes (e.g. on Linux, once the TMPDIR environment variable changes) or the
// file system is unmounted.
func TempDir(dest string) string {
	return tempDir("", filepath.Join(dest, "renameio-TempDir"))
}

func tempDir(dir, dest string) string {
	if dir != "" {
		return dir // caller-specified directory always wins
	}

	// Chose the destination directory as temporary directory so that we
	// definitely can rename the file, for which both temporary and destination
	// file need to point to the same mount point.
	fallback := filepath.Dir(dest)

	// The user might have overridden the os.TempDir() return value by setting
	// the TMPDIR environment variable.
	tmpdir := os.TempDir()

	testsrc, err := ioutil.TempFile(tmpdir, "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	cleanup := true
	defer func() {
		if cleanup {
			os.Remove(testsrc.Name())
		}
	}()
	testsrc.Close()

	testdest, err := ioutil.TempFile(filepath.Dir(dest), "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	defer os.Remove(testdest.Name())
	testdest.Close()

	if err := os.Rename(testsrc.Name(), testdest.Name()); err != nil {
		return fallback
	}
	cleanup = false // testsrc no longer exists
	return tmpdir
}

// PendingFile is a pending temporary file, waiting to replace the destination
// path in a call to CloseAtomicallyReplace.
type PendingFile struct {
	*os.File

	path   string
	done   bool
	closed bool
}

// Cleanup is a no-op if CloseAtomicallyReplace succeeded, and otherwise closes
// and removes the temporary file.
func (t *PendingFile) Cleanup() error {
	if t.done {
		return nil
	}
	// An error occurred. Close and remove the tempfile. Errors are returned for
	// reporting, there is nothing the caller can recover here.
	var closeErr error
	if !t.closed {
		closeErr = t.Close()
	}
	if err := os.Remove(t.Name()); err != nil {
		return err
	}
	return closeErr
}

// CloseAtomicallyReplace closes the temporary file and atomically replaces
// the destination file with it, i.e., a concurrent open(2) call will either
// open the file previously located at the destination path (if any), or the
// just written file, but the file will always be present.
func (t *PendingFile) CloseAtomicallyReplace() error {
	// Even on an ordered file system (e.g. ext4 with data=ordered) or file
	// systems with write barriers, we cannot skip the fsync(2) call as per
	// Theodore Ts'o (ext2/3/4 lead developer):
	//
	// > data=ordered only guarantees the avoidance of stale data (e.g., the previous
	// > contents of a data block showing up after a crash, where the previous data
	// > could be someone's love letters, medical records, etc.). Without the fsync(2)
	// > a zero-length file is a valid and possible outcome after the rename.
	if err := t.Sync(); err != nil {
		return err
	}
	t.closed = true
	if err := t.Close(); err != nil {
		return err
	}
	if err := os.Rename(t.Name(), t.path); err != nil {
		return err
	}
	t.done = true
	return nil
}

// TempFile wraps ioutil.TempFile for the use case of atomically creating or
// replacing the destination file at path.
//
// If dir is the empty string, TempDir(filepath.Base(path)) is used. If you are
// going to write a large number of files to the same file system, store the
// result of TempDir(filepath.Base(path)) and pass it instead of the empty
// string.
//
// The file's permissions will be 0600 by default. You can change these by
// explicitly calling Chmod on the returned PendingFile.
func TempFile(dir, path string) (*PendingFile, error) {
	f, err := ioutil.TempFile(tempDir(dir, path), "."+filepath.Base(path))
	if err != nil {
		return nil, err
	}

	return &PendingFile{File: f, path: path}, nil
}

// Symlink wraps os.Symlink, replacing an existing symlink with the same name
// atomically (os.Symlink fails when newname already exists, at least on Linux).
func Symlink(oldname, newname string) error {
	// Fast path: if newname does not exist yet, we can skip the whole dance
	// below.
	if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) {
		return err
	}

	// We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile,
	// and removing+symlinking creates a TOCTOU race.
	d, err := ioutil.TempDir(filepath.Dir(newname), "."+filepath.Base(newname))
	if err != nil {
		return err
	}
	cleanup := true
	defer func() {
		if cleanup {
			os.RemoveAll(d)
		}
	}()

	symlink := filepath.Join(d, "tmp.symlink")
	if err := os.Symlink(oldname, symlink); err != nil {
		return err
	}

	if err := os.Rename(symlink, newname); err != nil {
		return err
	}

	cleanup = false
	return os.RemoveAll(d)
}
<commit_msg>TempFile: document methods not concurrency-safe (#28)<commit_after>// Copyright 2018 Google Inc.
//
// 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.

// +build !windows

package renameio

import (
	"io/ioutil"
	"os"
	"path/filepath"
)

// TempDir checks whether os.TempDir() can be used as a temporary directory for
// later atomically replacing files within dest. If no (os.TempDir() resides on
// a different mount point), dest is returned.
//
// Note that the returned value ceases to be valid once either os.TempDir()
// changes (e.g. on Linux, once the TMPDIR environment variable changes) or the
// file system is unmounted.
func TempDir(dest string) string {
	return tempDir("", filepath.Join(dest, "renameio-TempDir"))
}

func tempDir(dir, dest string) string {
	if dir != "" {
		return dir // caller-specified directory always wins
	}

	// Chose the destination directory as temporary directory so that we
	// definitely can rename the file, for which both temporary and destination
	// file need to point to the same mount point.
	fallback := filepath.Dir(dest)

	// The user might have overridden the os.TempDir() return value by setting
	// the TMPDIR environment variable.
	tmpdir := os.TempDir()

	testsrc, err := ioutil.TempFile(tmpdir, "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	cleanup := true
	defer func() {
		if cleanup {
			os.Remove(testsrc.Name())
		}
	}()
	testsrc.Close()

	testdest, err := ioutil.TempFile(filepath.Dir(dest), "."+filepath.Base(dest))
	if err != nil {
		return fallback
	}
	defer os.Remove(testdest.Name())
	testdest.Close()

	if err := os.Rename(testsrc.Name(), testdest.Name()); err != nil {
		return fallback
	}
	cleanup = false // testsrc no longer exists
	return tmpdir
}

// PendingFile is a pending temporary file, waiting to replace the destination
// path in a call to CloseAtomicallyReplace.
type PendingFile struct {
	*os.File

	path   string
	done   bool
	closed bool
}

// Cleanup is a no-op if CloseAtomicallyReplace succeeded, and otherwise closes
// and removes the temporary file.
//
// This method is not safe for concurrent use by multiple goroutines.
func (t *PendingFile) Cleanup() error {
	if t.done {
		return nil
	}
	// An error occurred. Close and remove the tempfile. Errors are returned for
	// reporting, there is nothing the caller can recover here.
	var closeErr error
	if !t.closed {
		closeErr = t.Close()
	}
	if err := os.Remove(t.Name()); err != nil {
		return err
	}
	return closeErr
}

// CloseAtomicallyReplace closes the temporary file and atomically replaces
// the destination file with it, i.e., a concurrent open(2) call will either
// open the file previously located at the destination path (if any), or the
// just written file, but the file will always be present.
//
// This method is not safe for concurrent use by multiple goroutines.
func (t *PendingFile) CloseAtomicallyReplace() error {
	// Even on an ordered file system (e.g. ext4 with data=ordered) or file
	// systems with write barriers, we cannot skip the fsync(2) call as per
	// Theodore Ts'o (ext2/3/4 lead developer):
	//
	// > data=ordered only guarantees the avoidance of stale data (e.g., the previous
	// > contents of a data block showing up after a crash, where the previous data
	// > could be someone's love letters, medical records, etc.). Without the fsync(2)
	// > a zero-length file is a valid and possible outcome after the rename.
	if err := t.Sync(); err != nil {
		return err
	}
	t.closed = true
	if err := t.Close(); err != nil {
		return err
	}
	if err := os.Rename(t.Name(), t.path); err != nil {
		return err
	}
	t.done = true
	return nil
}

// TempFile wraps ioutil.TempFile for the use case of atomically creating or
// replacing the destination file at path.
//
// If dir is the empty string, TempDir(filepath.Base(path)) is used. If you are
// going to write a large number of files to the same file system, store the
// result of TempDir(filepath.Base(path)) and pass it instead of the empty
// string.
//
// The file's permissions will be 0600 by default. You can change these by
// explicitly calling Chmod on the returned PendingFile.
func TempFile(dir, path string) (*PendingFile, error) {
	f, err := ioutil.TempFile(tempDir(dir, path), "."+filepath.Base(path))
	if err != nil {
		return nil, err
	}

	return &PendingFile{File: f, path: path}, nil
}

// Symlink wraps os.Symlink, replacing an existing symlink with the same name
// atomically (os.Symlink fails when newname already exists, at least on Linux).
func Symlink(oldname, newname string) error {
	// Fast path: if newname does not exist yet, we can skip the whole dance
	// below.
	if err := os.Symlink(oldname, newname); err == nil || !os.IsExist(err) {
		return err
	}

	// We need to use ioutil.TempDir, as we cannot overwrite a ioutil.TempFile,
	// and removing+symlinking creates a TOCTOU race.
	d, err := ioutil.TempDir(filepath.Dir(newname), "."+filepath.Base(newname))
	if err != nil {
		return err
	}
	cleanup := true
	defer func() {
		if cleanup {
			os.RemoveAll(d)
		}
	}()

	symlink := filepath.Join(d, "tmp.symlink")
	if err := os.Symlink(oldname, symlink); err != nil {
		return err
	}

	if err := os.Rename(symlink, newname); err != nil {
		return err
	}

	cleanup = false
	return os.RemoveAll(d)
}
<|endoftext|>


<commit_before>package color

import (
	"io"
	"os"

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

// IsTerminal returns true if f is a terminal and false otherwise.
func IsTerminal(f *os.File) bool {
	return terminal.IsTerminal(int(f.Fd()))
}

// IsTerminalWriter returns true if w is a terminal and false otherwise.
func IsTerminalWriter(w io.Writer) bool {
	if f, ok := w.(*os.File); ok {
		return terminal.IsTerminal(int(f.Fd()))
	}
	return false
}
<commit_msg>removed IsTerminalWriter<commit_after>package color

import (
	"io"
	"os"

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

// IsTerminal returns true if f is a terminal and false otherwise.
func IsTerminal(f *os.File) bool {
	return terminal.IsTerminal(int(f.Fd()))
}
<|endoftext|>