<commit_before>package rbg2p

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/dlclark/regexp2"
)

// Context in which the rule applies (left hand/right hand context specified by a regular expression)
type Context struct {
	// Input is the regexp as written in the input string
	Input string

	// Regexp is the input string converted to a regular expression for internal use (with variables expanded, and adapted anchoring)
	Regexp *regexp2.Regexp
}

// Matches checks if the input string matches the context rule
func (c Context) Matches(s string) (bool, error) {
	if c.IsDefined() {
		res, err := c.Regexp.MatchString(s)
		if err != nil {
			return false, err
		}
		return res, nil
	}
	return true, nil
}

// IsDefined returns true if the contained regexp is defined
func (c Context) IsDefined() bool {
	return (nil != c.Regexp)
}

// String returns a string representation of the Context
func (c Context) String() string {
	if c.IsDefined() {
		return c.Input
	}
	return ""
}

// equals checks for equality (including underlying regexps); used for unit tests
func (c Context) equals(c2 Context) bool {
	if c.IsDefined() && !c2.IsDefined() {
		return false
	} else if c2.IsDefined() && !c.IsDefined() {
		return false
	} else if !c2.IsDefined() && !c.IsDefined() {
		return true
	}
	return c.Input == c2.Input
}

// Filter is a regexp filter for rules that cannot be expressed using the standard rule systme
type Filter struct {
	Regexp *regexp2.Regexp
	Output string
}

// Apply is used to apply the filter to an input string
func (f Filter) Apply(s string) (string, error) {
	//return f.Regexp.ReplaceAllString(s, f.Output)
	return f.Regexp.Replace(s, f.Output, -1, -1)
}

// Rule is a g2p rule representation
type Rule struct {
	Input        string
	Output       []string
	LeftContext  Context
	RightContext Context
}

// String returns a string representation of the Context
func (r Rule) String() string {
	return fmt.Sprintf("%s -> %s / %s _ %s", r.Input, r.Output, r.LeftContext, r.RightContext)
}

// equals: checks for equality (including underlying slices and regexps); used for unit tests
func (r Rule) equals(r2 Rule) bool {
	return r.Input == r2.Input &&
		reflect.DeepEqual(r.Output, r2.Output) &&
		r.LeftContext.equals(r2.LeftContext) &&
		r.RightContext.equals(r2.RightContext)
}

// equalsExceptOutput: checks for equality except for output (including underlying slices and regexps); used for unit tests
func (r Rule) equalsExceptOutput(r2 Rule) bool {
	return r.Input == r2.Input &&
		r.LeftContext.equals(r2.LeftContext) &&
		r.RightContext.equals(r2.RightContext)
}

// Test defines a rule test (input -> output)
type Test struct {
	Input  string
	Output []string
}

// equals checks for equality (including underlying slices); used for unit tests
func (t1 Test) equals(t2 Test) bool {
	return t1.Input == t2.Input && reflect.DeepEqual(t1.Output, t2.Output)
}

// RuleSet is a set of g2p rules, with variables and built-in tests
type RuleSet struct {
	CharacterSet      []string
	PhonemeSet        PhonemeSet
	PhonemeDelimiter  string
	SyllableDelimiter string
	DefaultPhoneme    string
	Vars              map[string]string
	Rules             []Rule
	Tests             []Test
	Filters           []Filter
	Syllabifier       Syllabifier
	Content           string
}

func (rs RuleSet) checkForUnusedChars(coveredChars map[string]bool, individualChars map[string]bool, validation *TestResult) {
	var errors = []string{}
	for _, char := range rs.CharacterSet {
		if _, ok := individualChars[char]; !ok {
			errors = append(errors, char)
		}
	}
	if len(errors) > 0 {
		validation.Errors = append(validation.Errors, fmt.Sprintf("no default rule for character(s): %s", strings.Join(errors, ",")))
	}
}

func (rs RuleSet) hasPhonemeSet() bool {
	return len(rs.PhonemeSet.Symbols) > 0
}

// Test runs the built-in tests. Returns a test result with errors and warnings, if any.
func (rs RuleSet) Test() TestResult {
	var result = TestResult{}
	var coveredChars = map[string]bool{}
	var individualChars = map[string]bool{}
	for _, rule := range rs.Rules {
		for _, char := range strings.Split(rule.Input, "") {
			coveredChars[char] = true
		}
		coveredChars[rule.Input] = true
		if !rule.LeftContext.IsDefined() && !rule.RightContext.IsDefined() {
			individualChars[rule.Input] = true
		}
	}
	rs.checkForUnusedChars(coveredChars, individualChars, &result)

	if rs.hasPhonemeSet() {
		validation, err := compareToPhonemeSet(rs)
		if err != nil {
			result.Errors = append(result.Errors, fmt.Sprintf("%v", err))
		}
		for _, w := range validation.Warnings {
			result.Warnings = append(result.Warnings, w)
		}
		for _, e := range validation.Errors {
			result.Errors = append(result.Errors, e)
		}
	}

	for _, test := range rs.Tests {
		input := test.Input
		expect := test.Output
		res0, err := rs.Apply(strings.ToLower(input))
		res := []string{}
		if err != nil {
			result.Errors = append(result.Errors, fmt.Sprintf("%v", err))
		}
		for _, trans := range res0 {
			res = append(res, trans)
		}
		delim := rs.PhonemeDelimiter
		if !reflect.DeepEqual(expect, res) {
			result.FailedTests = append(result.FailedTests, fmt.Sprintf("for '%s', expected /%s/, got /%s/", input, strings.Join(expect, delim), strings.Join(res, "/ + /")))
		}
	}
	return result
}

func (rs RuleSet) expandLoop(head g2p, tail []g2p, acc []trans) []trans {
	res := []trans{}
	for i := 0; i < len(acc); i++ {
		for _, add := range head.p {
			appendRange := []g2p{}
			// build prefix from previous rounds
			for _, g2p := range acc[i].phonemes {
				appendRange = append(appendRange, g2p)
			}
			// append current phonemes
			g2p := g2p{g: head.g, p: strings.Split(add, rs.PhonemeDelimiter)}
			appendRange = append(appendRange, g2p)
			res = append(res, trans{phonemes: appendRange})
		}

	}
	if len(tail) == 0 {
		return res
	}
	return rs.expandLoop(tail[0], tail[1:], res)
}

func (rs RuleSet) expand(phonemes []g2p) []trans {
	return rs.expandLoop(phonemes[0], phonemes[1:], []trans{{}})
}

func (rs RuleSet) applyFilters(trans string) (string, error) {
	res := trans
	var err error
	for _, f := range rs.Filters {
		res, err = f.Apply(res)
		if err != nil {
			return res, fmt.Errorf("couldn't execute regexp : %s", err)
		}
	}
	return res, nil
}

// Apply applies the rules to an input string, returns a slice of transcriptions. If unknown input characters are found, an error will be created, and an underscore will be appended to the transcription. Even if an error is returned, the loop will continue until the end of the input string.
func (rs RuleSet) Apply(s string) ([]string, error) {
	var i = 0
	var s0 = []rune(s)
	res := []g2p{}
	var couldntMap = []string{}
	for i < len(s0) {
		ss := string(s0[i:])
		thisChar := string(s0[i : i+1])
		left := string(s0[0:i])
		var matchFound = false
		for _, rule := range rs.Rules {
			leftMatch, err := rule.LeftContext.Matches(left)
			if err != nil {
				return []string{}, fmt.Errorf("couldn't execute regexp /%s/ : %s", rule.LeftContext.Regexp, err)
			}
			if strings.HasPrefix(ss, rule.Input) &&
				leftMatch {
				ruleInputLen := len([]rune(rule.Input))
				right := string(s0[i+ruleInputLen:])
				rightMatch, err := rule.RightContext.Matches(right)
				if err != nil {
					return []string{}, fmt.Errorf("couldn't execute regexp /%s/ : %s", rule.RightContext.Regexp, err)
				}
				if rightMatch {
					i = i + ruleInputLen
					res = append(res, g2p{g: rule.Input, p: rule.Output})
					matchFound = true
					break
				}
			}
		}
		if !matchFound {
			res = append(res, g2p{g: thisChar, p: []string{rs.DefaultPhoneme}})
			i = i + 1
			couldntMap = append(couldntMap, thisChar)
		}
	}
	expanded := rs.expand(res)

	transes := []string{}
	for _, t := range expanded {
		if rs.Syllabifier.IsDefined() {
			s := rs.Syllabifier.syllabify(t)
			transes = append(transes, s.string(rs.PhonemeDelimiter, rs.SyllableDelimiter))
		} else {
			transes = append(transes, t.string(rs.PhonemeDelimiter))
		}
	}
	var filtered []string
	for _, t := range transes {
		fted, err := rs.applyFilters(t)
		if err != nil {
			return filtered, err
		}
		filtered = append(filtered, fted)
	}
	if len(couldntMap) > 0 {
		return filtered, fmt.Errorf("Found unmappable symbol(s) in input string: %v in %s", couldntMap, s)
	}
	return filtered, nil
}

// compareToPhonemeSet validates the phonemes in the g2p rule set against the specified phonemeset. Returns an array of invalid phonemes, if any; or if errors are found, this is returned instead.
func compareToPhonemeSet(ruleSet RuleSet) (TestResult, error) {
	var validation = TestResult{}
	var usedSymbols = map[string]bool{}
	for _, rule := range ruleSet.Rules {
		for _, output := range rule.Output {
			invalid, err := ruleSet.PhonemeSet.validate(output)
			if err != nil {
				return TestResult{}, fmt.Errorf("found error in rule output /%s/ : %s", output, err)
			}
			splitted, err := ruleSet.PhonemeSet.SplitTranscription(output)
			if err != nil {
				return TestResult{}, err
			}
			for _, symbol := range splitted {
				usedSymbols[symbol] = true
			}

			for _, symbol := range invalid {
				validation.Errors = append(validation.Errors, fmt.Sprintf("invalid symbol in rule output %s: %s", rule, symbol))
			}
		}
	}
	for _, test := range ruleSet.Tests {
		for _, output := range test.Output {
			invalid, err := ruleSet.PhonemeSet.validate(output)
			if err != nil {
				return TestResult{}, fmt.Errorf("found error in test output /%s/ : %s", output, err)
			}
			splitted, err := ruleSet.PhonemeSet.SplitTranscription(output)
			if err != nil {
				return TestResult{}, err
			}
			for _, symbol := range splitted {
				usedSymbols[symbol] = true
			}
			for _, symbol := range invalid {
				validation.Errors = append(validation.Errors, fmt.Sprintf("invalid symbol in test output %s: %s", test, symbol))
			}

		}
	}
	for _, warn := range checkForUnusedSymbols(usedSymbols, ruleSet.PhonemeSet) {
		validation.Warnings = append(validation.Warnings, warn)
	}
	return validation, nil
}
<commit_msg>improved output from failing tests<commit_after>package rbg2p

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/dlclark/regexp2"
)

// Context in which the rule applies (left hand/right hand context specified by a regular expression)
type Context struct {
	// Input is the regexp as written in the input string
	Input string

	// Regexp is the input string converted to a regular expression for internal use (with variables expanded, and adapted anchoring)
	Regexp *regexp2.Regexp
}

// Matches checks if the input string matches the context rule
func (c Context) Matches(s string) (bool, error) {
	if c.IsDefined() {
		res, err := c.Regexp.MatchString(s)
		if err != nil {
			return false, err
		}
		return res, nil
	}
	return true, nil
}

// IsDefined returns true if the contained regexp is defined
func (c Context) IsDefined() bool {
	return (nil != c.Regexp)
}

// String returns a string representation of the Context
func (c Context) String() string {
	if c.IsDefined() {
		return c.Input
	}
	return ""
}

// equals checks for equality (including underlying regexps); used for unit tests
func (c Context) equals(c2 Context) bool {
	if c.IsDefined() && !c2.IsDefined() {
		return false
	} else if c2.IsDefined() && !c.IsDefined() {
		return false
	} else if !c2.IsDefined() && !c.IsDefined() {
		return true
	}
	return c.Input == c2.Input
}

// Filter is a regexp filter for rules that cannot be expressed using the standard rule systme
type Filter struct {
	Regexp *regexp2.Regexp
	Output string
}

// Apply is used to apply the filter to an input string
func (f Filter) Apply(s string) (string, error) {
	//return f.Regexp.ReplaceAllString(s, f.Output)
	return f.Regexp.Replace(s, f.Output, -1, -1)
}

// Rule is a g2p rule representation
type Rule struct {
	Input        string
	Output       []string
	LeftContext  Context
	RightContext Context
}

// String returns a string representation of the Context
func (r Rule) String() string {
	return fmt.Sprintf("%s -> %s / %s _ %s", r.Input, r.Output, r.LeftContext, r.RightContext)
}

// equals: checks for equality (including underlying slices and regexps); used for unit tests
func (r Rule) equals(r2 Rule) bool {
	return r.Input == r2.Input &&
		reflect.DeepEqual(r.Output, r2.Output) &&
		r.LeftContext.equals(r2.LeftContext) &&
		r.RightContext.equals(r2.RightContext)
}

// equalsExceptOutput: checks for equality except for output (including underlying slices and regexps); used for unit tests
func (r Rule) equalsExceptOutput(r2 Rule) bool {
	return r.Input == r2.Input &&
		r.LeftContext.equals(r2.LeftContext) &&
		r.RightContext.equals(r2.RightContext)
}

// Test defines a rule test (input -> output)
type Test struct {
	Input  string
	Output []string
}

// equals checks for equality (including underlying slices); used for unit tests
func (t1 Test) equals(t2 Test) bool {
	return t1.Input == t2.Input && reflect.DeepEqual(t1.Output, t2.Output)
}

// RuleSet is a set of g2p rules, with variables and built-in tests
type RuleSet struct {
	CharacterSet      []string
	PhonemeSet        PhonemeSet
	PhonemeDelimiter  string
	SyllableDelimiter string
	DefaultPhoneme    string
	Vars              map[string]string
	Rules             []Rule
	Tests             []Test
	Filters           []Filter
	Syllabifier       Syllabifier
	Content           string
}

func (rs RuleSet) checkForUnusedChars(coveredChars map[string]bool, individualChars map[string]bool, validation *TestResult) {
	var errors = []string{}
	for _, char := range rs.CharacterSet {
		if _, ok := individualChars[char]; !ok {
			errors = append(errors, char)
		}
	}
	if len(errors) > 0 {
		validation.Errors = append(validation.Errors, fmt.Sprintf("no default rule for character(s): %s", strings.Join(errors, ",")))
	}
}

func (rs RuleSet) hasPhonemeSet() bool {
	return len(rs.PhonemeSet.Symbols) > 0
}

// Test runs the built-in tests. Returns a test result with errors and warnings, if any.
func (rs RuleSet) Test() TestResult {
	var result = TestResult{}
	var coveredChars = map[string]bool{}
	var individualChars = map[string]bool{}
	for _, rule := range rs.Rules {
		for _, char := range strings.Split(rule.Input, "") {
			coveredChars[char] = true
		}
		coveredChars[rule.Input] = true
		if !rule.LeftContext.IsDefined() && !rule.RightContext.IsDefined() {
			individualChars[rule.Input] = true
		}
	}
	rs.checkForUnusedChars(coveredChars, individualChars, &result)

	if rs.hasPhonemeSet() {
		validation, err := compareToPhonemeSet(rs)
		if err != nil {
			result.Errors = append(result.Errors, fmt.Sprintf("%v", err))
		}
		for _, w := range validation.Warnings {
			result.Warnings = append(result.Warnings, w)
		}
		for _, e := range validation.Errors {
			result.Errors = append(result.Errors, e)
		}
	}

	for _, test := range rs.Tests {
		input := test.Input
		expect := test.Output
		res0, err := rs.Apply(strings.ToLower(input))
		res := []string{}
		if err != nil {
			result.Errors = append(result.Errors, fmt.Sprintf("%v", err))
		}
		for _, trans := range res0 {
			res = append(res, trans)
		}
		//delim := rs.PhonemeDelimiter
		if !reflect.DeepEqual(expect, res) {
			result.FailedTests = append(result.FailedTests, fmt.Sprintf("for '%s', expected /%s/, got /%s/", input, strings.Join(expect, "/ + /"), strings.Join(res, "/ + /")))
		}
	}
	return result
}

func (rs RuleSet) expandLoop(head g2p, tail []g2p, acc []trans) []trans {
	res := []trans{}
	for i := 0; i < len(acc); i++ {
		for _, add := range head.p {
			appendRange := []g2p{}
			// build prefix from previous rounds
			for _, g2p := range acc[i].phonemes {
				appendRange = append(appendRange, g2p)
			}
			// append current phonemes
			g2p := g2p{g: head.g, p: strings.Split(add, rs.PhonemeDelimiter)}
			appendRange = append(appendRange, g2p)
			res = append(res, trans{phonemes: appendRange})
		}

	}
	if len(tail) == 0 {
		return res
	}
	return rs.expandLoop(tail[0], tail[1:], res)
}

func (rs RuleSet) expand(phonemes []g2p) []trans {
	return rs.expandLoop(phonemes[0], phonemes[1:], []trans{{}})
}

func (rs RuleSet) applyFilters(trans string) (string, error) {
	res := trans
	var err error
	for _, f := range rs.Filters {
		res, err = f.Apply(res)
		if err != nil {
			return res, fmt.Errorf("couldn't execute regexp : %s", err)
		}
	}
	return res, nil
}

// Apply applies the rules to an input string, returns a slice of transcriptions. If unknown input characters are found, an error will be created, and an underscore will be appended to the transcription. Even if an error is returned, the loop will continue until the end of the input string.
func (rs RuleSet) Apply(s string) ([]string, error) {
	var i = 0
	var s0 = []rune(s)
	res := []g2p{}
	var couldntMap = []string{}
	for i < len(s0) {
		ss := string(s0[i:])
		thisChar := string(s0[i : i+1])
		left := string(s0[0:i])
		var matchFound = false
		for _, rule := range rs.Rules {
			leftMatch, err := rule.LeftContext.Matches(left)
			if err != nil {
				return []string{}, fmt.Errorf("couldn't execute regexp /%s/ : %s", rule.LeftContext.Regexp, err)
			}
			if strings.HasPrefix(ss, rule.Input) &&
				leftMatch {
				ruleInputLen := len([]rune(rule.Input))
				right := string(s0[i+ruleInputLen:])
				rightMatch, err := rule.RightContext.Matches(right)
				if err != nil {
					return []string{}, fmt.Errorf("couldn't execute regexp /%s/ : %s", rule.RightContext.Regexp, err)
				}
				if rightMatch {
					i = i + ruleInputLen
					res = append(res, g2p{g: rule.Input, p: rule.Output})
					matchFound = true
					break
				}
			}
		}
		if !matchFound {
			res = append(res, g2p{g: thisChar, p: []string{rs.DefaultPhoneme}})
			i = i + 1
			couldntMap = append(couldntMap, thisChar)
		}
	}
	expanded := rs.expand(res)

	transes := []string{}
	for _, t := range expanded {
		if rs.Syllabifier.IsDefined() {
			s := rs.Syllabifier.syllabify(t)
			transes = append(transes, s.string(rs.PhonemeDelimiter, rs.SyllableDelimiter))
		} else {
			transes = append(transes, t.string(rs.PhonemeDelimiter))
		}
	}
	var filtered []string
	for _, t := range transes {
		fted, err := rs.applyFilters(t)
		if err != nil {
			return filtered, err
		}
		filtered = append(filtered, fted)
	}
	if len(couldntMap) > 0 {
		return filtered, fmt.Errorf("Found unmappable symbol(s) in input string: %v in %s", couldntMap, s)
	}
	return filtered, nil
}

// compareToPhonemeSet validates the phonemes in the g2p rule set against the specified phonemeset. Returns an array of invalid phonemes, if any; or if errors are found, this is returned instead.
func compareToPhonemeSet(ruleSet RuleSet) (TestResult, error) {
	var validation = TestResult{}
	var usedSymbols = map[string]bool{}
	for _, rule := range ruleSet.Rules {
		for _, output := range rule.Output {
			invalid, err := ruleSet.PhonemeSet.validate(output)
			if err != nil {
				return TestResult{}, fmt.Errorf("found error in rule output /%s/ : %s", output, err)
			}
			splitted, err := ruleSet.PhonemeSet.SplitTranscription(output)
			if err != nil {
				return TestResult{}, err
			}
			for _, symbol := range splitted {
				usedSymbols[symbol] = true
			}

			for _, symbol := range invalid {
				validation.Errors = append(validation.Errors, fmt.Sprintf("invalid symbol in rule output %s: %s", rule, symbol))
			}
		}
	}
	for _, test := range ruleSet.Tests {
		for _, output := range test.Output {
			invalid, err := ruleSet.PhonemeSet.validate(output)
			if err != nil {
				return TestResult{}, fmt.Errorf("found error in test output /%s/ : %s", output, err)
			}
			splitted, err := ruleSet.PhonemeSet.SplitTranscription(output)
			if err != nil {
				return TestResult{}, err
			}
			for _, symbol := range splitted {
				usedSymbols[symbol] = true
			}
			for _, symbol := range invalid {
				validation.Errors = append(validation.Errors, fmt.Sprintf("invalid symbol in test output %s: %s", test, symbol))
			}

		}
	}
	for _, warn := range checkForUnusedSymbols(usedSymbols, ruleSet.PhonemeSet) {
		validation.Warnings = append(validation.Warnings, warn)
	}
	return validation, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 linux

// Provides Filesystem Stats
package fs

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"

	"github.com/docker/docker/pkg/mount"
	"github.com/golang/glog"
)

var partitionRegex = regexp.MustCompile("^(:?(:?s|xv)d[a-z]+\\d*|dm-\\d+)$")

const (
	LabelSystemRoot   = "root"
	LabelDockerImages = "docker-images"
)

type partition struct {
	mountpoint string
	major      uint
	minor      uint
	fsType     string
	blockSize  uint
}

type RealFsInfo struct {
	// Map from block device path to partition information.
	partitions map[string]partition
	// Map from label to block device path.
	// Labels are intent-specific tags that are auto-detected.
	labels map[string]string
}

type Context struct {
	// docker root directory.
	DockerRoot string
	DockerInfo map[string]string
}

func NewFsInfo(context Context) (FsInfo, error) {
	mounts, err := mount.GetMounts()
	if err != nil {
		return nil, err
	}
	partitions := make(map[string]partition, 0)
	fsInfo := &RealFsInfo{}
	fsInfo.labels = make(map[string]string, 0)
	supportedFsType := map[string]bool{
		// all ext systems are checked through prefix.
		"btrfs": true,
		"xfs":   true,
	}
	for _, mount := range mounts {
		if !strings.HasPrefix(mount.Fstype, "ext") && !supportedFsType[mount.Fstype] {
			continue
		}
		// Avoid bind mounts.
		if _, ok := partitions[mount.Source]; ok {
			continue
		}
		partitions[mount.Source] = partition{
			mountpoint: mount.Mountpoint,
			major:      uint(mount.Major),
			minor:      uint(mount.Minor),
		}
	}
	if storageDriver, ok := context.DockerInfo["Driver"]; ok && storageDriver == "devicemapper" {
		dev, major, minor, blockSize, err := dockerDMDevice(context.DockerInfo["DriverStatus"])
		if err != nil {
			glog.Warningf("Could not get Docker devicemapper device: %v", err)
		} else {
			partitions[dev] = partition{
				fsType:    "devicemapper",
				major:     major,
				minor:     minor,
				blockSize: blockSize,
			}
			fsInfo.labels[LabelDockerImages] = dev
		}
	}
	glog.Infof("Filesystem partitions: %+v", partitions)
	fsInfo.partitions = partitions
	fsInfo.addLabels(context)
	return fsInfo, nil
}

func (self *RealFsInfo) addLabels(context Context) {
	dockerPaths := getDockerImagePaths(context)
	for src, p := range self.partitions {
		if p.mountpoint == "/" {
			if _, ok := self.labels[LabelSystemRoot]; !ok {
				self.labels[LabelSystemRoot] = src
			}
		}
		self.updateDockerImagesPath(src, p.mountpoint, dockerPaths)
		// TODO(rjnagal): Add label for docker devicemapper pool.
	}
}

// Generate a list of possible mount points for docker image management from the docker root directory.
// Right now, we look for each type of supported graph driver directories, but we can do better by parsing
// some of the context from `docker info`.
func getDockerImagePaths(context Context) []string {
	// TODO(rjnagal): Detect docker root and graphdriver directories from docker info.
	dockerRoot := context.DockerRoot
	dockerImagePaths := []string{}
	for _, dir := range []string{"devicemapper", "btrfs", "aufs"} {
		dockerImagePaths = append(dockerImagePaths, path.Join(dockerRoot, dir))
	}
	for dockerRoot != "/" && dockerRoot != "." {
		dockerImagePaths = append(dockerImagePaths, dockerRoot)
		dockerRoot = filepath.Dir(dockerRoot)
	}
	dockerImagePaths = append(dockerImagePaths, "/")
	return dockerImagePaths
}

// This method compares the mountpoint with possible docker image mount points. If a match is found,
// docker images label is added to the partition.
func (self *RealFsInfo) updateDockerImagesPath(source string, mountpoint string, dockerImagePaths []string) {
	for _, v := range dockerImagePaths {
		if v == mountpoint {
			if i, ok := self.labels[LabelDockerImages]; ok {
				// pick the innermost mountpoint.
				mnt := self.partitions[i].mountpoint
				if len(mnt) < len(mountpoint) {
					self.labels[LabelDockerImages] = source
				}
			} else {
				self.labels[LabelDockerImages] = source
			}
		}
	}
}

func (self *RealFsInfo) GetDeviceForLabel(label string) (string, error) {
	dev, ok := self.labels[label]
	if !ok {
		return "", fmt.Errorf("non-existent label %q", label)
	}
	return dev, nil
}

func (self *RealFsInfo) GetLabelsForDevice(device string) ([]string, error) {
	labels := []string{}
	for label, dev := range self.labels {
		if dev == device {
			labels = append(labels, label)
		}
	}
	return labels, nil
}

func (self *RealFsInfo) GetMountpointForDevice(dev string) (string, error) {
	p, ok := self.partitions[dev]
	if !ok {
		return "", fmt.Errorf("no partition info for device %q", dev)
	}
	return p.mountpoint, nil
}

func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) {
	filesystems := make([]Fs, 0)
	deviceSet := make(map[string]struct{})
	diskStatsMap, err := getDiskStatsMap("/proc/diskstats")
	if err != nil {
		return nil, err
	}
	for device, partition := range self.partitions {
		_, hasMount := mountSet[partition.mountpoint]
		_, hasDevice := deviceSet[device]
		if mountSet == nil || (hasMount && !hasDevice) {
			var (
				total, free, avail uint64
				err                error
			)
			switch partition.fsType {
			case "devicemapper":
				total, free, avail, err = getDMStats(device, partition.blockSize)
			default:
				total, free, avail, err = getVfsStats(partition.mountpoint)
			}
			if err != nil {
				glog.Errorf("Stat fs failed. Error: %v", err)
			} else {
				deviceSet[device] = struct{}{}
				deviceInfo := DeviceInfo{
					Device: device,
					Major:  uint(partition.major),
					Minor:  uint(partition.minor),
				}
				fs := Fs{deviceInfo, total, free, avail, diskStatsMap[device]}
				filesystems = append(filesystems, fs)
			}
		}
	}
	return filesystems, nil
}

func getDiskStatsMap(diskStatsFile string) (map[string]DiskStats, error) {
	diskStatsMap := make(map[string]DiskStats)
	file, err := os.Open(diskStatsFile)
	if err != nil {
		if os.IsNotExist(err) {
			glog.Infof("not collecting filesystem statistics because file %q was not available", diskStatsFile)
			return diskStatsMap, nil
		}
		return nil, err
	}

	defer file.Close()
	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
		line := scanner.Text()
		words := strings.Fields(line)
		if !partitionRegex.MatchString(words[2]) {
			continue
		}
		// 8      50 sdd2 40 0 280 223 7 0 22 108 0 330 330
		deviceName := path.Join("/dev", words[2])
		wordLength := len(words)
		offset := 3
		var stats = make([]uint64, wordLength-offset)
		if len(stats) < 11 {
			return nil, fmt.Errorf("could not parse all 11 columns of /proc/diskstats")
		}
		var error error
		for i := offset; i < wordLength; i++ {
			stats[i-offset], error = strconv.ParseUint(words[i], 10, 64)
			if error != nil {
				return nil, error
			}
		}
		diskStats := DiskStats{
			ReadsCompleted:  stats[0],
			ReadsMerged:     stats[1],
			SectorsRead:     stats[2],
			ReadTime:        stats[3],
			WritesCompleted: stats[4],
			WritesMerged:    stats[5],
			SectorsWritten:  stats[6],
			WriteTime:       stats[7],
			IoInProgress:    stats[8],
			IoTime:          stats[9],
			WeightedIoTime:  stats[10],
		}
		diskStatsMap[deviceName] = diskStats
	}
	return diskStatsMap, nil
}

func (self *RealFsInfo) GetGlobalFsInfo() ([]Fs, error) {
	return self.GetFsInfoForPath(nil)
}

func major(devNumber uint64) uint {
	return uint((devNumber >> 8) & 0xfff)
}

func minor(devNumber uint64) uint {
	return uint((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00))
}

func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) {
	buf := new(syscall.Stat_t)
	err := syscall.Stat(dir, buf)
	if err != nil {
		return nil, fmt.Errorf("stat failed on %s with error: %s", dir, err)
	}
	major := major(buf.Dev)
	minor := minor(buf.Dev)
	for device, partition := range self.partitions {
		if partition.major == major && partition.minor == minor {
			return &DeviceInfo{device, major, minor}, nil
		}
	}
	return nil, fmt.Errorf("could not find device with major: %d, minor: %d in cached partitions map", major, minor)
}

func (self *RealFsInfo) GetDirUsage(dir string) (uint64, error) {
	out, err := exec.Command("nice", "-n", "19", "du", "-s", dir).CombinedOutput()
	if err != nil {
		return 0, fmt.Errorf("du command failed on %s with output %s - %s", dir, out, err)
	}
	usageInKb, err := strconv.ParseUint(strings.Fields(string(out))[0], 10, 64)
	if err != nil {
		return 0, fmt.Errorf("cannot parse 'du' output %s - %s", out, err)
	}
	return usageInKb * 1024, nil
}

func getVfsStats(path string) (uint64, uint64, uint64, error) {
	var s syscall.Statfs_t
	if err := syscall.Statfs(path, &s); err != nil {
		return 0, 0, 0, err
	}
	total := uint64(s.Frsize) * s.Blocks
	free := uint64(s.Frsize) * s.Bfree
	avail := uint64(s.Frsize) * s.Bavail
	return total, free, avail, nil
}

func dockerStatusValue(status [][]string, target string) string {
	for _, v := range status {
		if len(v) == 2 && strings.ToLower(v[0]) == strings.ToLower(target) {
			return v[1]
		}
	}
	return ""
}

func dockerDMDevice(driverStatus string) (string, uint, uint, uint, error) {
	var config [][]string
	err := json.Unmarshal([]byte(driverStatus), &config)
	if err != nil {
		return "", 0, 0, 0, err
	}
	poolName := dockerStatusValue(config, "Pool Name")
	if len(poolName) == 0 {
		return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name")
	}

	out, err := exec.Command("dmsetup", "table", poolName).Output()
	if err != nil {
		return "", 0, 0, 0, err
	}

	dmTable := string(out)
	dmTable = strings.Replace(dmTable, ":", " ", -1)
	dmFields := strings.Fields(dmTable)

	major, err := strconv.ParseUint(dmFields[5], 10, 32)
	if err != nil {
		return "", 0, 0, 0, err
	}
	minor, err := strconv.ParseUint(dmFields[6], 10, 32)
	if err != nil {
		return "", 0, 0, 0, err
	}
	dataBlkSize, err := strconv.ParseUint(dmFields[7], 10, 32)
	if err != nil {
		return "", 0, 0, 0, err
	}

	return poolName, uint(major), uint(minor), uint(dataBlkSize), nil
}

func getDMStats(poolName string, dataBlkSize uint) (uint64, uint64, uint64, error) {
	out, err := exec.Command("dmsetup", "status", poolName).Output()
	if err != nil {
		return 0, 0, 0, err
	}

	dmStatus := string(out)
	dmStatus = strings.Replace(dmStatus, "/", " ", -1)
	dmFields := strings.Fields(dmStatus)

	used, err := strconv.ParseUint(dmFields[6], 10, 64)
	if err != nil {
		return 0, 0, 0, err
	}
	total, err := strconv.ParseUint(dmFields[7], 10, 64)
	if err != nil {
		return 0, 0, 0, err
	}

	total *= 512 * uint64(dataBlkSize)
	used *= 512 * uint64(dataBlkSize)
	free := total - used

	return total, free, free, nil
}
<commit_msg>Refactor DM fs parsing<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 linux

// Provides Filesystem Stats
package fs

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"

	"github.com/docker/docker/pkg/mount"
	"github.com/golang/glog"
)

var partitionRegex = regexp.MustCompile("^(:?(:?s|xv)d[a-z]+\\d*|dm-\\d+)$")

const (
	LabelSystemRoot   = "root"
	LabelDockerImages = "docker-images"
)

type partition struct {
	mountpoint string
	major      uint
	minor      uint
	fsType     string
	blockSize  uint
}

type RealFsInfo struct {
	// Map from block device path to partition information.
	partitions map[string]partition
	// Map from label to block device path.
	// Labels are intent-specific tags that are auto-detected.
	labels map[string]string
}

type Context struct {
	// docker root directory.
	DockerRoot string
	DockerInfo map[string]string
}

func NewFsInfo(context Context) (FsInfo, error) {
	mounts, err := mount.GetMounts()
	if err != nil {
		return nil, err
	}
	partitions := make(map[string]partition, 0)
	fsInfo := &RealFsInfo{}
	fsInfo.labels = make(map[string]string, 0)
	supportedFsType := map[string]bool{
		// all ext systems are checked through prefix.
		"btrfs": true,
		"xfs":   true,
	}
	for _, mount := range mounts {
		if !strings.HasPrefix(mount.Fstype, "ext") && !supportedFsType[mount.Fstype] {
			continue
		}
		// Avoid bind mounts.
		if _, ok := partitions[mount.Source]; ok {
			continue
		}
		partitions[mount.Source] = partition{
			mountpoint: mount.Mountpoint,
			major:      uint(mount.Major),
			minor:      uint(mount.Minor),
		}
	}
	if storageDriver, ok := context.DockerInfo["Driver"]; ok && storageDriver == "devicemapper" {
		dev, major, minor, blockSize, err := dockerDMDevice(context.DockerInfo["DriverStatus"])
		if err != nil {
			glog.Warningf("Could not get Docker devicemapper device: %v", err)
		} else {
			partitions[dev] = partition{
				fsType:    "devicemapper",
				major:     major,
				minor:     minor,
				blockSize: blockSize,
			}
			fsInfo.labels[LabelDockerImages] = dev
		}
	}
	glog.Infof("Filesystem partitions: %+v", partitions)
	fsInfo.partitions = partitions
	fsInfo.addLabels(context)
	return fsInfo, nil
}

func (self *RealFsInfo) addLabels(context Context) {
	dockerPaths := getDockerImagePaths(context)
	for src, p := range self.partitions {
		if p.mountpoint == "/" {
			if _, ok := self.labels[LabelSystemRoot]; !ok {
				self.labels[LabelSystemRoot] = src
			}
		}
		self.updateDockerImagesPath(src, p.mountpoint, dockerPaths)
		// TODO(rjnagal): Add label for docker devicemapper pool.
	}
}

// Generate a list of possible mount points for docker image management from the docker root directory.
// Right now, we look for each type of supported graph driver directories, but we can do better by parsing
// some of the context from `docker info`.
func getDockerImagePaths(context Context) []string {
	// TODO(rjnagal): Detect docker root and graphdriver directories from docker info.
	dockerRoot := context.DockerRoot
	dockerImagePaths := []string{}
	for _, dir := range []string{"devicemapper", "btrfs", "aufs"} {
		dockerImagePaths = append(dockerImagePaths, path.Join(dockerRoot, dir))
	}
	for dockerRoot != "/" && dockerRoot != "." {
		dockerImagePaths = append(dockerImagePaths, dockerRoot)
		dockerRoot = filepath.Dir(dockerRoot)
	}
	dockerImagePaths = append(dockerImagePaths, "/")
	return dockerImagePaths
}

// This method compares the mountpoint with possible docker image mount points. If a match is found,
// docker images label is added to the partition.
func (self *RealFsInfo) updateDockerImagesPath(source string, mountpoint string, dockerImagePaths []string) {
	for _, v := range dockerImagePaths {
		if v == mountpoint {
			if i, ok := self.labels[LabelDockerImages]; ok {
				// pick the innermost mountpoint.
				mnt := self.partitions[i].mountpoint
				if len(mnt) < len(mountpoint) {
					self.labels[LabelDockerImages] = source
				}
			} else {
				self.labels[LabelDockerImages] = source
			}
		}
	}
}

func (self *RealFsInfo) GetDeviceForLabel(label string) (string, error) {
	dev, ok := self.labels[label]
	if !ok {
		return "", fmt.Errorf("non-existent label %q", label)
	}
	return dev, nil
}

func (self *RealFsInfo) GetLabelsForDevice(device string) ([]string, error) {
	labels := []string{}
	for label, dev := range self.labels {
		if dev == device {
			labels = append(labels, label)
		}
	}
	return labels, nil
}

func (self *RealFsInfo) GetMountpointForDevice(dev string) (string, error) {
	p, ok := self.partitions[dev]
	if !ok {
		return "", fmt.Errorf("no partition info for device %q", dev)
	}
	return p.mountpoint, nil
}

func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) {
	filesystems := make([]Fs, 0)
	deviceSet := make(map[string]struct{})
	diskStatsMap, err := getDiskStatsMap("/proc/diskstats")
	if err != nil {
		return nil, err
	}
	for device, partition := range self.partitions {
		_, hasMount := mountSet[partition.mountpoint]
		_, hasDevice := deviceSet[device]
		if mountSet == nil || (hasMount && !hasDevice) {
			var (
				total, free, avail uint64
				err                error
			)
			switch partition.fsType {
			case "devicemapper":
				total, free, avail, err = getDMStats(device, partition.blockSize)
			default:
				total, free, avail, err = getVfsStats(partition.mountpoint)
			}
			if err != nil {
				glog.Errorf("Stat fs failed. Error: %v", err)
			} else {
				deviceSet[device] = struct{}{}
				deviceInfo := DeviceInfo{
					Device: device,
					Major:  uint(partition.major),
					Minor:  uint(partition.minor),
				}
				fs := Fs{deviceInfo, total, free, avail, diskStatsMap[device]}
				filesystems = append(filesystems, fs)
			}
		}
	}
	return filesystems, nil
}

func getDiskStatsMap(diskStatsFile string) (map[string]DiskStats, error) {
	diskStatsMap := make(map[string]DiskStats)
	file, err := os.Open(diskStatsFile)
	if err != nil {
		if os.IsNotExist(err) {
			glog.Infof("not collecting filesystem statistics because file %q was not available", diskStatsFile)
			return diskStatsMap, nil
		}
		return nil, err
	}

	defer file.Close()
	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
		line := scanner.Text()
		words := strings.Fields(line)
		if !partitionRegex.MatchString(words[2]) {
			continue
		}
		// 8      50 sdd2 40 0 280 223 7 0 22 108 0 330 330
		deviceName := path.Join("/dev", words[2])
		wordLength := len(words)
		offset := 3
		var stats = make([]uint64, wordLength-offset)
		if len(stats) < 11 {
			return nil, fmt.Errorf("could not parse all 11 columns of /proc/diskstats")
		}
		var error error
		for i := offset; i < wordLength; i++ {
			stats[i-offset], error = strconv.ParseUint(words[i], 10, 64)
			if error != nil {
				return nil, error
			}
		}
		diskStats := DiskStats{
			ReadsCompleted:  stats[0],
			ReadsMerged:     stats[1],
			SectorsRead:     stats[2],
			ReadTime:        stats[3],
			WritesCompleted: stats[4],
			WritesMerged:    stats[5],
			SectorsWritten:  stats[6],
			WriteTime:       stats[7],
			IoInProgress:    stats[8],
			IoTime:          stats[9],
			WeightedIoTime:  stats[10],
		}
		diskStatsMap[deviceName] = diskStats
	}
	return diskStatsMap, nil
}

func (self *RealFsInfo) GetGlobalFsInfo() ([]Fs, error) {
	return self.GetFsInfoForPath(nil)
}

func major(devNumber uint64) uint {
	return uint((devNumber >> 8) & 0xfff)
}

func minor(devNumber uint64) uint {
	return uint((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00))
}

func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) {
	buf := new(syscall.Stat_t)
	err := syscall.Stat(dir, buf)
	if err != nil {
		return nil, fmt.Errorf("stat failed on %s with error: %s", dir, err)
	}
	major := major(buf.Dev)
	minor := minor(buf.Dev)
	for device, partition := range self.partitions {
		if partition.major == major && partition.minor == minor {
			return &DeviceInfo{device, major, minor}, nil
		}
	}
	return nil, fmt.Errorf("could not find device with major: %d, minor: %d in cached partitions map", major, minor)
}

func (self *RealFsInfo) GetDirUsage(dir string) (uint64, error) {
	out, err := exec.Command("nice", "-n", "19", "du", "-s", dir).CombinedOutput()
	if err != nil {
		return 0, fmt.Errorf("du command failed on %s with output %s - %s", dir, out, err)
	}
	usageInKb, err := strconv.ParseUint(strings.Fields(string(out))[0], 10, 64)
	if err != nil {
		return 0, fmt.Errorf("cannot parse 'du' output %s - %s", out, err)
	}
	return usageInKb * 1024, nil
}

func getVfsStats(path string) (uint64, uint64, uint64, error) {
	var s syscall.Statfs_t
	if err := syscall.Statfs(path, &s); err != nil {
		return 0, 0, 0, err
	}
	total := uint64(s.Frsize) * s.Blocks
	free := uint64(s.Frsize) * s.Bfree
	avail := uint64(s.Frsize) * s.Bavail
	return total, free, avail, nil
}

func dockerStatusValue(status [][]string, target string) string {
	for _, v := range status {
		if len(v) == 2 && strings.ToLower(v[0]) == strings.ToLower(target) {
			return v[1]
		}
	}
	return ""
}

func dockerDMDevice(driverStatus string) (string, uint, uint, uint, error) {
	var config [][]string
	err := json.Unmarshal([]byte(driverStatus), &config)
	if err != nil {
		return "", 0, 0, 0, err
	}
	poolName := dockerStatusValue(config, "Pool Name")
	if len(poolName) == 0 {
		return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name")
	}

	out, err := exec.Command("dmsetup", "table", poolName).Output()
	if err != nil {
		return "", 0, 0, 0, err
	}

	major, minor, dataBlkSize, err := parseDMTable(string(out))
	if err != nil {
		return "", 0, 0, 0, err
	}

	return poolName, major, minor, dataBlkSize, nil
}

func parseDMTable(dmTable string) (uint, uint, uint, error) {
	dmTable = strings.Replace(dmTable, ":", " ", -1)
	dmFields := strings.Fields(dmTable)

	if len(dmFields) < 8 {
		return 0, 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmTable)
	}

	major, err := strconv.ParseUint(dmFields[5], 10, 32)
	if err != nil {
		return 0, 0, 0, err
	}
	minor, err := strconv.ParseUint(dmFields[6], 10, 32)
	if err != nil {
		return 0, 0, 0, err
	}
	dataBlkSize, err := strconv.ParseUint(dmFields[7], 10, 32)
	if err != nil {
		return 0, 0, 0, err
	}

	return uint(major), uint(minor), uint(dataBlkSize), nil
}

func getDMStats(poolName string, dataBlkSize uint) (uint64, uint64, uint64, error) {
	out, err := exec.Command("dmsetup", "status", poolName).Output()
	if err != nil {
		return 0, 0, 0, err
	}

	used, total, err := parseDMStatus(string(out))
	if err != nil {
		return 0, 0, 0, err
	}

	used *= 512 * uint64(dataBlkSize)
	total *= 512 * uint64(dataBlkSize)
	free := total - used

	return total, free, free, nil
}

func parseDMStatus(dmStatus string) (uint64, uint64, error) {
	dmStatus = strings.Replace(dmStatus, "/", " ", -1)
	dmFields := strings.Fields(dmStatus)

	if len(dmFields) < 8 {
		return 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmStatus)
	}

	used, err := strconv.ParseUint(dmFields[6], 10, 64)
	if err != nil {
		return 0, 0, err
	}
	total, err := strconv.ParseUint(dmFields[7], 10, 64)
	if err != nil {
		return 0, 0, err
	}

	return used, total, nil
}
<|endoftext|>


<commit_before>// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package viper

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/kr/pretty"
	"github.com/spf13/cast"
	jww "github.com/spf13/jwalterweatherman"
	"gopkg.in/yaml.v1"
)

// A set of paths to look for the config file in
var configPaths []string

// Name of file to look for inside the path
var configName string = "config"

// extensions Supported
var SupportedExts []string = []string{"json", "toml", "yaml"}
var configFile string
var configType string

var config map[string]interface{} = make(map[string]interface{})
var override map[string]interface{} = make(map[string]interface{})
var defaults map[string]interface{} = make(map[string]interface{})
var aliases map[string]string = make(map[string]string)

func SetConfigFile(in string) {
	if in != "" {
		configFile = in
	}
}

func ConfigFileUsed() string {
	return configFile
}

func AddConfigPath(in string) {
	if in != "" {
		absin := absPathify(in)
		jww.INFO.Println("adding", absin, "to paths to search")
		if !stringInSlice(absin, configPaths) {
			configPaths = append(configPaths, absin)
		}
	}
}

func GetString(key string) string {
	return cast.ToString(Get(key))
}

func GetBool(key string) bool {
	return cast.ToBool(Get(key))
}

func GetInt(key string) int {
	return cast.ToInt(Get(key))
}

func GetFloat64(key string) float64 {
	return cast.ToFloat64(Get(key))
}

func GetTime(key string) time.Time {
	return cast.ToTime(Get(key))
}

func GetStringSlice(key string) []string {
	return cast.ToStringSlice(Get(key))
}

func GetStringMap(key string) map[string]interface{} {
	return cast.ToStringMap(Get(key))
}

func GetStringMapString(key string) map[string]string {
	return cast.ToStringMapString(Get(key))
}

func find(key string) interface{} {
	var val interface{}
	var exists bool

	// if the requested key is an alias, then return the proper key
	key = realKey(key)

	val, exists = override[key]
	if exists {
		jww.TRACE.Println(key, "found in override:", val)
		return val
	}

	val, exists = config[key]
	if exists {
		jww.TRACE.Println(key, "found in config:", val)
		return val
	}

	val, exists = defaults[key]
	if exists {
		jww.TRACE.Println(key, "found in defaults:", val)
		return val
	}

	return nil
}

func Get(key string) interface{} {
	key = strings.ToLower(key)
	v := find(key)

	if v == nil {
		return nil
	}

	switch v.(type) {
	case bool:
		return cast.ToBool(v)
	case string:
		return cast.ToString(v)
	case int64, int32, int16, int8, int:
		return cast.ToInt(v)
	case float64, float32:
		return cast.ToFloat64(v)
	case time.Time:
		return cast.ToTime(v)
	case []string:
		return v
	}
	return v
}

func IsSet(key string) bool {
	t := Get(key)
	return t != nil
}

func RegisterAlias(alias string, key string) {
	registerAlias(alias, strings.ToLower(key))
}

func registerAlias(alias string, key string) {
	alias = strings.ToLower(alias)
	if alias != key && alias != realKey(key) {
		_, exists := aliases[alias]
		if !exists {
			aliases[alias] = key
		}
	} else {
		jww.WARN.Println("Creating circular reference alias", alias, key, realKey(key))
	}
}

func realKey(key string) string {
	newkey, exists := aliases[key]
	if exists {
		jww.DEBUG.Println("Alias", key, "to", newkey)
		return realKey(newkey)
	} else {
		return key
	}
}

func InConfig(key string) bool {
	// if the requested key is an alias, then return the proper key
	key = realKey(key)

	_, exists := config[key]
	return exists
}

func SetDefault(key string, value interface{}) {
	// If alias passed in, then set the proper default
	key = realKey(strings.ToLower(key))
	defaults[key] = value
}

func Set(key string, value interface{}) {
	// If alias passed in, then set the proper override
	key = realKey(strings.ToLower(key))
	key = strings.ToLower(key)
	override[key] = value
}

func ReadInConfig() {
	jww.INFO.Println("Attempting to read in config file")
	if !stringInSlice(getConfigType(), SupportedExts) {
		jww.ERROR.Fatalf("Unsupported Config Type %q", getConfigType())
	}

	file, err := ioutil.ReadFile(getConfigFile())
	if err == nil {
		MarshallReader(bytes.NewReader(file))
	}
}

func MarshallReader(in io.Reader) {
	buf := new(bytes.Buffer)
	buf.ReadFrom(in)

	switch getConfigType() {
	case "yaml":
		if err := yaml.Unmarshal(buf.Bytes(), &config); err != nil {
			jww.ERROR.Fatalf("Error parsing config: %s", err)
		}

	case "json":
		if err := json.Unmarshal(buf.Bytes(), &config); err != nil {
			jww.ERROR.Fatalf("Error parsing config: %s", err)
		}

	case "toml":
		if _, err := toml.Decode(buf.String(), &config); err != nil {
			jww.ERROR.Fatalf("Error parsing config: %s", err)
		}
	}

	insensativiseMap()
}

func insensativiseMap() {
	for key, _ := range config {
		if key != strings.ToLower(key) {
			registerAlias(key, key)
		}
	}
}

func SetConfigName(in string) {
	if in != "" {
		configName = in
	}
}

func SetConfigType(in string) {
	if in != "" {
		configType = in
	}
}

func getConfigType() string {
	if configType != "" {
		return configType
	}

	cf := getConfigFile()
	ext := path.Ext(cf)

	if len(ext) > 1 {
		return ext[1:]
	} else {
		return ""
	}
}

func getConfigFile() string {
	// if explicitly set, then use it
	if configFile != "" {
		return configFile
	}

	cf, err := findConfigFile()
	if err != nil {
		jww.ERROR.Println(err)
	} else {
		configFile = cf
		return getConfigFile()
	}
	return ""
}

func searchInPath(in string) (filename string) {
	jww.DEBUG.Println("Searching for config in ", in)
	for _, ext := range SupportedExts {

		jww.DEBUG.Println("Checking for", path.Join(in, configName+"."+ext))
		if b, _ := exists(path.Join(in, configName+"."+ext)); b {
			jww.DEBUG.Println("Found: ", path.Join(in, configName+"."+ext))
			return path.Join(in, configName+"."+ext)
		}
	}

	return ""
}

func findConfigFile() (string, error) {
	jww.INFO.Println("Searching for config in ", configPaths)

	for _, cp := range configPaths {
		file := searchInPath(cp)
		if file != "" {
			return file, nil
		}
	}
	cwd, _ := findCWD()

	file := searchInPath(cwd)
	if file != "" {
		return file, nil
	}

	return "", fmt.Errorf("config file not found in: %s", configPaths)
}

func findCWD() (string, error) {
	serverFile, err := filepath.Abs(os.Args[0])

	if err != nil {
		return "", fmt.Errorf("Can't get absolute path for executable: %v", err)
	}

	path := filepath.Dir(serverFile)
	realFile, err := filepath.EvalSymlinks(serverFile)

	if err != nil {
		if _, err = os.Stat(serverFile + ".exe"); err == nil {
			realFile = filepath.Clean(serverFile + ".exe")
		}
	}

	if err == nil && realFile != serverFile {
		path = filepath.Dir(realFile)
	}

	return path, nil
}

// Check if File / Directory Exists
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 stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

func absPathify(inPath string) string {
	jww.INFO.Println("Trying to resolve absolute path to", inPath)
	if filepath.IsAbs(inPath) {
		return filepath.Clean(inPath)
	}

	p, err := filepath.Abs(inPath)
	if err == nil {
		return filepath.Clean(p)
	} else {
		jww.ERROR.Println("Couldn't discover absolute path")
		jww.ERROR.Println(err)
	}
	return ""
}

func Debug() {
	fmt.Println("Config:")
	pretty.Println(config)
	fmt.Println("Defaults:")
	pretty.Println(defaults)
	fmt.Println("Override:")
	pretty.Println(override)
}

func Reset() {
	configPaths = nil
	configName = "config"

	// extensions Supported
	SupportedExts = []string{"json", "toml", "yaml"}
	configFile = ""
	configType = ""

	config = make(map[string]interface{})
	override = make(map[string]interface{})
	defaults = make(map[string]interface{})
	aliases = make(map[string]string)
}
<commit_msg>return more useful error if config file is missing<commit_after>// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package viper

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/kr/pretty"
	"github.com/spf13/cast"
	jww "github.com/spf13/jwalterweatherman"
	"gopkg.in/yaml.v1"
)

// A set of paths to look for the config file in
var configPaths []string

// Name of file to look for inside the path
var configName string = "config"

// extensions Supported
var SupportedExts []string = []string{"json", "toml", "yaml"}
var configFile string
var configType string

var config map[string]interface{} = make(map[string]interface{})
var override map[string]interface{} = make(map[string]interface{})
var defaults map[string]interface{} = make(map[string]interface{})
var aliases map[string]string = make(map[string]string)

func SetConfigFile(in string) {
	if in != "" {
		configFile = in
	}
}

func ConfigFileUsed() string {
	return configFile
}

func AddConfigPath(in string) {
	if in != "" {
		absin := absPathify(in)
		jww.INFO.Println("adding", absin, "to paths to search")
		if !stringInSlice(absin, configPaths) {
			configPaths = append(configPaths, absin)
		}
	}
}

func GetString(key string) string {
	return cast.ToString(Get(key))
}

func GetBool(key string) bool {
	return cast.ToBool(Get(key))
}

func GetInt(key string) int {
	return cast.ToInt(Get(key))
}

func GetFloat64(key string) float64 {
	return cast.ToFloat64(Get(key))
}

func GetTime(key string) time.Time {
	return cast.ToTime(Get(key))
}

func GetStringSlice(key string) []string {
	return cast.ToStringSlice(Get(key))
}

func GetStringMap(key string) map[string]interface{} {
	return cast.ToStringMap(Get(key))
}

func GetStringMapString(key string) map[string]string {
	return cast.ToStringMapString(Get(key))
}

func find(key string) interface{} {
	var val interface{}
	var exists bool

	// if the requested key is an alias, then return the proper key
	key = realKey(key)

	val, exists = override[key]
	if exists {
		jww.TRACE.Println(key, "found in override:", val)
		return val
	}

	val, exists = config[key]
	if exists {
		jww.TRACE.Println(key, "found in config:", val)
		return val
	}

	val, exists = defaults[key]
	if exists {
		jww.TRACE.Println(key, "found in defaults:", val)
		return val
	}

	return nil
}

func Get(key string) interface{} {
	key = strings.ToLower(key)
	v := find(key)

	if v == nil {
		return nil
	}

	switch v.(type) {
	case bool:
		return cast.ToBool(v)
	case string:
		return cast.ToString(v)
	case int64, int32, int16, int8, int:
		return cast.ToInt(v)
	case float64, float32:
		return cast.ToFloat64(v)
	case time.Time:
		return cast.ToTime(v)
	case []string:
		return v
	}
	return v
}

func IsSet(key string) bool {
	t := Get(key)
	return t != nil
}

func RegisterAlias(alias string, key string) {
	registerAlias(alias, strings.ToLower(key))
}

func registerAlias(alias string, key string) {
	alias = strings.ToLower(alias)
	if alias != key && alias != realKey(key) {
		_, exists := aliases[alias]
		if !exists {
			aliases[alias] = key
		}
	} else {
		jww.WARN.Println("Creating circular reference alias", alias, key, realKey(key))
	}
}

func realKey(key string) string {
	newkey, exists := aliases[key]
	if exists {
		jww.DEBUG.Println("Alias", key, "to", newkey)
		return realKey(newkey)
	} else {
		return key
	}
}

func InConfig(key string) bool {
	// if the requested key is an alias, then return the proper key
	key = realKey(key)

	_, exists := config[key]
	return exists
}

func SetDefault(key string, value interface{}) {
	// If alias passed in, then set the proper default
	key = realKey(strings.ToLower(key))
	defaults[key] = value
}

func Set(key string, value interface{}) {
	// If alias passed in, then set the proper override
	key = realKey(strings.ToLower(key))
	key = strings.ToLower(key)
	override[key] = value
}

type UnsupportedConfigError string

func (str UnsupportedConfigError) Error() string {
	return fmt.Sprintf("Unsupported Config Type %q", string(str))
}

func ReadInConfig() error {
	jww.INFO.Println("Attempting to read in config file")
	if !stringInSlice(getConfigType(), SupportedExts) {
		return UnsupportedConfigError(getConfigType())
	}

	file, err := ioutil.ReadFile(getConfigFile())
	if err != nil {
		return err
	}

	MarshallReader(bytes.NewReader(file))
	return nil
}

func MarshallReader(in io.Reader) {
	buf := new(bytes.Buffer)
	buf.ReadFrom(in)

	switch getConfigType() {
	case "yaml":
		if err := yaml.Unmarshal(buf.Bytes(), &config); err != nil {
			jww.ERROR.Fatalf("Error parsing config: %s", err)
		}

	case "json":
		if err := json.Unmarshal(buf.Bytes(), &config); err != nil {
			jww.ERROR.Fatalf("Error parsing config: %s", err)
		}

	case "toml":
		if _, err := toml.Decode(buf.String(), &config); err != nil {
			jww.ERROR.Fatalf("Error parsing config: %s", err)
		}
	}

	insensativiseMap()
}

func insensativiseMap() {
	for key, _ := range config {
		if key != strings.ToLower(key) {
			registerAlias(key, key)
		}
	}
}

func SetConfigName(in string) {
	if in != "" {
		configName = in
	}
}

func SetConfigType(in string) {
	if in != "" {
		configType = in
	}
}

func getConfigType() string {
	if configType != "" {
		return configType
	}

	cf := getConfigFile()
	ext := path.Ext(cf)

	if len(ext) > 1 {
		return ext[1:]
	} else {
		return ""
	}
}

func getConfigFile() string {
	// if explicitly set, then use it
	if configFile != "" {
		return configFile
	}

	cf, err := findConfigFile()
	if err != nil {
		return ""
	}

	configFile = cf
	return getConfigFile()
}

func searchInPath(in string) (filename string) {
	jww.DEBUG.Println("Searching for config in ", in)
	for _, ext := range SupportedExts {

		jww.DEBUG.Println("Checking for", path.Join(in, configName+"."+ext))
		if b, _ := exists(path.Join(in, configName+"."+ext)); b {
			jww.DEBUG.Println("Found: ", path.Join(in, configName+"."+ext))
			return path.Join(in, configName+"."+ext)
		}
	}

	return ""
}

func findConfigFile() (string, error) {
	jww.INFO.Println("Searching for config in ", configPaths)

	for _, cp := range configPaths {
		file := searchInPath(cp)
		if file != "" {
			return file, nil
		}
	}
	cwd, _ := findCWD()

	file := searchInPath(cwd)
	if file != "" {
		return file, nil
	}

	return "", fmt.Errorf("config file not found in: %s", configPaths)
}

func findCWD() (string, error) {
	serverFile, err := filepath.Abs(os.Args[0])

	if err != nil {
		return "", fmt.Errorf("Can't get absolute path for executable: %v", err)
	}

	path := filepath.Dir(serverFile)
	realFile, err := filepath.EvalSymlinks(serverFile)

	if err != nil {
		if _, err = os.Stat(serverFile + ".exe"); err == nil {
			realFile = filepath.Clean(serverFile + ".exe")
		}
	}

	if err == nil && realFile != serverFile {
		path = filepath.Dir(realFile)
	}

	return path, nil
}

// Check if File / Directory Exists
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 stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

func absPathify(inPath string) string {
	jww.INFO.Println("Trying to resolve absolute path to", inPath)
	if filepath.IsAbs(inPath) {
		return filepath.Clean(inPath)
	}

	p, err := filepath.Abs(inPath)
	if err == nil {
		return filepath.Clean(p)
	} else {
		jww.ERROR.Println("Couldn't discover absolute path")
		jww.ERROR.Println(err)
	}
	return ""
}

func Debug() {
	fmt.Println("Config:")
	pretty.Println(config)
	fmt.Println("Defaults:")
	pretty.Println(defaults)
	fmt.Println("Override:")
	pretty.Println(override)
}

func Reset() {
	configPaths = nil
	configName = "config"

	// extensions Supported
	SupportedExts = []string{"json", "toml", "yaml"}
	configFile = ""
	configType = ""

	config = make(map[string]interface{})
	override = make(map[string]interface{})
	defaults = make(map[string]interface{})
	aliases = make(map[string]string)
}
<|endoftext|>


<commit_before>// Copyright (C) 2012 Mostafa Hajizadeh
// Copyright (C) 2014-5 Steve Francia

// package fsync keeps two files or directories in sync.
//
//         err := fsync.Sync("~/dst", ".")
//
// After the above code, if err is nil, every file and directory in the current
// directory is copied to ~/dst and has the same permissions. Consequent calls
// will only copy changed or new files.
//
// SyncTo is a helper function which helps you sync a groups of files or
// directories into a single destination. For instance, calling
//
//     SyncTo("public", "build/app.js", "build/app.css", "images", "fonts")
//
// is equivalent to calling
//
//     Sync("public/app.js", "build/app.js")
//     Sync("public/app.css", "build/app.css")
//     Sync("public/images", "images")
//     Sync("public/fonts", "fonts")
//
// Actually, this is how SyncTo is implemented: consequent calls to Sync.
//
// By default, sync code ignores extra files in the destination that don’t have
// identicals in the source. Setting Delete field of a Syncer to true changes
// this behavior and deletes these extra files.

package fsync

import (
	"bytes"
	"errors"
	"io"
	"os"
	"path/filepath"
	"runtime"

	"github.com/spf13/afero"
)

var (
	ErrFileOverDir = errors.New(
		"fsync: trying to overwrite a non-empty directory with a file")
)

// Sync copies files and directories inside src into dst.
func Sync(dst, src string) error {
	return NewSyncer().Sync(dst, src)
}

// SyncTo syncs srcs files and directories into to directory.
func SyncTo(to string, srcs ...string) error {
	return NewSyncer().SyncTo(to, srcs...)
}

// Type Syncer provides functions for syncing files.
type Syncer struct {
	// Set this to true to delete files in the destination that don't exist
	// in the source.
	Delete bool
	// To allow certain files to remain in the destination, implement this function.
	// Return true to skip file, false to delete.
	DeleteFilter func(f os.FileInfo) bool
	// By default, modification times are synced. This can be turned off by
	// setting this to true.
	NoTimes bool
	// NoChmod disables permission mode syncing.
	NoChmod bool

	// TODO add options for not checking content for equality

	SrcFs  afero.Fs
	DestFs afero.Fs
}

// NewSyncer creates a new instance of Syncer with default options.
func NewSyncer() *Syncer {
	s := Syncer{SrcFs: new(afero.OsFs), DestFs: new(afero.OsFs)}
	s.DeleteFilter = func(f os.FileInfo) bool {
		return false
	}
	return &s
}

// Sync copies files and directories inside src into dst.
func (s *Syncer) Sync(dst, src string) error {
	// make sure src exists
	if _, err := s.SrcFs.Stat(src); err != nil {
		return err
	}
	// return error instead of replacing a non-empty directory with a file
	if b, err := s.checkDir(dst, src); err != nil {
		return err
	} else if b {
		return ErrFileOverDir
	}

	return s.syncRecover(dst, src)
}

// SyncTo syncs srcs files or directories into to directory.
func (s *Syncer) SyncTo(to string, srcs ...string) error {
	for _, src := range srcs {
		dst := filepath.Join(to, filepath.Base(src))
		if err := s.Sync(dst, src); err != nil {
			return err
		}
	}
	return nil
}

// syncRecover handles errors and calls sync
func (s *Syncer) syncRecover(dst, src string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			if _, ok := r.(runtime.Error); ok {
				panic(r)
			}
			err = r.(error)
		}
	}()

	s.sync(dst, src)
	return nil
}

// sync updates dst to match with src, handling both files and directories.
func (s *Syncer) sync(dst, src string) {
	// sync permissions and modification times after handling content
	defer s.syncstats(dst, src)

	// read files info
	dstat, err := s.DestFs.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		panic(err)
	}
	sstat, err := s.SrcFs.Stat(src)
	if err != nil && os.IsNotExist(err) {
		return // src was deleted before we could copy it
	}
	check(err)

	if !sstat.IsDir() {
		// src is a file
		// delete dst if its a directory
		if dstat != nil && dstat.IsDir() {
			check(s.DestFs.RemoveAll(dst))
		}
		if !s.equal(dst, src) {
			// perform copy
			df, err := s.DestFs.Create(dst)
			check(err)
			defer df.Close()
			sf, err := s.SrcFs.Open(src)
			if os.IsNotExist(err) {
				return
			}
			check(err)
			defer sf.Close()
			_, err = io.Copy(df, sf)
			if os.IsNotExist(err) {
				return
			}
			check(err)
		}
		return
	}

	// src is a directory
	// make dst if necessary
	if dstat == nil {
		// dst does not exist; create directory
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	} else if !dstat.IsDir() {
		// dst is a file; remove and create directory
		check(s.DestFs.Remove(dst))
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	}

	// go through sf files and sync them
	files, err := afero.ReadDir(s.SrcFs, src)
	if os.IsNotExist(err) {
		return
	}
	check(err)
	// make a map of filenames for quick lookup; used in deletion
	// deletion below
	m := make(map[string]bool, len(files))
	for _, file := range files {
		dst2 := filepath.Join(dst, file.Name())
		src2 := filepath.Join(src, file.Name())
		s.sync(dst2, src2)
		m[file.Name()] = true
	}

	// delete files from dst that does not exist in src
	if s.Delete {
		files, err = afero.ReadDir(s.DestFs, dst)
		check(err)
		for _, file := range files {
			if !m[file.Name()] && !s.DeleteFilter(file) {
				check(s.DestFs.RemoveAll(filepath.Join(dst, file.Name())))
			}
		}
	}
}

// syncstats makes sure dst has the same pemissions and modification time as src
func (s *Syncer) syncstats(dst, src string) {
	// get file infos; return if not exist and panic if error
	dstat, err1 := s.DestFs.Stat(dst)
	sstat, err2 := s.SrcFs.Stat(src)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return
	}
	check(err1)
	check(err2)

	// update dst's permission bits
	if !s.NoChmod {
		if dstat.Mode().Perm() != sstat.Mode().Perm() {
			check(s.DestFs.Chmod(dst, sstat.Mode().Perm()))
		}
	}

	// update dst's modification time
	if !s.NoTimes {
		if !dstat.ModTime().Equal(sstat.ModTime()) {
			err := s.DestFs.Chtimes(dst, sstat.ModTime(), sstat.ModTime())
			check(err)
		}
	}
}

// equal returns true if both dst and src files are equal
func (s *Syncer) equal(dst, src string) bool {
	// get file infos
	info1, err1 := s.DestFs.Stat(dst)
	info2, err2 := s.SrcFs.Stat(src)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return false
	}
	check(err1)
	check(err2)

	// check sizes
	if info1.Size() != info2.Size() {
		return false
	}

	// both have the same size, check the contents
	f1, err := s.DestFs.Open(dst)
	check(err)
	defer f1.Close()
	f2, err := s.SrcFs.Open(src)
	check(err)
	defer f2.Close()
	buf1 := make([]byte, 1000)
	buf2 := make([]byte, 1000)
	for {
		// read from both
		n1, err := f1.Read(buf1)
		if err != nil && err != io.EOF {
			panic(err)
		}
		n2, err := f2.Read(buf2)
		if err != nil && err != io.EOF {
			panic(err)
		}

		// compare read bytes
		if !bytes.Equal(buf1[:n1], buf2[:n2]) {
			return false
		}

		// end of both files
		if n1 == 0 && n2 == 0 {
			break
		}
	}

	return true
}

// checkDir returns true if dst is a non-empty directory and src is a file
func (s *Syncer) checkDir(dst, src string) (b bool, err error) {
	// read file info
	dstat, err := s.DestFs.Stat(dst)
	if os.IsNotExist(err) {
		return false, nil
	} else if err != nil {
		return false, err
	}
	sstat, err := s.SrcFs.Stat(src)
	if err != nil {
		return false, err
	}

	// return false is dst is not a directory or src is a directory
	if !dstat.IsDir() || sstat.IsDir() {
		return false, nil
	}

	// dst is a directory and src is a file
	// check if dst is non-empty
	// read dst directory

	files, err := afero.ReadDir(s.DestFs, dst)
	if err != nil {
		return false, err
	}
	if len(files) > 0 {
		return true, nil
	}
	return false, nil
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}
<commit_msg>Add ChmodFilter<commit_after>// Copyright (C) 2012 Mostafa Hajizadeh
// Copyright (C) 2014-5 Steve Francia

// package fsync keeps two files or directories in sync.
//
//         err := fsync.Sync("~/dst", ".")
//
// After the above code, if err is nil, every file and directory in the current
// directory is copied to ~/dst and has the same permissions. Consequent calls
// will only copy changed or new files.
//
// SyncTo is a helper function which helps you sync a groups of files or
// directories into a single destination. For instance, calling
//
//     SyncTo("public", "build/app.js", "build/app.css", "images", "fonts")
//
// is equivalent to calling
//
//     Sync("public/app.js", "build/app.js")
//     Sync("public/app.css", "build/app.css")
//     Sync("public/images", "images")
//     Sync("public/fonts", "fonts")
//
// Actually, this is how SyncTo is implemented: consequent calls to Sync.
//
// By default, sync code ignores extra files in the destination that don’t have
// identicals in the source. Setting Delete field of a Syncer to true changes
// this behavior and deletes these extra files.

package fsync

import (
	"bytes"
	"errors"
	"io"
	"os"
	"path/filepath"
	"runtime"

	"github.com/spf13/afero"
)

var (
	ErrFileOverDir = errors.New(
		"fsync: trying to overwrite a non-empty directory with a file")
)

// Sync copies files and directories inside src into dst.
func Sync(dst, src string) error {
	return NewSyncer().Sync(dst, src)
}

// SyncTo syncs srcs files and directories into to directory.
func SyncTo(to string, srcs ...string) error {
	return NewSyncer().SyncTo(to, srcs...)
}

// Type Syncer provides functions for syncing files.
type Syncer struct {
	// Set this to true to delete files in the destination that don't exist
	// in the source.
	Delete bool
	// To allow certain files to remain in the destination, implement this function.
	// Return true to skip file, false to delete.
	DeleteFilter func(f os.FileInfo) bool
	// By default, modification times are synced. This can be turned off by
	// setting this to true.
	NoTimes bool
	// NoChmod disables permission mode syncing.
	NoChmod bool
	// Implement this function to skip Chmod syncing for only certain files
	// or directories. Return true to skip Chmod.
	ChmodFilter func(dst, src os.FileInfo) bool

	// TODO add options for not checking content for equality

	SrcFs  afero.Fs
	DestFs afero.Fs
}

// NewSyncer creates a new instance of Syncer with default options.
func NewSyncer() *Syncer {
	s := Syncer{SrcFs: new(afero.OsFs), DestFs: new(afero.OsFs)}
	s.DeleteFilter = func(f os.FileInfo) bool {
		return false
	}
	return &s
}

// Sync copies files and directories inside src into dst.
func (s *Syncer) Sync(dst, src string) error {
	// make sure src exists
	if _, err := s.SrcFs.Stat(src); err != nil {
		return err
	}
	// return error instead of replacing a non-empty directory with a file
	if b, err := s.checkDir(dst, src); err != nil {
		return err
	} else if b {
		return ErrFileOverDir
	}

	return s.syncRecover(dst, src)
}

// SyncTo syncs srcs files or directories into to directory.
func (s *Syncer) SyncTo(to string, srcs ...string) error {
	for _, src := range srcs {
		dst := filepath.Join(to, filepath.Base(src))
		if err := s.Sync(dst, src); err != nil {
			return err
		}
	}
	return nil
}

// syncRecover handles errors and calls sync
func (s *Syncer) syncRecover(dst, src string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			if _, ok := r.(runtime.Error); ok {
				panic(r)
			}
			err = r.(error)
		}
	}()

	s.sync(dst, src)
	return nil
}

// sync updates dst to match with src, handling both files and directories.
func (s *Syncer) sync(dst, src string) {
	// sync permissions and modification times after handling content
	defer s.syncstats(dst, src)

	// read files info
	dstat, err := s.DestFs.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		panic(err)
	}
	sstat, err := s.SrcFs.Stat(src)
	if err != nil && os.IsNotExist(err) {
		return // src was deleted before we could copy it
	}
	check(err)

	if !sstat.IsDir() {
		// src is a file
		// delete dst if its a directory
		if dstat != nil && dstat.IsDir() {
			check(s.DestFs.RemoveAll(dst))
		}
		if !s.equal(dst, src) {
			// perform copy
			df, err := s.DestFs.Create(dst)
			check(err)
			defer df.Close()
			sf, err := s.SrcFs.Open(src)
			if os.IsNotExist(err) {
				return
			}
			check(err)
			defer sf.Close()
			_, err = io.Copy(df, sf)
			if os.IsNotExist(err) {
				return
			}
			check(err)
		}
		return
	}

	// src is a directory
	// make dst if necessary
	if dstat == nil {
		// dst does not exist; create directory
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	} else if !dstat.IsDir() {
		// dst is a file; remove and create directory
		check(s.DestFs.Remove(dst))
		check(s.DestFs.MkdirAll(dst, 0755)) // permissions will be synced later
	}

	// go through sf files and sync them
	files, err := afero.ReadDir(s.SrcFs, src)
	if os.IsNotExist(err) {
		return
	}
	check(err)
	// make a map of filenames for quick lookup; used in deletion
	// deletion below
	m := make(map[string]bool, len(files))
	for _, file := range files {
		dst2 := filepath.Join(dst, file.Name())
		src2 := filepath.Join(src, file.Name())
		s.sync(dst2, src2)
		m[file.Name()] = true
	}

	// delete files from dst that does not exist in src
	if s.Delete {
		files, err = afero.ReadDir(s.DestFs, dst)
		check(err)
		for _, file := range files {
			if !m[file.Name()] && !s.DeleteFilter(file) {
				check(s.DestFs.RemoveAll(filepath.Join(dst, file.Name())))
			}
		}
	}
}

// syncstats makes sure dst has the same pemissions and modification time as src
func (s *Syncer) syncstats(dst, src string) {
	// get file infos; return if not exist and panic if error
	dstat, err1 := s.DestFs.Stat(dst)
	sstat, err2 := s.SrcFs.Stat(src)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return
	}
	check(err1)
	check(err2)

	// update dst's permission bits
	noChmod := s.NoChmod
	if !noChmod && s.ChmodFilter != nil {
		noChmod = s.ChmodFilter(dstat, sstat)
	}
	if !noChmod {
		if dstat.Mode().Perm() != sstat.Mode().Perm() {
			check(s.DestFs.Chmod(dst, sstat.Mode().Perm()))
		}
	}

	// update dst's modification time
	if !s.NoTimes {
		if !dstat.ModTime().Equal(sstat.ModTime()) {
			err := s.DestFs.Chtimes(dst, sstat.ModTime(), sstat.ModTime())
			check(err)
		}
	}
}

// equal returns true if both dst and src files are equal
func (s *Syncer) equal(dst, src string) bool {
	// get file infos
	info1, err1 := s.DestFs.Stat(dst)
	info2, err2 := s.SrcFs.Stat(src)
	if os.IsNotExist(err1) || os.IsNotExist(err2) {
		return false
	}
	check(err1)
	check(err2)

	// check sizes
	if info1.Size() != info2.Size() {
		return false
	}

	// both have the same size, check the contents
	f1, err := s.DestFs.Open(dst)
	check(err)
	defer f1.Close()
	f2, err := s.SrcFs.Open(src)
	check(err)
	defer f2.Close()
	buf1 := make([]byte, 1000)
	buf2 := make([]byte, 1000)
	for {
		// read from both
		n1, err := f1.Read(buf1)
		if err != nil && err != io.EOF {
			panic(err)
		}
		n2, err := f2.Read(buf2)
		if err != nil && err != io.EOF {
			panic(err)
		}

		// compare read bytes
		if !bytes.Equal(buf1[:n1], buf2[:n2]) {
			return false
		}

		// end of both files
		if n1 == 0 && n2 == 0 {
			break
		}
	}

	return true
}

// checkDir returns true if dst is a non-empty directory and src is a file
func (s *Syncer) checkDir(dst, src string) (b bool, err error) {
	// read file info
	dstat, err := s.DestFs.Stat(dst)
	if os.IsNotExist(err) {
		return false, nil
	} else if err != nil {
		return false, err
	}
	sstat, err := s.SrcFs.Stat(src)
	if err != nil {
		return false, err
	}

	// return false is dst is not a directory or src is a directory
	if !dstat.IsDir() || sstat.IsDir() {
		return false, nil
	}

	// dst is a directory and src is a file
	// check if dst is non-empty
	// read dst directory

	files, err := afero.ReadDir(s.DestFs, dst)
	if err != nil {
		return false, err
	}
	if len(files) > 0 {
		return true, nil
	}
	return false, nil
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>//watch handles reloading of a command by watching a directory and if supplied a set of given extensions for change
package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/howeyc/fsnotify"
)

var multispaces = regexp.MustCompile(`\s+`)

func goDeps(targetdir string) (bool, error) {
	cmdline := []string{"go", "get"}

	cmdline = append(cmdline, targetdir)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go install failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

//goRun runs the runs a command
func goRun(cmd string) string {
	var cmdline []string
	com := strings.Split(cmd, " ")

	if len(com) < 0 {
		return ""
	}

	if len(com) == 1 {
		cmdline = append(cmdline, com...)
	} else {
		cmdline = append(cmdline, com[0])
		cmdline = append(cmdline, com[1:]...)
	}

	//setup the executor and use a shard buffer
	cmdo := exec.Command(cmdline[0], cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmdo.Stdout = buf
	cmdo.Stderr = buf

	_ = cmdo.Run()

	return buf.String()
}

//gobuild runs the build process and returns true/false and an error
func gobuild(dir, name string) (bool, error) {
	cmdline := []string{"go", "build"}

	if runtime.GOOS == "windows" {
		name = fmt.Sprintf("%s.exe", name)
	}

	target := filepath.Join(dir, name)
	cmdline = append(cmdline, "-o", target)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go build failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

// runBin runs the generated bin file with the arguments expected
func runBin(bindir, bin string, args []string) chan bool {
	var relunch = make(chan bool)
	go func() {
		binfile := fmt.Sprintf("%s/%s", bindir, bin)
		cmdline := append([]string{bin}, args...)
		var proc *os.Process

		for dosig := range relunch {
			if proc != nil {
				if err := proc.Signal(os.Interrupt); err != nil {
					log.Printf("Error in sending signal %s", err)
					proc.Kill()
				}
				proc.Wait()
			}

			if !dosig {
				continue
			}

			log.Printf("Exc Command: %s", binfile)

			cmd := exec.Command(binfile, args...)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr

			log.Printf("Running Command: %s", cmdline)

			if err := cmd.Start(); err != nil {
				log.Printf("Error starting process: %s", err)
			}

			log.Printf("Setting process")
			proc = cmd.Process
		}
	}()
	log.Printf("Returning channel")
	return relunch
}

func buildPkgWatcher(pkpath string) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	add2Watcher(ws, pkpath, map[string]bool{})
	return ws, err
}

func buildWatcher(pkpath string) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	ws.Watch(pkpath)
	return ws, err
}

func add2Watcher(ws *fsnotify.Watcher, pkgpath string, assets map[string]bool) {
	pkg, err := build.Import(pkgpath, "", 0)

	if err != nil {
		return
	}

	if pkg.Goroot {
		return
	}

	ws.Watch(pkg.Dir)
	assets[pkgpath] = true

	for _, imp := range pkg.Imports {
		if !assets[imp] {
			add2Watcher(ws, imp, assets)
		}
	}
}

func watch(command, importable, bin, exts string, dobuild bool, args []string) error {
	log.Printf("Command: %s %s %s %t", command, importable, bin, dobuild)

	extcls := multispaces.ReplaceAllString(exts, " ")
	extens := multispaces.Split(extcls, -1)

	if len(extens) == 1 && extens[0] == "" {
		extens = extens[:0]
	}

	var buildName string
	var ubin string

	var buildHandler = func() error {
		var pkgs *build.Package
		var err error
		pkgs, err = build.Import(importable, "", 0)

		if err != nil {
			return err
		}

		_, buildName = path.Split(pkgs.ImportPath)
		// _, buildName := path.Split("./")

		wd, _ := os.Getwd()
		if bin != "" {
			ubin = filepath.ToSlash(filepath.Join(wd, bin))
		} else {
			ubin = pkgs.BinDir
		}

		log.Printf("Using bin path: %s", ubin)
		// lets install
		_, err = goDeps("./")

		if err != nil {
			return err
		}

		log.Printf("Building Pkg(%s) bin to %s with name: %s", pkgs.ImportPath, ubin, buildName)

		done, err := gobuild(ubin, buildName)

		if err != nil {
			return err
		}

		_ = done
		return nil
	}

	var buildWatch = func() (*fsnotify.Watcher, error) {
		var err error
		var watch *fsnotify.Watcher

		if dobuild {
			watch, err = buildPkgWatcher(importable)
		} else {
			watch, err = buildWatcher("./")
		}
		return watch, err
	}

	var err error
	var watch *fsnotify.Watcher
	var binRun bool
	var binChan chan bool

	//lets build if we are allowed
	if dobuild {
		if err = buildHandler(); err != nil {
			return err
		}

		binChan = runBin(ubin, buildName, args)
		binRun = true
		binChan <- true
	}

	log.Printf("Building dir watchers.....")
	watch, err = buildWatch()

	if err != nil {
		log.Printf("Unable to build err %s", err.Error())
		return err
	}

	for {

		//should we watch
		we, _ := <-watch.Event

		exo := filepath.Ext(we.Name)

		if filepath.ToSlash(we.Name) == filepath.ToSlash(ubin) {
			continue
		}

		log.Printf("Watch: %s -> %s with extensions: %s", exo, we.Name, extens)

		if len(extens) > 0 {
			var found bool
			for _, mo := range extens {
				if exo == mo {
					found = true
					break
				}
			}

			if !found {
				continue
			}
		}

		log.Printf("Watcher notified change: %s", we.Name)

		watch.Close()

		go func(evs chan *fsnotify.FileEvent) {
			for _ = range evs {
			}
		}(watch.Event)

		log.Printf("Re-initiating watch scans .....")

		if command != "" {
			log.Printf("Running cmd '%s' with result: '%s'", command, goRun(command))
		}

		if dobuild {
			if err = buildHandler(); err != nil {
				return err
			}

			if binRun {
				binChan <- true
			}
		}

		watch, err = buildWatch()

		if err != nil {
			return err
		}

		go func(errors chan error) {
			for _ = range errors {
			}
		}(watch.Error)

	}
}

func usage() {
	fmt.Printf(`Watch:
    About: provides a simple but combined go dir builder and file watcher
    Version: %s
    Usage: watch [--import] <import path> [--cmd] <cmd_to_rerun> [--ext] <extensions> [--bin] <bin path to store>
    `, version)
}

var version = "0.0.1"

func main() {
	exts := flag.String("ext", "", "a space seperated string of extensions to watch")
	cmd := flag.String("cmd", "", "Command to run instead on every change")
	bindir := flag.String("bin", "./bin", "The build directory for storing the build file")
	importdir := flag.String("import", "", "Command to run instead on every change")

	flag.Parse()

	if *cmd == "" && *importdir == "" {
		usage()
		return
	}

	build := (*importdir != "")

	err := watch(*cmd, *importdir, *bindir, *exts, build, flag.Args())

	if err != nil {
		log.Printf("Errored: %s", err.Error())
	}
}
<commit_msg>added full directory watching<commit_after>//watch handles reloading of a command by watching a directory and if supplied a set of given extensions for change
package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/howeyc/fsnotify"
)

var multispaces = regexp.MustCompile(`\s+`)

func goDeps(targetdir string) (bool, error) {
	cmdline := []string{"go", "get"}

	cmdline = append(cmdline, targetdir)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go install failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

//goRun runs the runs a command
func goRun(cmd string) string {
	var cmdline []string
	com := strings.Split(cmd, " ")

	if len(com) < 0 {
		return ""
	}

	if len(com) == 1 {
		cmdline = append(cmdline, com...)
	} else {
		cmdline = append(cmdline, com[0])
		cmdline = append(cmdline, com[1:]...)
	}

	//setup the executor and use a shard buffer
	cmdo := exec.Command(cmdline[0], cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmdo.Stdout = buf
	cmdo.Stderr = buf

	_ = cmdo.Run()

	return buf.String()
}

//gobuild runs the build process and returns true/false and an error
func gobuild(dir, name string) (bool, error) {
	cmdline := []string{"go", "build"}

	if runtime.GOOS == "windows" {
		name = fmt.Sprintf("%s.exe", name)
	}

	target := filepath.Join(dir, name)
	cmdline = append(cmdline, "-o", target)

	//setup the executor and use a shard buffer
	cmd := exec.Command("go", cmdline[1:]...)
	buf := bytes.NewBuffer([]byte{})
	cmd.Stdout = buf
	cmd.Stderr = buf

	err := cmd.Run()

	if buf.Len() > 0 {
		return false, fmt.Errorf("go build failed: %s: %s", buf.String(), err.Error())
	}

	return true, nil
}

// runBin runs the generated bin file with the arguments expected
func runBin(bindir, bin string, args []string) chan bool {
	var relunch = make(chan bool)
	go func() {
		binfile := fmt.Sprintf("%s/%s", bindir, bin)
		cmdline := append([]string{bin}, args...)
		var proc *os.Process

		for dosig := range relunch {
			if proc != nil {
				if err := proc.Signal(os.Interrupt); err != nil {
					log.Printf("Error in sending signal %s", err)
					proc.Kill()
				}
				proc.Wait()
			}

			if !dosig {
				continue
			}

			log.Printf("Exc Command: %s", binfile)

			cmd := exec.Command(binfile, args...)
			cmd.Stdout = os.Stdout
			cmd.Stderr = os.Stderr

			log.Printf("Running Command: %s", cmdline)

			if err := cmd.Start(); err != nil {
				log.Printf("Error starting process: %s", err)
			}

			log.Printf("Setting process")
			proc = cmd.Process
		}
	}()
	log.Printf("Returning channel")
	return relunch
}

func buildPkgWatcher(pkpath string, assets map[string]bool) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	add2Watcher(ws, pkpath, assets)
	return ws, err
}

func buildWatcher(pkpath string) (*fsnotify.Watcher, error) {
	ws, err := fsnotify.NewWatcher()
	ws.Watch(pkpath)
	return ws, err
}

func hasIn(paths []string, dt string) bool {
	for _, so := range paths {
		if strings.Contains(so, dt) || so == dt {
			return true
		}
	}
	return false
}

func watchDir(ws *fsnotify.Watcher, dir string, assets map[string]bool, skip []string) {

	mo, err := os.Stat(dir)

	if err != nil {
		return
	}

	if !mo.IsDir() {
		return
	}

	filepath.Walk(filepath.ToSlash(dir), func(path string, info os.FileInfo, err error) error {

		if strings.Contains(path, ".git") {
			return nil
		}

		if info == nil {
			return nil
		}

		if hasIn(skip, path) {
			return nil
		}

		// if !info.IsDir() {
		// 	return nil
		// }

		if assets[path] {
			return nil
		}

		ws.Watch(path)
		assets[path] = true
		return nil
	})
}

func add2Watcher(ws *fsnotify.Watcher, pkgpath string, assets map[string]bool) {
	pkg, err := build.Import(pkgpath, "", 0)

	if err != nil {
		return
	}

	if pkg.Goroot {
		return
	}

	ws.Watch(pkg.Dir)
	assets[pkgpath] = true

	for _, imp := range pkg.Imports {
		if !assets[imp] {
			add2Watcher(ws, imp, assets)
		}
	}
}

func watch(command, importable, bin, exts string, dobuild, withdir bool, args []string) error {
	log.Printf("Command: %s %s %s %t", command, importable, bin, dobuild)

	extcls := multispaces.ReplaceAllString(exts, " ")
	extens := multispaces.Split(extcls, -1)

	if len(extens) == 1 && extens[0] == "" {
		extens = extens[:0]
	}

	var buildName string
	var ubin string

	var buildHandler = func() error {
		var pkgs *build.Package
		var err error
		pkgs, err = build.Import(importable, "", 0)

		if err != nil {
			return err
		}

		_, buildName = path.Split(pkgs.ImportPath)
		// _, buildName := path.Split("./")

		wd, _ := os.Getwd()
		if bin != "" {
			ubin = filepath.ToSlash(filepath.Join(wd, bin))
		} else {
			ubin = pkgs.BinDir
		}

		log.Printf("Using bin path: %s", ubin)
		// lets install
		_, err = goDeps("./")

		if err != nil {
			return err
		}

		log.Printf("Building Pkg(%s) bin to %s with name: %s", pkgs.ImportPath, ubin, buildName)

		done, err := gobuild(ubin, buildName)

		if err != nil {
			return err
		}

		_ = done
		return nil
	}

	var buildWatch = func() (*fsnotify.Watcher, error) {
		var err error
		var watch *fsnotify.Watcher

		added := make(map[string]bool)

		if dobuild {
			watch, err = buildPkgWatcher(importable, added)
		} else {
			if !added["./"] {
				watch, err = buildWatcher("./")
			}
		}

		//lets watch the current directory also if allowed
		if withdir && err == nil {
			wod, ex := os.Getwd()

			if ex == nil && wod != "" {
				watchDir(watch, wod, added, []string{ubin})
			}
		}

		return watch, err
	}

	var err error
	var watch *fsnotify.Watcher
	var binRun bool
	var binChan chan bool

	//lets build if we are allowed
	if dobuild {
		if err = buildHandler(); err != nil {
			return err
		}

		binChan = runBin(ubin, buildName, args)
		binRun = true
		binChan <- true
	}

	log.Printf("Building dir watchers.....")
	watch, err = buildWatch()

	if err != nil {
		log.Printf("Unable to build err %s", err.Error())
		return err
	}

	for {

		//should we watch
		we, _ := <-watch.Event

		exo := filepath.Ext(we.Name)

		if filepath.ToSlash(we.Name) == filepath.ToSlash(ubin) {
			continue
		}

		log.Printf("Watch: %s -> %s with extensions: %s", exo, we.Name, extens)

		if len(extens) > 0 {
			var found bool
			for _, mo := range extens {
				if exo == mo {
					found = true
					break
				}
			}

			if !found {
				continue
			}
		}

		log.Printf("Watcher notified change: %s", we.Name)

		watch.Close()

		go func(evs chan *fsnotify.FileEvent) {
			for _ = range evs {
			}
		}(watch.Event)

		log.Printf("Re-initiating watch scans .....")

		if command != "" {
			log.Printf("Running cmd '%s' with result: '%s'", command, goRun(command))
		}

		if dobuild {
			if err = buildHandler(); err != nil {
				return err
			}

			if binRun {
				binChan <- true
			}
		}

		watch, err = buildWatch()

		if err != nil {
			return err
		}

		go func(errors chan error) {
			for _ = range errors {
			}
		}(watch.Error)

	}
}

func usage() {
	fmt.Printf(`Watch:
    About: provides a simple but combined go dir builder and file watcher
    Version: %s
    Usage: watch [--import] <import path> [--cmd] <cmd_to_rerun> [--ext] <extensions> [--bin] <bin path to store>
    `, version)
}

var version = "0.0.1"

func main() {
	exts := flag.String("ext", "", "a space seperated string of extensions to watch")
	cmd := flag.String("cmd", "", "Command to run instead on every change")
	withdir := flag.Bool("dir", false, "This sets the current directories and subdirectories to be watched")
	bindir := flag.String("bin", "./bin", "The build directory for storing the build file")
	importdir := flag.String("import", "", "Command to run instead on every change")

	flag.Parse()

	if *cmd == "" && *importdir == "" {
		usage()
		return
	}

	build := (*importdir != "")

	err := watch(*cmd, *importdir, *bindir, *exts, build, *withdir, flag.Args())

	if err != nil {
		log.Printf("Errored: %s", err.Error())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/pengwynn/flint/flint"
	"os"
)

func main() {
	app := cli.NewApp()
	app.Name = "flint"
	app.Usage = "Check a project for common sources of contributor friction"
	app.Version = "0.0.2"
	app.Flags = []cli.Flag{
		cli.BoolFlag{"skip-readme", "skip check for README"},
		cli.BoolFlag{"skip-contributing", "skip check for contributing guide"},
		cli.BoolFlag{"skip-license", "skip check for license"},
		cli.BoolFlag{"skip-bootstrap", "skip check for bootstrap script"},
		cli.BoolFlag{"skip-test", "skip check for test script"},
		cli.BoolFlag{"skip-scripts", "skip check for all scripts"},
	}
	app.Action = func(c *cli.Context) {
		path, _ := os.Getwd()
		if len(c.Args()) > 0 {
			path = c.Args()[0]
		}
		linter := &flint.Lint{Path: path}

		if !c.Bool("skip-readme") {
			linter.CheckReadme()
		}
		if !c.Bool("skip-contributing") {
			linter.CheckContributing()
		}
		if !c.Bool("skip-license") {
			linter.CheckLicense()
		}
		if !c.Bool("skip-scripts") {
			if !c.Bool("skip-bootstrap") {
				linter.CheckBootstrap()
			}
			if !c.Bool("skip-test") {
				linter.CheckTest()
			}
		}

		if len(linter.Errors) > 0 {
			for _, element := range linter.Errors {
				fmt.Println(element.Message)
			}
			level := linter.Severity()
			if level > 0 {
				fmt.Println("[CRITICAL] Some critical problems found. Please fix right away!")
			}
			os.Exit(level)
		} else {
			fmt.Println("[OK] All is well!")
		}
	}

	app.Run(os.Args)
}
<commit_msg>v0.0.3<commit_after>package main

import (
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/pengwynn/flint/flint"
	"os"
)

func main() {
	app := cli.NewApp()
	app.Name = "flint"
	app.Usage = "Check a project for common sources of contributor friction"
	app.Version = "0.0.3"
	app.Flags = []cli.Flag{
		cli.BoolFlag{"skip-readme", "skip check for README"},
		cli.BoolFlag{"skip-contributing", "skip check for contributing guide"},
		cli.BoolFlag{"skip-license", "skip check for license"},
		cli.BoolFlag{"skip-bootstrap", "skip check for bootstrap script"},
		cli.BoolFlag{"skip-test", "skip check for test script"},
		cli.BoolFlag{"skip-scripts", "skip check for all scripts"},
	}
	app.Action = func(c *cli.Context) {
		path, _ := os.Getwd()
		if len(c.Args()) > 0 {
			path = c.Args()[0]
		}
		linter := &flint.Lint{Path: path}

		if !c.Bool("skip-readme") {
			linter.CheckReadme()
		}
		if !c.Bool("skip-contributing") {
			linter.CheckContributing()
		}
		if !c.Bool("skip-license") {
			linter.CheckLicense()
		}
		if !c.Bool("skip-scripts") {
			if !c.Bool("skip-bootstrap") {
				linter.CheckBootstrap()
			}
			if !c.Bool("skip-test") {
				linter.CheckTest()
			}
		}

		if len(linter.Errors) > 0 {
			for _, element := range linter.Errors {
				fmt.Println(element.Message)
			}
			level := linter.Severity()
			if level > 0 {
				fmt.Println("[CRITICAL] Some critical problems found. Please fix right away!")
			}
			os.Exit(level)
		} else {
			fmt.Println("[OK] All is well!")
		}
	}

	app.Run(os.Args)
}
<|endoftext|>