<commit_before>package commands

import (
	"bufio"
	"bytes"
	"os"

	"github.com/spf13/cobra"
)

const boilerPlate = `
# Copyright 2016 The Kubernetes Authors.
#
# 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.
`

func init() {
	autocompleteCmd.AddCommand(bashAutocompleteCmd)
	autocompleteCmd.AddCommand(zshAutocompleteCmd)

	RootCmd.AddCommand(autocompleteCmd)
}

var autocompleteCmd = &cobra.Command{
	Use:   "completion",
	Short: "Output shell completion code for the given shell (bash or zsh)",
}

var bashAutocompleteCmd = &cobra.Command{
	Use:   "bash",
	Short: "Output shell completion code for bash",
	Long: `
Output shell completion code for bash.
This command prints shell code which must be evaluated to provide interactive
completion of awless commands.
		$ source <(awless completion bash)
will load the awless completion code for bash. Note that this depends on the
bash-completion framework. It must be sourced before sourcing the awless
completion, e.g. on the Mac:
		$ brew install bash-completion
		$ source $(brew --prefix)/etc/bash_completion
		$ source <(awless completion bash)`,
	RunE: runCompletionBash,
}

var zshAutocompleteCmd = &cobra.Command{
	Use:   "zsh",
	Short: "List users",
	Long: `
Output shell completion code for zsh.
This command prints shell code which must be evaluated to provide interactive
completion of awless commands.
	$ source <(awless completion zsh)
[1] zsh completions are only supported in versions of zsh >= 5.2`,
	RunE: runCompletionZsh,
}

func runCompletionBash(cmd *cobra.Command, args []string) error {
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush()
	_, err := out.Write([]byte(boilerPlate))
	exitOn(err)
	return RootCmd.GenBashCompletion(out)
}

func runCompletionZsh(cmd *cobra.Command, args []string) error {
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush()
	_, err := out.Write([]byte(boilerPlate))
	exitOn(err)
	zshInitialization := `
__awless_bash_source() {
	alias shopt=':'
	alias _expand=_bash_expand
	alias _complete=_bash_comp
	emulate -L sh
	setopt kshglob noshglob braceexpand
	source "$@"
}
__awless_type() {
	# -t is not supported by zsh
	if [ "$1" == "-t" ]; then
		shift
		# fake Bash 4 to disable "complete -o nospace". Instead
		# "compopt +-o nospace" is used in the code to toggle trailing
		# spaces. We don't support that, but leave trailing spaces on
		# all the time
		if [ "$1" = "__awless_compopt" ]; then
			echo builtin
			return 0
		fi
	fi
	type "$@"
}
__awless_compgen() {
	local completions w
	completions=( $(compgen "$@") ) || return $?
	# filter by given word as prefix
	while [[ "$1" = -* && "$1" != -- ]]; do
		shift
		shift
	done
	if [[ "$1" == -- ]]; then
		shift
	fi
	for w in "${completions[@]}"; do
		if [[ "${w}" = "$1"* ]]; then
			echo "${w}"
		fi
	done
}
__awless_compopt() {
	true # don't do anything. Not supported by bashcompinit in zsh
}
__awless_declare() {
	if [ "$1" == "-F" ]; then
		whence -w "$@"
	else
		builtin declare "$@"
	fi
}
__awless_ltrim_colon_completions()
{
	if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
		# Remove colon-word prefix from COMPREPLY items
		local colon_word=${1%${1##*:}}
		local i=${#COMPREPLY[*]}
		while [[ $((--i)) -ge 0 ]]; do
			COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
		done
	fi
}
__awless_get_comp_words_by_ref() {
	cur="${COMP_WORDS[COMP_CWORD]}"
	prev="${COMP_WORDS[${COMP_CWORD}-1]}"
	words=("${COMP_WORDS[@]}")
	cword=("${COMP_CWORD[@]}")
}
__awless_filedir() {
	local RET OLD_IFS w qw
	__debug "_filedir $@ cur=$cur"
	if [[ "$1" = \~* ]]; then
		# somehow does not work. Maybe, zsh does not call this at all
		eval echo "$1"
		return 0
	fi
	OLD_IFS="$IFS"
	IFS=$'\n'
	if [ "$1" = "-d" ]; then
		shift
		RET=( $(compgen -d) )
	else
		RET=( $(compgen -f) )
	fi
	IFS="$OLD_IFS"
	IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
	for w in ${RET[@]}; do
		if [[ ! "${w}" = "${cur}"* ]]; then
			continue
		fi
		if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
			qw="$(__awless_quote "${w}")"
			if [ -d "${w}" ]; then
				COMPREPLY+=("${qw}/")
			else
				COMPREPLY+=("${qw}")
			fi
		fi
	done
}
__awless_quote() {
    if [[ $1 == \'* || $1 == \"* ]]; then
        # Leave out first character
        printf %q "${1:1}"
    else
    	printf %q "$1"
    fi
}
autoload -U +X bashcompinit && bashcompinit
# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
	LWORD='\<'
	RWORD='\>'
fi
__awless_convert_bash_to_zsh() {
	sed \
	-e 's/declare -F/whence -w/' \
	-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
	-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
	-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
	-e "s/${LWORD}_filedir${RWORD}/__awless_filedir/g" \
	-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__awless_get_comp_words_by_ref/g" \
	-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__awless_ltrim_colon_completions/g" \
	-e "s/${LWORD}compgen${RWORD}/__awless_compgen/g" \
	-e "s/${LWORD}compopt${RWORD}/__awless_compopt/g" \
	-e "s/${LWORD}declare${RWORD}/__awless_declare/g" \
	-e "s/\\\$(type${RWORD}/\$(__awless_type/g" \
	<<'BASH_COMPLETION_EOF'
`
	out.Write([]byte(zshInitialization))

	buf := new(bytes.Buffer)
	RootCmd.GenBashCompletion(buf)
	out.Write(buf.Bytes())

	zshTail := `
BASH_COMPLETION_EOF
}
__awless_bash_source <(__awless_convert_bash_to_zsh)
`
	out.Write([]byte(zshTail))
	return nil
}
<commit_msg>update completion help message and boilerplate<commit_after>package commands

// Copyright 2016 The Kubernetes Authors.
//
// 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.

import (
	"bufio"
	"bytes"
	"os"

	"github.com/spf13/cobra"
)

func init() {
	autocompleteCmd.AddCommand(bashAutocompleteCmd)
	autocompleteCmd.AddCommand(zshAutocompleteCmd)

	RootCmd.AddCommand(autocompleteCmd)
}

var autocompleteCmd = &cobra.Command{
	Use:   "completion",
	Short: "Output shell completion code for the given shell (bash or zsh)",
	Long: `
Output shell completion code for bash or zsh
This command prints shell code which must be evaluated to provide interactive
completion of awless commands.

Bash
	$ source <(awless completion bash)
will load the awless completion code for bash. Note that this depends on the
bash-completion framework. It must be sourced before sourcing the awless
completion, e.g. on macOS:
	$ brew install bash-completion
	$ source $(brew --prefix)/etc/bash_completion
	$ source <(awless completion bash)
	(or, if you want to preserve completion within new terminal sessions)
	$ echo 'source <(awless completion bash)\n' >> ~/.bashrc

Zsh
	$ source <(awless completion zsh)
	(or, if you want to preserve completion within new terminal sessions)
	$ echo 'source <(awless completion zsh)\n' >> ~/.zshrc`,
}

var bashAutocompleteCmd = &cobra.Command{
	Use:   "bash",
	Short: "Output shell completion code for bash",
	Long: `
Output shell completion code for bash.
This command prints shell code which must be evaluated to provide interactive
completion of awless commands.
	$ source <(awless completion bash)
will load the awless completion code for bash. Note that this depends on the
bash-completion framework. It must be sourced before sourcing the awless
completion, e.g. on macOS:
	$ brew install bash-completion
	$ source $(brew --prefix)/etc/bash_completion
	$ source <(awless completion bash)
	(or, if you want to preserve completion within new terminal sessions)
	$ echo 'source <(awless completion bash)\n' >> ~/.bashrc`,
	RunE: runCompletionBash,
}

var zshAutocompleteCmd = &cobra.Command{
	Use:   "zsh",
	Short: "List users",
	Long: `
Output shell completion code for zsh.
This command prints shell code which must be evaluated to provide interactive
completion of awless commands.
	$ source <(awless completion zsh)
	(or, if you want to preserve completion within new terminal sessions)
	$ echo 'source <(awless completion zsh)\n' >> ~/.zshrc
zsh completions are only supported in versions of zsh >= 5.2`,
	RunE: runCompletionZsh,
}

func runCompletionBash(cmd *cobra.Command, args []string) error {
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush()
	return RootCmd.GenBashCompletion(out)
}

func runCompletionZsh(cmd *cobra.Command, args []string) error {
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush()
	zshInitialization := `
__awless_bash_source() {
	alias shopt=':'
	alias _expand=_bash_expand
	alias _complete=_bash_comp
	emulate -L sh
	setopt kshglob noshglob braceexpand
	source "$@"
}
__awless_type() {
	# -t is not supported by zsh
	if [ "$1" == "-t" ]; then
		shift
		# fake Bash 4 to disable "complete -o nospace". Instead
		# "compopt +-o nospace" is used in the code to toggle trailing
		# spaces. We don't support that, but leave trailing spaces on
		# all the time
		if [ "$1" = "__awless_compopt" ]; then
			echo builtin
			return 0
		fi
	fi
	type "$@"
}
__awless_compgen() {
	local completions w
	completions=( $(compgen "$@") ) || return $?
	# filter by given word as prefix
	while [[ "$1" = -* && "$1" != -- ]]; do
		shift
		shift
	done
	if [[ "$1" == -- ]]; then
		shift
	fi
	for w in "${completions[@]}"; do
		if [[ "${w}" = "$1"* ]]; then
			echo "${w}"
		fi
	done
}
__awless_compopt() {
	true # don't do anything. Not supported by bashcompinit in zsh
}
__awless_declare() {
	if [ "$1" == "-F" ]; then
		whence -w "$@"
	else
		builtin declare "$@"
	fi
}
__awless_ltrim_colon_completions()
{
	if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
		# Remove colon-word prefix from COMPREPLY items
		local colon_word=${1%${1##*:}}
		local i=${#COMPREPLY[*]}
		while [[ $((--i)) -ge 0 ]]; do
			COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
		done
	fi
}
__awless_get_comp_words_by_ref() {
	cur="${COMP_WORDS[COMP_CWORD]}"
	prev="${COMP_WORDS[${COMP_CWORD}-1]}"
	words=("${COMP_WORDS[@]}")
	cword=("${COMP_CWORD[@]}")
}
__awless_filedir() {
	local RET OLD_IFS w qw
	__debug "_filedir $@ cur=$cur"
	if [[ "$1" = \~* ]]; then
		# somehow does not work. Maybe, zsh does not call this at all
		eval echo "$1"
		return 0
	fi
	OLD_IFS="$IFS"
	IFS=$'\n'
	if [ "$1" = "-d" ]; then
		shift
		RET=( $(compgen -d) )
	else
		RET=( $(compgen -f) )
	fi
	IFS="$OLD_IFS"
	IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"
	for w in ${RET[@]}; do
		if [[ ! "${w}" = "${cur}"* ]]; then
			continue
		fi
		if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
			qw="$(__awless_quote "${w}")"
			if [ -d "${w}" ]; then
				COMPREPLY+=("${qw}/")
			else
				COMPREPLY+=("${qw}")
			fi
		fi
	done
}
__awless_quote() {
    if [[ $1 == \'* || $1 == \"* ]]; then
        # Leave out first character
        printf %q "${1:1}"
    else
    	printf %q "$1"
    fi
}
autoload -U +X bashcompinit && bashcompinit
# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
	LWORD='\<'
	RWORD='\>'
fi
__awless_convert_bash_to_zsh() {
	sed \
	-e 's/declare -F/whence -w/' \
	-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
	-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
	-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
	-e "s/${LWORD}_filedir${RWORD}/__awless_filedir/g" \
	-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__awless_get_comp_words_by_ref/g" \
	-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__awless_ltrim_colon_completions/g" \
	-e "s/${LWORD}compgen${RWORD}/__awless_compgen/g" \
	-e "s/${LWORD}compopt${RWORD}/__awless_compopt/g" \
	-e "s/${LWORD}declare${RWORD}/__awless_declare/g" \
	-e "s/\\\$(type${RWORD}/\$(__awless_type/g" \
	<<'BASH_COMPLETION_EOF'
`
	out.Write([]byte(zshInitialization))

	buf := new(bytes.Buffer)
	RootCmd.GenBashCompletion(buf)
	out.Write(buf.Bytes())

	zshTail := `
BASH_COMPLETION_EOF
}
__awless_bash_source <(__awless_convert_bash_to_zsh)
`
	out.Write([]byte(zshTail))
	return nil
}
<|endoftext|>


<commit_before>package status

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
)

// Process takes the raw output of `git status --porcelain -b -z` and turns it
// into a structured data type.
func Process(gitStatusOutput []byte, root string) *StatusList {
	// initialize a statuslist to hold the results
	results := NewStatusList()

	// put the output into a bufferreader+scanner so we can consume it iteratively
	scanner := bufio.NewScanner(bytes.NewReader(gitStatusOutput))

	// the scanner needs a custom split function for splitting on NUL
	scanNul := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		for i, b := range data {
			if b == '\x00' {
				return i + 1, data[:i], nil
			}
		}
		return 0, nil, nil
	}
	scanner.Split(scanNul)

	// branch output is first line
	if !scanner.Scan() {
		log.Println("Failed to read buffer when expecting branch status")
		log.Fatal(scanner.Err())
	}
	branchBytes := scanner.Bytes()
	results.branch = ExtractBranch(branchBytes)

	// give ProcessChanges the scanner and let it handle the rest
	// (it does complicated stuff so it needs the entire scanner)
	for _, r := range ProcessChanges(scanner, root) {
		results.groups[r.group].items = append(results.groups[r.group].items, r)
	}

	return results
}

// ExtractBranch handles parsing the branch status from `status --porcelain -b`.
//
// Examples of stuff we will want to parse:
//
// 		## Initial commit on master
// 		## master
// 		## master...origin/master
// 		## master...origin/master [ahead 1]
//
func ExtractBranch(bs []byte) *BranchInfo {
	b := BranchInfo{}

	b.name = decodeBranchName(bs)
	b.ahead, b.behind = decodeBranchPosition(bs)

	return &b
}

func decodeBranchName(bs []byte) string {
	re := regexp.MustCompile(`^## (?:Initial commit on )?([^ \.]+)`)
	m := re.FindSubmatch(bs)
	if m == nil {
		log.Fatalf("Failed to parse branch name for output: [%s]", bs)
	}

	return string(m[1])
}

func decodeBranchPosition(bs []byte) (ahead, behind int) {
	reA := regexp.MustCompile(`\[ahead ?(\d+).*\]`)
	reB := regexp.MustCompile(`\[.*behind ?(\d+)\]`)

	mA := reA.FindSubmatch(bs)
	if mA != nil {
		ahead, _ = strconv.Atoi(string(mA[1]))
	}

	mB := reB.FindSubmatch(bs)
	if mB != nil {
		behind, _ = strconv.Atoi(string(mB[1]))
	}

	return
}

// basically a StatusItem minus the file information, for now just being
// used to get results from the change code processing...
// This could probably be encapsulated in StatusItem itself, but wary of adding
// more nesting...
type change struct {
	msg   string
	col   ColorGroup
	group StatusGroup
}

/*
ProcessChanges takes `git status -z` output and returns all status items.

(Note: in our case, we actually use `git status -bz` and remove branch header
when we process it earlier, but the results are binary identical.)

This is a complicated process because the format is weird. Each line is a
variable length number of columns (2-3), but the separator for 1-2 is a space
(but the content of columns can contain spaces too!), and the seperator for 2-3
is a NUL character (ASCII 0), *if* there is a third column. But here's where it
gets wacky: NUL is also the entry terminator (rather than a LF like in normal
porcelain mode)

Thankfully(?), column 1 which contains the status codes is a fixed length of two
bytes, and in theory the status codes contain enough secrets for us to determine
whether we should expect 2 or 3 columns (current hypothesis is we only get the
third column which is PATH2 when there is a "rename" operation). Sooo... we can
just read those two bytes and use that to determine how many NULs to scan to
until we have consumed a full entry.

We put up with this because it means no shell escaping, which should mean better
cross-platform support. Better hope some Windows people end up using it someday!
*/
func ProcessChanges(s *bufio.Scanner, root string) (results []*StatusItem) {

	// Before we process any changes, get the Current Working Directory.
	// We're going to need use to calculate absolute and relative filepaths for
	// every change, so we get it once now and pass it along.
	// If for some reason this fails (?), fallback to the git worktree root.
	wd, err := os.Getwd()
	if err != nil {
		wd = root
	}

	for s.Scan() {
		chunk := s.Bytes()
		// ...if chunk represents a rename or copy op, need to append another chunk
		// to get the full change item, with NUL manually reinserted because scanner
		// will extract past it.
		if (chunk[0] == 'R' || chunk[0] == 'C') && s.Scan() {
			chunk = append(chunk, '\x00')
			chunk = append(chunk, s.Bytes()...)
		}
		results = append(results, processChange(chunk, wd, root)...)
	}

	return
}

// process change for a single item from a `git status -z`.
//
// Takes raw bytes representing status change from `git status --porcelain -z`,
// assumes that it has already been properly split away from the rest of the
// changes.
//
// See ProcessChanges (plural) for more details on that process.
//
// Note some change items can have multiple statuses, so this returns a slice.
func processChange(chunk []byte, wd, root string) (results []*StatusItem) {

	absolutePath, relativePath := extractFile(chunk, root, wd)

	for _, c := range extractChangeCodes(chunk) {
		result := &StatusItem{
			msg:         c.msg,
			col:         c.col,
			group:       c.group,
			fileAbsPath: absolutePath,
			fileRelPath: relativePath,
		}
		results = append(results, result)
	}

	if len(results) < 1 {
		log.Fatalf(`
Failed to decode git status change code for chunk: [%s]
Please file a bug including this error message as well as the output of:

git status --porcelain

You can file the bug at: https://github.com/mroth/scmpuff/issues/
		`, chunk)
	}
	return results
}

/*
extractFile extracts the filename from a status change, and determines the
absolute and display paths.

 - root: the absolute path to the git working tree
 - wd: current working directory path
*/
func extractFile(chunk []byte, root, wd string) (absPath, relPath string) {
	// file identifier starts at pos4 and continues to EOL
	filePortion := chunk[3:len(chunk)]
	files := bytes.SplitN(filePortion, []byte{'\x00'}, 2)

	n := len(files)
	switch {
	case n < 1:
		log.Fatalf("tried to process a change chunk with no file")
	case n > 1:
		toFile, fromFile := files[0], files[1]
		var toRelPath, fromRelPath string

		absPath, toRelPath = calcPaths(toFile, root, wd)
		_, fromRelPath = calcPaths(fromFile, root, wd)

		relPath = fmt.Sprintf("%s -> %s", fromRelPath, toRelPath)
	default:
		absPath, relPath = calcPaths(files[0], root, wd)
	}

	return
}

// given path of a file relative to git root, git root, and working directory,
// calculate the absolute path of the file on the system, and attempt to figure
// out its relative path to $CWD (if can't, fallback to absolute for both).
func calcPaths(rootPath []byte, root, wd string) (absPath, relPath string) {
	file := rootPath
	absPath = filepath.Join(root, string(file))
	relPath, err := filepath.Rel(wd, absPath)
	if err != nil {
		relPath = absPath
	}
	return
}

/*
	 TODO: REPLICATE THIS LOGIC, INSTEAD OF STUFF PORTED FROM SCM_BREEZE.

   Ignored files are not listed, unless --ignored option is in effect, in
   which case XY are !!.

   X          Y     Meaning
   -------------------------------------------------
             [MD]   not updated
   M        [ MD]   updated in index
   A        [ MD]   added to index
   D         [ M]   deleted from index
   R        [ MD]   renamed in index
   C        [ MD]   copied in index
   [MARC]           index and work tree matches
   [ MARC]     M    work tree changed since index
   [ MARC]     D    deleted in work tree
   -------------------------------------------------
   D           D    unmerged, both deleted
   A           U    unmerged, added by us
   U           D    unmerged, deleted by them
   U           A    unmerged, added by them
   D           U    unmerged, deleted by us
   A           A    unmerged, both added
   U           U    unmerged, both modified
   -------------------------------------------------
   ?           ?    untracked
   !           !    ignored
   -------------------------------------------------
*/
func extractChangeCodes(chunk []byte) []*change {
	x := rune(chunk[0])
	y := rune(chunk[1])

	var changes []*change
	if p := decodePrimaryChangeCode(x, y); p != nil {
		changes = append(changes, p)
	}
	if s := decodeSecondaryChangeCode(x, y); s != nil {
		changes = append(changes, s)
	}
	return changes
}

func decodePrimaryChangeCode(x, y rune) *change {
	switch {
	case x == 'D' && y == 'D': //DD
		return &change{
			"   both deleted",
			del,
			Unmerged,
		}
	case x == 'A' && y == 'U': //AU
		return &change{
			"    added by us",
			neu,
			Unmerged,
		}
	case x == 'U' && y == 'D': //UD
		return &change{
			"deleted by them",
			del,
			Unmerged,
		}
	case x == 'U' && y == 'A': //UA
		return &change{
			"  added by them",
			neu,
			Unmerged,
		}
	case x == 'D' && y == 'U': //DU
		return &change{
			"  deleted by us",
			del,
			Unmerged,
		}
	case x == 'A' && y == 'A': //AA
		return &change{
			"     both added",
			neu,
			Unmerged,
		}
	case x == 'U' && y == 'U': //UU
		return &change{
			"  both modified",
			mod,
			Unmerged,
		}
	case x == 'M': //          //M.
		return &change{
			"  modified",
			mod,
			Staged,
		}
	case x == 'A': //          //A.
		return &change{
			"  new file",
			neu,
			Staged,
		}
	case x == 'D': //          //D.
		return &change{
			"   deleted",
			del,
			Staged,
		}
	case x == 'R': //          //R.
		return &change{
			"   renamed",
			ren,
			Staged,
		}
	case x == 'C': //          //C.
		return &change{
			"    copied",
			cpy,
			Staged,
		}
	case x == 'T': //          //T.
		return &change{
			"typechange",
			typ,
			Staged,
		}
	case x == '?' && y == '?': //??
		return &change{
			" untracked",
			unt,
			Untracked,
		}
	}

	return nil
}

func decodeSecondaryChangeCode(x, y rune) *change {
	switch {
	case y == 'M': //.M
		return &change{
			"  modified",
			mod,
			Unstaged,
		}
	case y == 'D' && x != 'D' && x != 'U': //[!D!U]D
		// Don't show deleted 'y' during a merge conflict.
		return &change{
			"   deleted",
			del,
			Unstaged,
		}
	case y == 'T': //.T
		return &change{
			"typechange",
			typ,
			Unstaged,
		}
	}

	return nil
}
<commit_msg>start cleanup & rationalize for shortcode decoding<commit_after>package status

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
)

// Process takes the raw output of `git status --porcelain -b -z` and turns it
// into a structured data type.
func Process(gitStatusOutput []byte, root string) *StatusList {
	// initialize a statuslist to hold the results
	results := NewStatusList()

	// put the output into a bufferreader+scanner so we can consume it iteratively
	scanner := bufio.NewScanner(bytes.NewReader(gitStatusOutput))

	// the scanner needs a custom split function for splitting on NUL
	scanNul := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		for i, b := range data {
			if b == '\x00' {
				return i + 1, data[:i], nil
			}
		}
		return 0, nil, nil
	}
	scanner.Split(scanNul)

	// branch output is first line
	if !scanner.Scan() {
		log.Println("Failed to read buffer when expecting branch status")
		log.Fatal(scanner.Err())
	}
	branchBytes := scanner.Bytes()
	results.branch = ExtractBranch(branchBytes)

	// give ProcessChanges the scanner and let it handle the rest
	// (it does complicated stuff so it needs the entire scanner)
	for _, r := range ProcessChanges(scanner, root) {
		results.groups[r.group].items = append(results.groups[r.group].items, r)
	}

	return results
}

// ExtractBranch handles parsing the branch status from `status --porcelain -b`.
//
// Examples of stuff we will want to parse:
//
// 		## Initial commit on master
// 		## master
// 		## master...origin/master
// 		## master...origin/master [ahead 1]
//
func ExtractBranch(bs []byte) *BranchInfo {
	b := BranchInfo{}

	b.name = decodeBranchName(bs)
	b.ahead, b.behind = decodeBranchPosition(bs)

	return &b
}

func decodeBranchName(bs []byte) string {
	re := regexp.MustCompile(`^## (?:Initial commit on )?([^ \.]+)`)
	m := re.FindSubmatch(bs)
	if m == nil {
		log.Fatalf("Failed to parse branch name for output: [%s]", bs)
	}

	return string(m[1])
}

func decodeBranchPosition(bs []byte) (ahead, behind int) {
	reA := regexp.MustCompile(`\[ahead ?(\d+).*\]`)
	reB := regexp.MustCompile(`\[.*behind ?(\d+)\]`)

	mA := reA.FindSubmatch(bs)
	if mA != nil {
		ahead, _ = strconv.Atoi(string(mA[1]))
	}

	mB := reB.FindSubmatch(bs)
	if mB != nil {
		behind, _ = strconv.Atoi(string(mB[1]))
	}

	return
}

/*
ProcessChanges takes `git status -z` output and returns all status items.

(Note: in our case, we actually use `git status -bz` and remove branch header
when we process it earlier, but the results are binary identical.)

This is a complicated process because the format is weird. Each line is a
variable length number of columns (2-3), but the separator for 1-2 is a space
(but the content of columns can contain spaces too!), and the seperator for 2-3
is a NUL character (ASCII 0), *if* there is a third column. But here's where it
gets wacky: NUL is also the entry terminator (rather than a LF like in normal
porcelain mode)

Thankfully(?), column 1 which contains the status codes is a fixed length of two
bytes, and in theory the status codes contain enough secrets for us to determine
whether we should expect 2 or 3 columns (current hypothesis is we only get the
third column which is PATH2 when there is a "rename" operation). Sooo... we can
just read those two bytes and use that to determine how many NULs to scan to
until we have consumed a full entry.

We put up with this because it means no shell escaping, which should mean better
cross-platform support. Better hope some Windows people end up using it someday!
*/
func ProcessChanges(s *bufio.Scanner, root string) (results []*StatusItem) {

	// Before we process any changes, get the Current Working Directory.
	// We're going to need use to calculate absolute and relative filepaths for
	// every change, so we get it once now and pass it along.
	// If for some reason this fails (?), fallback to the git worktree root.
	wd, err := os.Getwd()
	if err != nil {
		wd = root
	}

	for s.Scan() {
		chunk := s.Bytes()
		// ...if chunk represents a rename or copy op, need to append another chunk
		// to get the full change item, with NUL manually reinserted because scanner
		// will extract past it.
		if (chunk[0] == 'R' || chunk[0] == 'C') && s.Scan() {
			chunk = append(chunk, '\x00')
			chunk = append(chunk, s.Bytes()...)
		}
		results = append(results, processChange(chunk, wd, root)...)
	}

	return
}

// process change for a single item from a `git status -z`.
//
// Takes raw bytes representing status change from `git status --porcelain -z`,
// assumes that it has already been properly split away from the rest of the
// changes.
//
// See ProcessChanges (plural) for more details on that process.
//
// Note some change items can have multiple statuses, so this returns a slice.
func processChange(chunk []byte, wd, root string) (results []*StatusItem) {

	absolutePath, relativePath := extractFile(chunk, root, wd)

	for _, c := range extractChangeCodes(chunk) {
		result := &StatusItem{
			msg:         c.msg,
			col:         c.col,
			group:       c.group,
			fileAbsPath: absolutePath,
			fileRelPath: relativePath,
		}
		results = append(results, result)
	}

	if len(results) < 1 {
		log.Fatalf(`
Failed to decode git status change code for chunk: [%s]
Please file a bug including this error message as well as the output of:

git status --porcelain

You can file the bug at: https://github.com/mroth/scmpuff/issues/
		`, chunk)
	}
	return results
}

/*
extractFile extracts the filename from a status change, and determines the
absolute and display paths.

 - root: the absolute path to the git working tree
 - wd: current working directory path
*/
func extractFile(chunk []byte, root, wd string) (absPath, relPath string) {
	// file identifier starts at pos4 and continues to EOL
	filePortion := chunk[3:len(chunk)]
	files := bytes.SplitN(filePortion, []byte{'\x00'}, 2)

	n := len(files)
	switch {
	case n < 1:
		log.Fatalf("tried to process a change chunk with no file")
	case n > 1:
		toFile, fromFile := files[0], files[1]
		var toRelPath, fromRelPath string

		absPath, toRelPath = calcPaths(toFile, root, wd)
		_, fromRelPath = calcPaths(fromFile, root, wd)

		relPath = fmt.Sprintf("%s -> %s", fromRelPath, toRelPath)
	default:
		absPath, relPath = calcPaths(files[0], root, wd)
	}

	return
}

// given path of a file relative to git root, git root, and working directory,
// calculate the absolute path of the file on the system, and attempt to figure
// out its relative path to $CWD (if can't, fallback to absolute for both).
func calcPaths(rootPath []byte, root, wd string) (absPath, relPath string) {
	file := rootPath
	absPath = filepath.Join(root, string(file))
	relPath, err := filepath.Rel(wd, absPath)
	if err != nil {
		relPath = absPath
	}
	return
}

// basically a StatusItem minus the file information, for now just being
// used to get results from the change code processing...
// This could probably be encapsulated in StatusItem itself, but wary of adding
// more nesting...
// TODO: should either figure out a way to get rid of this or formalize it more.
type change struct {
	msg   string
	col   ColorGroup
	group StatusGroup
}

/*
Extracts a git status "short code" into the proper UI "change" items we will
display in our status output.

Below documentation from git status:

   Ignored files are not listed, unless --ignored option is in effect, in
   which case XY are !!.

   X          Y     Meaning
   -------------------------------------------------
             [MD]   not updated
   M        [ MD]   updated in index
   A        [ MD]   added to index
   D         [ M]   deleted from index
   R        [ MD]   renamed in index
   C        [ MD]   copied in index
   [MARC]           index and work tree matches
   [ MARC]     M    work tree changed since index
   [ MARC]     D    deleted in work tree
   -------------------------------------------------
   D           D    unmerged, both deleted
   A           U    unmerged, added by us
   U           D    unmerged, deleted by them
   U           A    unmerged, added by them
   D           U    unmerged, deleted by us
   A           A    unmerged, both added
   U           U    unmerged, both modified
   -------------------------------------------------
   ?           ?    untracked
   !           !    ignored
   -------------------------------------------------
*/
func extractChangeCodes(chunk []byte) []*change {
	x := rune(chunk[0])
	y := rune(chunk[1])

	var changes []*change
	if p := decodePrimaryChangeCode(x, y); p != nil {
		changes = append(changes, p)
	}
	if s := decodeSecondaryChangeCode(x, y); s != nil {
		changes = append(changes, s)
	}
	return changes
}

func decodePrimaryChangeCode(x, y rune) *change {
	xy := string(x) + string(y)

	// unmerged cases are simple, only a single change UI is possible
	//
	// TODO: should we handle !! as well? need to determine if possible for user
	// to enable it via their gitconfig where it would affect us.  we coud also
	// allow it to be activated via CLI switch on our end too if desired.
	switch xy {
	case "DD":
		return &change{"   both deleted", del, Unmerged}
	case "AU":
		return &change{"    added by us", neu, Unmerged}
	case "UD":
		return &change{"deleted by them", del, Unmerged}
	case "UA":
		return &change{"  added by them", neu, Unmerged}
	case "DU":
		return &change{"  deleted by us", del, Unmerged}
	case "AA":
		return &change{"     both added", neu, Unmerged}
	case "UU":
		return &change{"  both modified", mod, Unmerged}
	case "??":
		return &change{" untracked", unt, Untracked}
	}

	// staged changes are all single X cases
	// right now we dont need to check the Y, because we consider a modifying Y to
	// these to be a compound case that adds a secondary change in the UI, so that
	// is currently handled in decodeSecondaryChangeCode()
	switch x {
	case 'M':
		return &change{"  modified", mod, Staged}
	case 'A':
		return &change{"  new file", neu, Staged}
	case 'D':
		return &change{"   deleted", del, Staged}
	case 'R':
		return &change{"   renamed", ren, Staged}
	case 'C':
		return &change{"    copied", cpy, Staged}
	case 'T':
		return &change{"typechange", typ, Staged}
	}

	return nil
}

func decodeSecondaryChangeCode(x, y rune) *change {
	switch {
	case y == 'M': //.M
		return &change{"  modified", mod, Unstaged}
	// Don't show deleted 'y' during a merge conflict.
	case y == 'D' && x != 'D' && x != 'U': //[!D!U]D
		return &change{"   deleted", del, Unstaged}
	case y == 'T': //.T
		return &change{"typechange", typ, Unstaged}
	}

	return nil
}
<|endoftext|>


<commit_before>package bufit

import (
	"container/heap"
	"io"
	"sync"
	"sync/atomic"
)

// Reader provides an io.Reader whose methods MUST be concurrent-safe
// with the Write method of the Writer from which it was generated.
// It also MUST be safe for concurrent calls to Writer.Discard
// for bytes which have already been read by this Reader.
type Reader interface {

	// Len returns the unread # of bytes in this Reader
	Len() int

	// Discard drops the next n bytes from the Reader, as if it were Read()
	// it returns the # of bytes actually dropped. It may return io.EOF
	// if all remaining bytes have been discarded.
	Discard(int) (int, error)

	// Read bytes into the provided buffer.
	io.Reader
}

// Writer accepts bytes and generates Readers who consume those bytes.
// Generated Readers methods must be concurrent-safe with the Write method.
type Writer interface {

	// Len returns the # of bytes buffered for Readers
	Len() int

	// Discard drops the next n buffered bytes. It returns the actual number of
	// bytes dropped and may return io.EOF if all remaining bytes have been
	// discarded. Discard must be concurrent-safe with methods calls
	// on generated Readers, when discarding bytes that have been read
	// by all Readers.
	Discard(int) (int, error)

	// NextReader returns a Reader which reads a "snapshot" of the current written bytes
	// (excluding discarded bytes). The Reader should work independently of the Writer
	// and be concurrent-safe with the Write method on the Writer.
	NextReader() Reader

	// Write writes the given bytes into the Writer's underlying buffer. Which will
	// be available for reading using NextReader() to grab a snapshot of the current
	// written bytes.
	io.Writer
}

// Buffer is used to provide multiple readers with access to a shared buffer.
// Readers may join/leave at any time, however a joining reader will only
// see whats currently in the buffer onwards. Data is evicted from the buffer
// once all active readers have read that section.
type Buffer struct {
	mu   sync.RWMutex
	cond *sync.Cond
	off  int
	rh   readerHeap
	buf  Writer
	life
}

type life struct {
	state int32
}

func (l *life) alive() bool { return atomic.LoadInt32(&l.state) == 0 }
func (l *life) kill()       { atomic.AddInt32(&l.state, 1) }

func (b *Buffer) fetch(r *reader) {
	b.mu.Lock()
	defer b.mu.Unlock()

	if r.alive() {
		r.off += r.size
		r.size = 0
		heap.Fix(&b.rh, r.i)
		b.shift()
	}

	for r.off == b.off+b.buf.Len() && b.alive() && r.alive() {
		b.cond.Wait()
	}

	if !r.alive() {
		return
	}

	r.data = b.buf.NextReader()
	r.data.Discard(r.off - b.off)
	r.size = r.data.Len()
}

func (b *Buffer) drop(r *reader) {
	b.mu.Lock()
	defer b.mu.Unlock()
	heap.Remove(&b.rh, r.i)
	b.shift()
}

func (b *Buffer) shift() {
	if b.rh.Len() == 0 {
		return
	}

	if diff := b.rh.Peek().off - b.off; diff > 0 {
		b.buf.Discard(diff)
		b.off += diff
	}
}

// NextReader returns a new ReadCloser for this shared buffer.
// Read/Close are safe to call concurrently with the buffers Write/Close methods.
// Read calls will block if the Buffer is not Closed and contains no data.
func (b *Buffer) NextReader() io.ReadCloser {
	b.mu.Lock()
	defer b.mu.Unlock()
	r := &reader{
		buf:  b,
		size: b.buf.Len(),
		off:  b.off,
		data: b.buf.NextReader(),
	}
	heap.Push(&b.rh, r)
	return r
}

// Write appends the given data to the buffer. All active readers will
// see this write.
func (b *Buffer) Write(p []byte) (int, error) {
	b.mu.Lock()
	defer b.cond.Broadcast()
	defer b.mu.Unlock()
	if !b.alive() {
		return 0, io.ErrClosedPipe
	}
	return b.buf.Write(p)
}

// Close marks the buffer as complete. Readers will return io.EOF instead of blocking
// when they reach the end of the buffer.
func (b *Buffer) Close() error {
	defer b.cond.Broadcast()
	b.kill()
	return nil
}

// NewBuffer creates and returns a new Buffer backed by the passed Writer
func NewBuffer(w Writer) *Buffer {
	buf := Buffer{
		buf: w,
	}
	buf.cond = sync.NewCond(&buf.mu)
	return &buf
}

// New creates and returns a new Buffer
func New() *Buffer {
	return NewBuffer(newWriter(nil))
}
<commit_msg>check buffer alive outside lock first, ensure Close() grabs the lock<commit_after>package bufit

import (
	"container/heap"
	"io"
	"sync"
	"sync/atomic"
)

// Reader provides an io.Reader whose methods MUST be concurrent-safe
// with the Write method of the Writer from which it was generated.
// It also MUST be safe for concurrent calls to Writer.Discard
// for bytes which have already been read by this Reader.
type Reader interface {

	// Len returns the unread # of bytes in this Reader
	Len() int

	// Discard drops the next n bytes from the Reader, as if it were Read()
	// it returns the # of bytes actually dropped. It may return io.EOF
	// if all remaining bytes have been discarded.
	Discard(int) (int, error)

	// Read bytes into the provided buffer.
	io.Reader
}

// Writer accepts bytes and generates Readers who consume those bytes.
// Generated Readers methods must be concurrent-safe with the Write method.
type Writer interface {

	// Len returns the # of bytes buffered for Readers
	Len() int

	// Discard drops the next n buffered bytes. It returns the actual number of
	// bytes dropped and may return io.EOF if all remaining bytes have been
	// discarded. Discard must be concurrent-safe with methods calls
	// on generated Readers, when discarding bytes that have been read
	// by all Readers.
	Discard(int) (int, error)

	// NextReader returns a Reader which reads a "snapshot" of the current written bytes
	// (excluding discarded bytes). The Reader should work independently of the Writer
	// and be concurrent-safe with the Write method on the Writer.
	NextReader() Reader

	// Write writes the given bytes into the Writer's underlying buffer. Which will
	// be available for reading using NextReader() to grab a snapshot of the current
	// written bytes.
	io.Writer
}

// Buffer is used to provide multiple readers with access to a shared buffer.
// Readers may join/leave at any time, however a joining reader will only
// see whats currently in the buffer onwards. Data is evicted from the buffer
// once all active readers have read that section.
type Buffer struct {
	mu   sync.RWMutex
	cond *sync.Cond
	off  int
	rh   readerHeap
	buf  Writer
	life
}

type life struct {
	state int32
}

func (l *life) alive() bool { return atomic.LoadInt32(&l.state) == 0 }
func (l *life) kill()       { atomic.AddInt32(&l.state, 1) }

func (b *Buffer) fetch(r *reader) {
	b.mu.Lock()
	defer b.mu.Unlock()

	if r.alive() {
		r.off += r.size
		r.size = 0
		heap.Fix(&b.rh, r.i)
		b.shift()
	}

	for r.off == b.off+b.buf.Len() && b.alive() && r.alive() {
		b.cond.Wait()
	}

	if !r.alive() {
		return
	}

	r.data = b.buf.NextReader()
	r.data.Discard(r.off - b.off)
	r.size = r.data.Len()
}

func (b *Buffer) drop(r *reader) {
	b.mu.Lock()
	defer b.mu.Unlock()
	heap.Remove(&b.rh, r.i)
	b.shift()
}

func (b *Buffer) shift() {
	if b.rh.Len() == 0 {
		return
	}

	if diff := b.rh.Peek().off - b.off; diff > 0 {
		b.buf.Discard(diff)
		b.off += diff
	}
}

// NextReader returns a new ReadCloser for this shared buffer.
// Read/Close are safe to call concurrently with the buffers Write/Close methods.
// Read calls will block if the Buffer is not Closed and contains no data.
func (b *Buffer) NextReader() io.ReadCloser {
	b.mu.Lock()
	defer b.mu.Unlock()
	r := &reader{
		buf:  b,
		size: b.buf.Len(),
		off:  b.off,
		data: b.buf.NextReader(),
	}
	heap.Push(&b.rh, r)
	return r
}

// Write appends the given data to the buffer. All active readers will
// see this write.
func (b *Buffer) Write(p []byte) (int, error) {
	if !b.alive() {
		return 0, io.ErrClosedPipe
	}

	b.mu.Lock()
	defer b.cond.Broadcast()
	defer b.mu.Unlock()
	if !b.alive() {
		return 0, io.ErrClosedPipe
	}
	return b.buf.Write(p)
}

// Close marks the buffer as complete. Readers will return io.EOF instead of blocking
// when they reach the end of the buffer.
func (b *Buffer) Close() error {
	b.mu.Lock()
	defer b.cond.Broadcast()
	defer b.mu.Unlock()
	b.kill()
	return nil
}

// NewBuffer creates and returns a new Buffer backed by the passed Writer
func NewBuffer(w Writer) *Buffer {
	buf := Buffer{
		buf: w,
	}
	buf.cond = sync.NewCond(&buf.mu)
	return &buf
}

// New creates and returns a new Buffer
func New() *Buffer {
	return NewBuffer(newWriter(nil))
}
<|endoftext|>


<commit_before>package dockercommand

import (
	"archive/tar"
	"bytes"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	docker "github.com/fsouza/go-dockerclient"
)

type BuildOptions struct {
	Dockerfile string
	Path       string
	Tag        string
}

func (dock *Docker) Build(options *BuildOptions) error {
	t := time.Now()

	inputbuf, outputbuf := bytes.NewBuffer(nil), bytes.NewBuffer(nil)

	bytearray, err := ioutil.ReadFile(options.Dockerfile)
	if err != nil {
		return err
	}

	tw := tar.NewWriter(inputbuf)
	if err = tw.WriteHeader(&tar.Header{Name: "/Dockerfile", Size: int64(len(bytearray)), ModTime: t, AccessTime: t, ChangeTime: t}); err != nil {
		return err
	}
	if _, err = tw.Write(bytearray); err != nil {
		return err
	}

	buildContextPath(options.Path, options.Path, tw)

	if err = tw.Close(); err != nil {
		return err
	}
	opts := docker.BuildImageOptions{
		Name:         options.Tag,
		InputStream:  inputbuf,
		OutputStream: outputbuf,
	}
	err = dock.client.BuildImage(opts)
	log.Printf("%s\n", outputbuf.Bytes())
	if err != nil {
		return err
	}
	return nil
}

func buildContextPath(sourcePath, dirPath string, tw *tar.Writer) error {
	dir, err := os.Open(dirPath)
	if err != nil {
		return err
	}
	defer dir.Close()
	fis, err := dir.Readdir(0)
	if err != nil {
		return err
	}
	for _, fi := range fis {
		curPath := dirPath + "/" + fi.Name()
		if fi.IsDir() {
			buildContextPath(sourcePath, curPath, tw)
		} else {
			filePath := strings.Replace(curPath, sourcePath, "", 1)
			tarGzWrite(filePath, curPath, tw, fi)
		}
	}
	return nil
}

func tarGzWrite(tarPath string, _path string, tw *tar.Writer, fi os.FileInfo) error {
	h := new(tar.Header)
	h.Name = tarPath
	h.Size = fi.Size()
	h.Mode = int64(fi.Mode())
	h.ModTime = fi.ModTime()

	bytearray, err := ioutil.ReadFile(_path)
	if err != nil {
		return err
	}

	if err = tw.WriteHeader(h); err != nil {
		return err
	}

	if _, err = tw.Write(bytearray); err != nil {
		return err
	}

	return nil
}
<commit_msg>Logs of a container build are retrievied with a Scanner<commit_after>package dockercommand

import (
	"archive/tar"
	"bufio"
	"bytes"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	docker "github.com/fsouza/go-dockerclient"
)

type BuildOptions struct {
	Dockerfile string
	Path       string
	Tag        string
}

func (dock *Docker) Build(options *BuildOptions) error {
	t := time.Now()

	inputbuf := bytes.NewBuffer(nil)

	bytearray, err := ioutil.ReadFile(options.Dockerfile)
	if err != nil {
		return err
	}

	tw := tar.NewWriter(inputbuf)
	if err = tw.WriteHeader(&tar.Header{Name: "/Dockerfile", Size: int64(len(bytearray)), ModTime: t, AccessTime: t, ChangeTime: t}); err != nil {
		return err
	}
	if _, err = tw.Write(bytearray); err != nil {
		return err
	}

	buildContextPath(options.Path, options.Path, tw)

	if err = tw.Close(); err != nil {
		return err
	}

	logsReader, outputbuf := io.Pipe()
	go func(reader io.Reader) {
		scanner := bufio.NewScanner(reader)
		for scanner.Scan() {
			log.Printf("%s \n", scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			log.Println("There was an error with the scanner in attached container", err)
		}
	}(logsReader)

	opts := docker.BuildImageOptions{
		Name:         options.Tag,
		InputStream:  inputbuf,
		OutputStream: outputbuf,
	}
	err = dock.client.BuildImage(opts)

	if err != nil {
		return err
	}
	return nil
}

func buildContextPath(sourcePath, dirPath string, tw *tar.Writer) error {
	dir, err := os.Open(dirPath)
	if err != nil {
		return err
	}
	defer dir.Close()
	fis, err := dir.Readdir(0)
	if err != nil {
		return err
	}
	for _, fi := range fis {
		curPath := dirPath + "/" + fi.Name()
		if fi.IsDir() {
			buildContextPath(sourcePath, curPath, tw)
		} else {
			filePath := strings.Replace(curPath, sourcePath, "", 1)
			tarGzWrite(filePath, curPath, tw, fi)
		}
	}
	return nil
}

func tarGzWrite(tarPath string, _path string, tw *tar.Writer, fi os.FileInfo) error {
	h := new(tar.Header)
	h.Name = tarPath
	h.Size = fi.Size()
	h.Mode = int64(fi.Mode())
	h.ModTime = fi.ModTime()

	bytearray, err := ioutil.ReadFile(_path)
	if err != nil {
		return err
	}

	if err = tw.WriteHeader(h); err != nil {
		return err
	}

	if _, err = tw.Write(bytearray); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package wellington

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fatih/color"
	libsass "github.com/wellington/go-libsass"
	"github.com/wellington/wellington/types"
)

var testch chan struct{}

// BuildArgs holds universal arguments for a build that the parser
// uses during the initial build and the filewatcher passes back to
// the parser on any file changes.
type BuildArgs struct {
	// Imgs, Sprites spritewell.SafeImageMap
	Payload  types.Payloader
	ImageDir string

	// BuildDir is the base build directory used. When recursive
	// file matching is involved, this directory will be used as the
	// parent.
	BuildDir string
	Includes string
	Font     string
	Gen      string
	Style    int
	Comments bool
}

func (b *BuildArgs) Init() {
	b.Payload = newPayload()
}

// Build holds a set of read only arguments to the builder.
// Channels from this are used to communicate between the workers
// and loaders executing builds.
type Build struct {
	wg      sync.WaitGroup
	closing chan struct{}

	workwg sync.WaitGroup
	err    error
	done   chan error
	status chan error
	queue  chan string

	async      bool
	paths      []string
	bArgs      *BuildArgs
	partialMap *SafePartialMap
}

// NewBuild accepts arguments to reate a new Builder
func NewBuild(paths []string, args *BuildArgs, pMap *SafePartialMap, async bool) *Build {
	return &Build{
		done:   make(chan error),
		status: make(chan error),

		queue:   make(chan string),
		closing: make(chan struct{}),

		paths:      paths,
		bArgs:      args,
		partialMap: pMap,
		async:      async,
	}
}

// ErrParitalMap when no partial map is found
var ErrPartialMap = errors.New("No partial map found")

// Build compiles all valid Sass files found in the passed paths.
// It will block until all files are compiled.
func (b *Build) Run() error {

	if b.partialMap == nil {
		return ErrPartialMap
	}

	b.wg.Add(1)
	go func() {
		defer b.wg.Done()
		b.doBuild()
	}()

	go b.loadWork()

	return <-b.done
}

func (b *Build) loadWork() {
	paths := pathsToFiles(b.paths, true)
	for _, path := range paths {
		b.queue <- path
	}
	close(b.queue)
}

func (b *Build) doBuild() {
	for {
		select {
		case <-b.closing:
			return
		case path := <-b.queue:
			if len(path) == 0 {
				b.workwg.Wait()
				b.done <- nil
				return
			}
			b.workwg.Add(1)
			go func(path string) {
				err := b.build(path)
				defer b.workwg.Done()
				if err != nil {
					b.done <- err
				}
			}(path)
		}
	}
}

func (b *Build) build(path string) error {
	return LoadAndBuild(path, b.bArgs, b.partialMap)
}

// Close shuts down the builder ensuring all go routines have properly
// closed before returning.
func (b *Build) Close() error {
	close(b.closing)
	b.wg.Wait()
	return nil
}

var inputFileTypes = []string{".scss", ".sass"}

func (b *BuildArgs) getOut(path string) (io.WriteCloser, string, error) {
	var (
		out  io.WriteCloser
		fout string
	)
	if b == nil {
		return nil, "", errors.New("build args is nil")
	}
	if len(b.BuildDir) == 0 {
		out = os.Stdout
		return out, "", nil
	}

	// Build output file based off build directory and input filename
	rel, _ := filepath.Rel(b.Includes, filepath.Dir(path))
	filename := updateFileOutputType(filepath.Base(path))
	fout = filepath.Join(b.BuildDir, rel, filename)

	dir := filepath.Dir(fout)
	// FIXME: do this once per Build instead of every file
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		return nil, "", fmt.Errorf("Failed to create directory: %s",
			dir)
	}
	out, err = os.Create(fout)
	if err != nil {
		return nil, "", err
	}

	return out, dir, nil
}

// LoadAndBuild kicks off parser and compiling. It expands directories
// to recursively locate Sass files
// TODO: make this function testable
func LoadAndBuild(path string, gba *BuildArgs, pMap *SafePartialMap) error {
	if len(path) == 0 {
		return errors.New("invalid path passed")
	}

	// file detected!
	if isImportable(path) {
		out, bdir, err := gba.getOut(path)
		if err != nil {
			return err
		}
		return loadAndBuild(path, gba, pMap, out, bdir)
	}

	out, bdir, err := gba.getOut(path)
	log.Println(out, bdir)
	if err != nil {
		return err
	}
	err = loadAndBuild(path, gba, pMap, out, bdir)
	if err != nil {
		return err
	}

	return nil
}

func NewContext(gba *BuildArgs) *libsass.Context {
	ctx := libsass.NewContext()
	ctx.Payload = gba.Payload
	ctx.OutputStyle = gba.Style
	ctx.ImageDir = gba.ImageDir
	ctx.FontDir = gba.Font

	// Ahem... build directory is inferred in loadAndBuild
	// ctx.BuildDir = filepath.Dir(fout)
	ctx.BuildDir = gba.BuildDir
	ctx.GenImgDir = gba.Gen
	// ctx.MainFile = sassFile
	ctx.Comments = gba.Comments

	// This needs to happen at start of context
	// ctx.IncludePaths = []string{filepath.Dir(sassFile)}

	ctx.Imports.Init()
	if gba.Includes != "" {
		ctx.IncludePaths = append(ctx.IncludePaths,
			strings.Split(gba.Includes, ",")...)
	}
	return ctx
}

func loadAndBuild(sassFile string, gba *BuildArgs, partialMap *SafePartialMap, out io.WriteCloser, buildDir string) error {
	defer func() {
		// BuildDir lets us know if we should closer out. If no buildDir,
		// specified out == os.Stdout and do not close. If buildDir != "",
		// then out must be something we should close.
		// This is important, since out can be many things and inspecting
		// them could be race unsafe.
		if len(buildDir) > 0 {
			out.Close()
		}
	}()

	ctx := NewContext(gba)
	// FIXME: moves this elsewhere or make it so it doesn't need to be set
	// Adjust directories if necessary
	if len(ctx.ImageDir) == 0 {
		ctx.ImageDir = filepath.Dir(sassFile)
	}
	ctx.BuildDir = buildDir

	err := ctx.FileCompile(sassFile, out)
	if err != nil {
		return errors.New(color.RedString("%s", err))
	}

	// After building, go-libsass collects a list of files used to build
	// this file. Add these to the partial map and move on.
	for _, inc := range ctx.ResolvedImports {
		partialMap.AddRelation(sassFile, inc)
	}

	go func(file string) {
		select {
		case <-testch:
		default:
			fmt.Printf("Rebuilt: %s\n", file)
		}
	}(sassFile)
	return nil
}

func updateFileOutputType(filename string) string {
	for _, filetype := range inputFileTypes {
		filename = strings.Replace(filename, filetype, ".css", 1)
	}
	return filename
}
<commit_msg>not worth the small performance gain<commit_after>package wellington

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fatih/color"
	libsass "github.com/wellington/go-libsass"
	"github.com/wellington/wellington/types"
)

var testch chan struct{}

// BuildArgs holds universal arguments for a build that the parser
// uses during the initial build and the filewatcher passes back to
// the parser on any file changes.
type BuildArgs struct {
	// Imgs, Sprites spritewell.SafeImageMap
	Payload  types.Payloader
	ImageDir string

	// BuildDir is the base build directory used. When recursive
	// file matching is involved, this directory will be used as the
	// parent.
	BuildDir string
	Includes string
	Font     string
	Gen      string
	Style    int
	Comments bool
}

func (b *BuildArgs) Init() {
	b.Payload = newPayload()
}

// Build holds a set of read only arguments to the builder.
// Channels from this are used to communicate between the workers
// and loaders executing builds.
type Build struct {
	wg      sync.WaitGroup
	closing chan struct{}

	workwg sync.WaitGroup
	err    error
	done   chan error
	status chan error
	queue  chan string

	async      bool
	paths      []string
	bArgs      *BuildArgs
	partialMap *SafePartialMap
}

// NewBuild accepts arguments to reate a new Builder
func NewBuild(paths []string, args *BuildArgs, pMap *SafePartialMap, async bool) *Build {
	return &Build{
		done:   make(chan error),
		status: make(chan error),

		queue:   make(chan string),
		closing: make(chan struct{}),

		paths:      paths,
		bArgs:      args,
		partialMap: pMap,
		async:      async,
	}
}

// ErrParitalMap when no partial map is found
var ErrPartialMap = errors.New("No partial map found")

// Build compiles all valid Sass files found in the passed paths.
// It will block until all files are compiled.
func (b *Build) Run() error {

	if b.partialMap == nil {
		return ErrPartialMap
	}

	b.wg.Add(1)
	go func() {
		defer b.wg.Done()
		b.doBuild()
	}()

	go b.loadWork()

	return <-b.done
}

func (b *Build) loadWork() {
	paths := pathsToFiles(b.paths, true)
	for _, path := range paths {
		b.queue <- path
	}
	close(b.queue)
}

func (b *Build) doBuild() {
	for {
		select {
		case <-b.closing:
			return
		case path := <-b.queue:
			if len(path) == 0 {
				b.workwg.Wait()
				b.done <- nil
				return
			}
			b.workwg.Add(1)
			go func(path string) {
				err := b.build(path)
				defer b.workwg.Done()
				if err != nil {
					b.done <- err
				}
			}(path)
		}
	}
}

func (b *Build) build(path string) error {
	return LoadAndBuild(path, b.bArgs, b.partialMap)
}

// Close shuts down the builder ensuring all go routines have properly
// closed before returning.
func (b *Build) Close() error {
	close(b.closing)
	b.wg.Wait()
	return nil
}

var inputFileTypes = []string{".scss", ".sass"}

func (b *BuildArgs) getOut(path string) (io.WriteCloser, string, error) {
	var (
		out  io.WriteCloser
		fout string
	)
	if b == nil {
		return nil, "", errors.New("build args is nil")
	}
	if len(b.BuildDir) == 0 {
		out = os.Stdout
		return out, "", nil
	}

	// Build output file based off build directory and input filename
	rel, _ := filepath.Rel(b.Includes, filepath.Dir(path))
	filename := updateFileOutputType(filepath.Base(path))
	fout = filepath.Join(b.BuildDir, rel, filename)

	dir := filepath.Dir(fout)
	// FIXME: do this once per Build instead of every file
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		return nil, "", fmt.Errorf("Failed to create directory: %s",
			dir)
	}
	out, err = os.Create(fout)
	if err != nil {
		return nil, "", err
	}

	return out, dir, nil
}

// LoadAndBuild kicks off parser and compiling. It expands directories
// to recursively locate Sass files
// TODO: make this function testable
func LoadAndBuild(path string, gba *BuildArgs, pMap *SafePartialMap) error {
	if len(path) == 0 {
		return errors.New("invalid path passed")
	}

	// file detected!
	if isImportable(path) {
		out, bdir, err := gba.getOut(path)
		if err != nil {
			return err
		}
		return loadAndBuild(path, gba, pMap, out, bdir)
	}

	out, bdir, err := gba.getOut(path)
	log.Println(out, bdir)
	if err != nil {
		return err
	}
	err = loadAndBuild(path, gba, pMap, out, bdir)
	if err != nil {
		return err
	}

	return nil
}

func NewContext(gba *BuildArgs) *libsass.Context {
	ctx := libsass.NewContext()
	ctx.Payload = gba.Payload
	ctx.OutputStyle = gba.Style
	ctx.ImageDir = gba.ImageDir
	ctx.FontDir = gba.Font

	// Ahem... build directory is inferred in loadAndBuild
	// ctx.BuildDir = filepath.Dir(fout)
	ctx.BuildDir = gba.BuildDir
	ctx.GenImgDir = gba.Gen
	// ctx.MainFile = sassFile
	ctx.Comments = gba.Comments

	// This needs to happen at start of context
	// ctx.IncludePaths = []string{filepath.Dir(sassFile)}

	ctx.Imports.Init()
	if gba.Includes != "" {
		ctx.IncludePaths = append(ctx.IncludePaths,
			strings.Split(gba.Includes, ",")...)
	}
	return ctx
}

func loadAndBuild(sassFile string, gba *BuildArgs, partialMap *SafePartialMap, out io.WriteCloser, buildDir string) error {
	defer func() {
		// BuildDir lets us know if we should closer out. If no buildDir,
		// specified out == os.Stdout and do not close. If buildDir != "",
		// then out must be something we should close.
		// This is important, since out can be many things and inspecting
		// them could be race unsafe.
		if len(buildDir) > 0 {
			out.Close()
		}
	}()

	ctx := NewContext(gba)
	// FIXME: moves this elsewhere or make it so it doesn't need to be set
	// Adjust directories if necessary
	if len(ctx.ImageDir) == 0 {
		ctx.ImageDir = filepath.Dir(sassFile)
	}
	ctx.BuildDir = buildDir

	err := ctx.FileCompile(sassFile, out)
	if err != nil {
		return errors.New(color.RedString("%s", err))
	}

	// After building, go-libsass collects a list of files used to build
	// this file. Add these to the partial map and move on.
	for _, inc := range ctx.ResolvedImports {
		partialMap.AddRelation(sassFile, inc)
	}

	// TODO: moves this method to *Build and wait on it to finish
	// go func(file string) {
	select {
	case <-testch:
	default:
		fmt.Printf("Rebuilt: %s\n", sassFile)
	}
	// }(sassFile)
	return nil
}

func updateFileOutputType(filename string) string {
	for _, filetype := range inputFileTypes {
		filename = strings.Replace(filename, filetype, ".css", 1)
	}
	return filename
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/stripe/sequins/blocks"
	"github.com/stripe/sequins/sequencefile"
)

var errFilesChanged = errors.New("the list of remote files changed while building")

// build prepares the version, blocking until all local partitions are ready,
// then returns it. If onlyFromManifest is true, it will only load data on local
// disk from a manifest, and fail otherwise.
func (vs *version) build(files []string) error {
	if vs.blockStore != nil {
		return nil
	}

	if len(files) == 0 {
		log.Println("Version", vs.name, "of", vs.db, "has no data. Loading it anyway.")
		return nil
	}

	var local map[int]bool
	if vs.sequins.peers != nil {
		local = vs.partitions.pickLocalPartitions()
		if len(local) == 0 {
			log.Println("All valid partitions for", vs.db, "version", vs.name,
				"are already spoken for. Consider increasing the replication level.")
			return nil
		}
	}

	log.Println("Loading", vs.db, "version", vs.name, "from",
		vs.sequins.backend.DisplayPath(vs.name), "into local directory", vs.path)

	blockStore, err := vs.createStore(files, local)
	if err != nil {
		return err
	}

	// Verify that the list of files stayed the same.
	newFiles, err := vs.sequins.backend.ListFiles(vs.db, vs.name)
	if err != nil {
		return err
	} else {
		if vs.compareFileSets(files, newFiles) {
			return errFilesChanged
		}
	}

	vs.blockStoreLock.Lock()
	defer vs.blockStoreLock.Unlock()
	vs.blockStore = blockStore
	if vs.partitions != nil {
		vs.partitions.updateLocalPartitions(local)
	}

	return nil
}

func (vs *version) compareFileSets(oldFiles, newFiles []string) bool {
	setOld := make(map[string]bool, len(oldFiles))
	setNew := make(map[string]bool, len(newFiles))
	different := false

	if len(oldFiles) != len(newFiles) {
		log.Printf("Number of files under %s changed (%d vs %d)",
			vs.sequins.backend.DisplayPath(vs.db, vs.name), len(oldFiles), len(newFiles))
		different = true
	}

	for _, f := range oldFiles {
		setOld[f] = true
	}

	for _, f := range newFiles {
		setNew[f] = true
		if !setOld[f] {
			log.Println("New file:", vs.sequins.backend.DisplayPath(vs.db, vs.name, f))
			different = true
		}
	}

	for _, f := range oldFiles {
		if !setNew[f] {
			log.Println("Missing file:", vs.sequins.backend.DisplayPath(vs.db, vs.name, f))
			different = true
		}
	}

	return different
}

// TODO: parallelize files

func (vs *version) createStore(files []string, partitions map[int]bool) (*blocks.BlockStore, error) {
	if _, err := os.Stat(vs.path); err == nil {
		log.Println("Clearing local directory", vs.path)
	}

	os.RemoveAll(vs.path)
	err := os.MkdirAll(vs.path, 0755|os.ModeDir)
	if err != nil {
		return nil, fmt.Errorf("error creating local storage directory: %s", err)
	}

	blockStore := blocks.New(vs.path, len(files), partitions,
		vs.sequins.config.Storage.Compression, vs.sequins.config.Storage.BlockSize)
	for _, file := range files {
		err := vs.addFile(blockStore, file)
		if err != nil {
			return nil, err
		}
	}

	blockStore.Save()
	return blockStore, nil
}

func (vs *version) addFile(bs *blocks.BlockStore, file string) error {
	disp := vs.sequins.backend.DisplayPath(vs.db, vs.name, file)
	log.Println("Reading records from", disp)

	stream, err := vs.sequins.backend.Open(vs.db, vs.name, file)
	if err != nil {
		return fmt.Errorf("reading %s: %s", disp, err)
	}

	sf := sequencefile.New(stream)
	err = sf.ReadHeader()
	if err != nil {
		return fmt.Errorf("reading header from %s: %s", disp, err)
	}

	err = bs.AddFile(sf, vs.sequins.config.ThrottleLoads.Duration)
	if err == blocks.ErrWrongPartition {
		log.Println("Skipping", disp, "because it contains no relevant partitions")
	} else if err != nil {
		return fmt.Errorf("reading %s: %s", disp, err)
	}

	return nil
}
<commit_msg>Fix log line<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/stripe/sequins/blocks"
	"github.com/stripe/sequins/sequencefile"
)

var errFilesChanged = errors.New("the list of remote files changed while building")

// build prepares the version, blocking until all local partitions are ready,
// then returns it. If onlyFromManifest is true, it will only load data on local
// disk from a manifest, and fail otherwise.
func (vs *version) build(files []string) error {
	if vs.blockStore != nil {
		return nil
	}

	if len(files) == 0 {
		log.Println("Version", vs.name, "of", vs.db, "has no data. Loading it anyway.")
		return nil
	}

	var local map[int]bool
	if vs.sequins.peers != nil {
		local = vs.partitions.pickLocalPartitions()
		if len(local) == 0 {
			log.Println("All valid partitions for", vs.db, "version", vs.name,
				"are already spoken for. Consider increasing the replication level.")
			return nil
		}
	}

	log.Println("Loading", vs.db, "version", vs.name, "from",
		vs.sequins.backend.DisplayPath(vs.db, vs.name), "into local directory", vs.path)

	blockStore, err := vs.createStore(files, local)
	if err != nil {
		return err
	}

	// Verify that the list of files stayed the same.
	newFiles, err := vs.sequins.backend.ListFiles(vs.db, vs.name)
	if err != nil {
		return err
	} else {
		if vs.compareFileSets(files, newFiles) {
			return errFilesChanged
		}
	}

	vs.blockStoreLock.Lock()
	defer vs.blockStoreLock.Unlock()
	vs.blockStore = blockStore
	if vs.partitions != nil {
		vs.partitions.updateLocalPartitions(local)
	}

	return nil
}

func (vs *version) compareFileSets(oldFiles, newFiles []string) bool {
	setOld := make(map[string]bool, len(oldFiles))
	setNew := make(map[string]bool, len(newFiles))
	different := false

	if len(oldFiles) != len(newFiles) {
		log.Printf("Number of files under %s changed (%d vs %d)",
			vs.sequins.backend.DisplayPath(vs.db, vs.name), len(oldFiles), len(newFiles))
		different = true
	}

	for _, f := range oldFiles {
		setOld[f] = true
	}

	for _, f := range newFiles {
		setNew[f] = true
		if !setOld[f] {
			log.Println("New file:", vs.sequins.backend.DisplayPath(vs.db, vs.name, f))
			different = true
		}
	}

	for _, f := range oldFiles {
		if !setNew[f] {
			log.Println("Missing file:", vs.sequins.backend.DisplayPath(vs.db, vs.name, f))
			different = true
		}
	}

	return different
}

// TODO: parallelize files

func (vs *version) createStore(files []string, partitions map[int]bool) (*blocks.BlockStore, error) {
	if _, err := os.Stat(vs.path); err == nil {
		log.Println("Clearing local directory", vs.path)
	}

	os.RemoveAll(vs.path)
	err := os.MkdirAll(vs.path, 0755|os.ModeDir)
	if err != nil {
		return nil, fmt.Errorf("error creating local storage directory: %s", err)
	}

	blockStore := blocks.New(vs.path, len(files), partitions,
		vs.sequins.config.Storage.Compression, vs.sequins.config.Storage.BlockSize)
	for _, file := range files {
		err := vs.addFile(blockStore, file)
		if err != nil {
			return nil, err
		}
	}

	blockStore.Save()
	return blockStore, nil
}

func (vs *version) addFile(bs *blocks.BlockStore, file string) error {
	disp := vs.sequins.backend.DisplayPath(vs.db, vs.name, file)
	log.Println("Reading records from", disp)

	stream, err := vs.sequins.backend.Open(vs.db, vs.name, file)
	if err != nil {
		return fmt.Errorf("reading %s: %s", disp, err)
	}

	sf := sequencefile.New(stream)
	err = sf.ReadHeader()
	if err != nil {
		return fmt.Errorf("reading header from %s: %s", disp, err)
	}

	err = bs.AddFile(sf, vs.sequins.config.ThrottleLoads.Duration)
	if err == blocks.ErrWrongPartition {
		log.Println("Skipping", disp, "because it contains no relevant partitions")
	} else if err != nil {
		return fmt.Errorf("reading %s: %s", disp, err)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "./ascii"

func main() {
a := ascii.Ascii
h := ascii.Hello

var con bool

for j := 0; j < len(h); j++ {
for i := 0; i < len(a); i++ {
    if h[j] == a[i] {
      con = true
      break
    } else {
      con = false
    }
  }
    if con == false {
      fmt.Println("index", j, "is not part of ASCII")
    }
    }
  }
}
<commit_msg>Fiksa syntax error i main_ascii_t<commit_after>package main

import "fmt"
import "./ascii"

func main() {
a := ascii.Ascii
h := ascii.Hello

var con bool

for j := 0; j < len(h); j++ {
for i := 0; i < len(a); i++ {
    if h[j] == a[i] {
      con = true
      break
    } else {
      con = false
    }
  }
    if con == false {
      fmt.Println("index", j, "is not part of ASCII")
    }
  }
}
<|endoftext|>


<commit_before>package govaluate

import (
	"testing"
)

/*
	Represents a test of correctly creating a SQL query string from an expression.
*/
type QueryTest struct {
	Name     string
	Input    string
	Expected string
}

func TestSQLSerialization(test *testing.T) {

	testCases := []QueryTest{

		QueryTest{

			Name:     "Single GT",
			Input:    "1 > 0",
			Expected: "1 > 0",
		},
		QueryTest{

			Name:     "Single LT",
			Input:    "0 < 1",
			Expected: "0 < 1",
		},
		QueryTest{

			Name:     "Single GTE",
			Input:    "1 >= 0",
			Expected: "1 >= 0",
		},
		QueryTest{

			Name:     "Single LTE",
			Input:    "0 <= 1",
			Expected: "0 <= 1",
		},
		QueryTest{

			Name:     "Single EQ",
			Input:    "1 == 0",
			Expected: "1 = 0",
		},
		QueryTest{

			Name:     "Single NEQ",
			Input:    "1 != 0",
			Expected: "1 <> 0",
		},

		QueryTest{

			Name:     "Parameter names",
			Input:    "foo == bar",
			Expected: "[foo] = [bar]",
		},
		QueryTest{

			Name:     "Strings",
			Input:    "'foo'",
			Expected: "'foo'",
		},
		QueryTest{

			Name:     "Date format",
			Input:    "'2014-07-04'",
			Expected: "'2014-07-04T00:00:00Z'",
		},
		QueryTest{

			Name:     "Single PLUS",
			Input:    "10 + 10",
			Expected: "10 + 10",
		},
		QueryTest{

			Name:     "Single MINUS",
			Input:    "10 - 10",
			Expected: "10 - 10",
		},
		QueryTest{

			Name:     "Single MULTIPLY",
			Input:    "10 * 10",
			Expected: "10 * 10",
		},
		QueryTest{

			Name:     "Single DIVIDE",
			Input:    "10 / 10",
			Expected: "10 / 10",
		},
		QueryTest{

			Name:     "Single true bool",
			Input:    "true",
			Expected: "1",
		},
		QueryTest{

			Name:     "Single false bool",
			Input:    "false",
			Expected: "0",
		},
		QueryTest{

			Name:     "Single AND",
			Input:    "true && true",
			Expected: "1 AND 1",
		},
		QueryTest{

			Name:     "Single OR",
			Input:    "true || true",
			Expected: "1 OR 1",
		},
		QueryTest{

			Name:     "Clauses",
			Input:    "10 + (foo + bar)",
			Expected: "10 + ( [foo] + [bar] )",
		},
		QueryTest{

			Name:     "Negate prefix",
			Input:    "foo < -1",
			Expected: "[foo] < -1",
		},
	}

	runQueryTests(testCases, test)
}

func runQueryTests(testCases []QueryTest, test *testing.T) {

	var expression *EvaluableExpression
	var actualQuery string
	var err error

	test.Logf("Running %d SQL translation test cases", len(testCases))

	// Run the test cases.
	for _, testCase := range testCases {

		expression, err = NewEvaluableExpression(testCase.Input)

		if err != nil {

			test.Logf("Test '%s' failed to parse: %s", testCase.Name, err)
			test.Logf("Expression: '%s'", testCase.Input)
			test.Fail()
			continue
		}

		actualQuery, err = expression.ToSQLQuery()

		if err != nil {

			test.Logf("Test '%s' failed to create query: %s", testCase.Name, err)
			test.Logf("Expression: '%s'", testCase.Input)
			test.Fail()
			continue
		}

		if actualQuery != testCase.Expected {

			test.Logf("Test '%s' did not create expected query. Actual: '%s'", testCase.Name, actualQuery)
			test.Logf("Actual: '%s', expected '%s'", actualQuery, testCase.Expected)
			test.Fail()
			continue
		}
	}
}
<commit_msg>Added tests for new operators that original ToSQL implementation never supported<commit_after>package govaluate

import (
	"testing"
)

/*
	Represents a test of correctly creating a SQL query string from an expression.
*/
type QueryTest struct {
	Name     string
	Input    string
	Expected string
}

func TestSQLSerialization(test *testing.T) {

	testCases := []QueryTest{

		QueryTest{

			Name:     "Single GT",
			Input:    "1 > 0",
			Expected: "1 > 0",
		},
		QueryTest{

			Name:     "Single LT",
			Input:    "0 < 1",
			Expected: "0 < 1",
		},
		QueryTest{

			Name:     "Single GTE",
			Input:    "1 >= 0",
			Expected: "1 >= 0",
		},
		QueryTest{

			Name:     "Single LTE",
			Input:    "0 <= 1",
			Expected: "0 <= 1",
		},
		QueryTest{

			Name:     "Single EQ",
			Input:    "1 == 0",
			Expected: "1 = 0",
		},
		QueryTest{

			Name:     "Single NEQ",
			Input:    "1 != 0",
			Expected: "1 <> 0",
		},

		QueryTest{

			Name:     "Parameter names",
			Input:    "foo == bar",
			Expected: "[foo] = [bar]",
		},
		QueryTest{

			Name:     "Strings",
			Input:    "'foo'",
			Expected: "'foo'",
		},
		QueryTest{

			Name:     "Date format",
			Input:    "'2014-07-04'",
			Expected: "'2014-07-04T00:00:00Z'",
		},
		QueryTest{

			Name:     "Single PLUS",
			Input:    "10 + 10",
			Expected: "10 + 10",
		},
		QueryTest{

			Name:     "Single MINUS",
			Input:    "10 - 10",
			Expected: "10 - 10",
		},
		QueryTest{

			Name:     "Single MULTIPLY",
			Input:    "10 * 10",
			Expected: "10 * 10",
		},
		QueryTest{

			Name:     "Single DIVIDE",
			Input:    "10 / 10",
			Expected: "10 / 10",
		},
		QueryTest{

			Name:     "Single true bool",
			Input:    "true",
			Expected: "1",
		},
		QueryTest{

			Name:     "Single false bool",
			Input:    "false",
			Expected: "0",
		},
		QueryTest{

			Name:     "Single AND",
			Input:    "true && true",
			Expected: "1 AND 1",
		},
		QueryTest{

			Name:     "Single OR",
			Input:    "true || true",
			Expected: "1 OR 1",
		},
		QueryTest{

			Name:     "Clauses",
			Input:    "10 + (foo + bar)",
			Expected: "10 + ( [foo] + [bar] )",
		},
		QueryTest{

			Name:     "Negate prefix",
			Input:    "foo < -1",
			Expected: "[foo] < -1",
		},
		QueryTest{

			Name:     "Invert prefix",
			Input:    "!(foo > 1)",
			Expected: "NOT ( [foo] > 1 )",
		},
		QueryTest{

			Name:     "Exponent",
			Input:    "1 ** 2",
			Expected: "POW(1, 2)",
		},
		QueryTest{

			Name:     "Modulus",
			Input:    "10 % 2",
			Expected: "MOD(10, 2)",
		},
		QueryTest{

			Name:     "Membership operator",
			Input:    "foo IN (1, 2, 3)",
			Expected: "FIND_IN_SET([foo], '1,2,3') > 0 ",
		},
		QueryTest{

			Name:     "Null coalescence",
			Input:    "foo ?? bar",
			Expected: "COALESCE([foo], [bar])",
		},
		QueryTest{

			Name: 	  "Full ternary",
			Input:	  "[foo] ? 1 : 2",
			Expected: "IF([foo] = 0, 1, 2)",
		},
		QueryTest{

			Name: 	  "Half ternary",
			Input:	  "[foo] ? 1",
			Expected: "IF([foo] = 0, 1)",
		},
		QueryTest{

			Name: 	  "Regex equals",
			Input:	  "'foo' =~ '[fF][oO]+'",
			Expected: "'foo' RLIKE '[fF][oO]+'",
		},
		QueryTest{

			Name: 	  "Regex not-equals",
			Input:	  "'foo' !~ '[fF][oO]+'",
			Expected: "'foo' NOT RLIKE '[fF][oO]+'",
		},
	}

	runQueryTests(testCases, test)
}

func runQueryTests(testCases []QueryTest, test *testing.T) {

	var expression *EvaluableExpression
	var actualQuery string
	var err error

	test.Logf("Running %d SQL translation test cases", len(testCases))

	// Run the test cases.
	for _, testCase := range testCases {

		expression, err = NewEvaluableExpression(testCase.Input)

		if err != nil {

			test.Logf("Test '%s' failed to parse: %s", testCase.Name, err)
			test.Logf("Expression: '%s'", testCase.Input)
			test.Fail()
			continue
		}

		actualQuery, err = expression.ToSQLQuery()

		if err != nil {

			test.Logf("Test '%s' failed to create query: %s", testCase.Name, err)
			test.Logf("Expression: '%s'", testCase.Input)
			test.Fail()
			continue
		}

		if actualQuery != testCase.Expected {

			test.Logf("Test '%s' did not create expected query. Actual: '%s'", testCase.Name, actualQuery)
			test.Logf("Actual: '%s', expected '%s'", actualQuery, testCase.Expected)
			test.Fail()
			continue
		}
	}
}
<|endoftext|>


<commit_before>package cistern

import (
	"fmt"
	"sync"
	"time"
)

type Item struct {
	Object     interface{} // 缓存数据项
	Expiration int64       // 数据项过期时间
}

func (i *Item) Expired() bool {
	if i.Expiration == 0 {
		return false
	}
	return time.Now().UnixNano() > i.Expiration
}

type Cache struct {
	items             map[string]Item
	mu                sync.RWMutex
	defaultExpiration time.Duration
	cleanInterval     time.Duration
	stopClean         chan bool
}

// 创建缓存实例
func NewCache(defaultExpiration, cleanInterval time.Duration) *Cache {
	c := &Cache{
		defaultExpiration: defaultExpiration,
		cleanInterval:     cleanInterval,
		items:             map[string]Item{},
		stopClean:         make(chan bool),
	}
	go c.cleanLoop() // 启动一个gorountine用于清理过期数据项
	return c
}

// 清理过期数据项
func (c *Cache) CleanExpired() {
	now := time.Now().UnixNano()
	c.mu.Lock()
	defer c.mu.Unlock()

	for k, v := range c.items {
		if v.Expiration > 0 && now > v.Expiration {
			c.del(k)
		}
	}
}

func (c *Cache) Set(k string, v interface{}, d time.Duration) {
	var e int64
	if d == 0 {
		d = c.defaultExpiration
	}
	if d > 0 {
		e = time.Now().Add(d).UnixNano()
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[k] = Item{
		Object:     v,
		Expiration: e,
	}
}

func (c *Cache) Get(k string) (interface{}, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.get(k)
}

func (c *Cache) Add(k string, v interface{}, d time.Duration) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	_, found := c.get(k)
	if found {
		return fmt.Errorf("Item %s already exist", k)
	}
	c.set(k, v, d)
	return nil
}

func (c *Cache) Delete(k string) {
	c.mu.Lock()
	c.del(k)
	c.mu.Unlock()
}

func (c *Cache) StopClean() {
	c.stopClean <- true
}

// 删除数据项
func (c *Cache) del(k string) {
	delete(c.items, k)
}

// 不加锁的方法，内部调用
func (c *Cache) get(k string) (interface{}, bool) {
	item, found := c.items[k]
	if !found {
		return nil, false
	}
	if item.Expired() {
		return nil, false
	}
	return item.Object, true
}

func (c *Cache) set(k string, v interface{}, d time.Duration) {
	var e int64
	if d == 0 {
		d = c.defaultExpiration
	}
	if d > 0 {
		e = time.Now().Add(d).UnixNano()
	}
	c.items[k] = Item{
		Object:     v,
		Expiration: e,
	}
}

func (c *Cache) cleanLoop() {
	ticker := time.NewTicker(c.cleanInterval)
	for {
		select {
		case <-ticker.C:
			c.CleanExpired()
		case <-c.stopClean:
			ticker.Stop()
			return
		}
	}
}
<commit_msg>add api:Clean,rename others<commit_after>package cistern

import (
	"fmt"
	"sync"
	"time"
)

type Item struct {
	Object     interface{} // 缓存数据项
	Expiration int64       // 数据项过期时间
}

func (i *Item) Expired() bool {
	if i.Expiration == 0 {
		return false
	}
	return time.Now().UnixNano() > i.Expiration
}

type Cache struct {
	items             map[string]Item
	mu                sync.RWMutex
	defaultExpiration time.Duration
	gcInterval        time.Duration
	stopGC            chan bool
}

// 创建缓存实例
func NewCache(defaultExpiration, gcInterval time.Duration) *Cache {
	c := &Cache{
		defaultExpiration: defaultExpiration,
		gcInterval:        gcInterval,
		items:             map[string]Item{},
		stopGC:            make(chan bool),
	}
	go c.gcLoop() // 启动一个gorountine用于清理过期数据项
	return c
}

// 清理过期数据项
func (c *Cache) GcExpired() {
	now := time.Now().UnixNano()
	c.mu.Lock()
	defer c.mu.Unlock()

	for k, v := range c.items {
		if v.Expiration > 0 && now > v.Expiration {
			c.del(k)
		}
	}
}

func (c *Cache) Set(k string, v interface{}, d time.Duration) {
	var e int64
	if d == 0 {
		d = c.defaultExpiration
	}
	if d > 0 {
		e = time.Now().Add(d).UnixNano()
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[k] = Item{
		Object:     v,
		Expiration: e,
	}
}

func (c *Cache) Get(k string) (interface{}, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.get(k)
}

func (c *Cache) Add(k string, v interface{}, d time.Duration) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	_, found := c.get(k)
	if found {
		return fmt.Errorf("Item %s already exist", k)
	}
	c.set(k, v, d)
	return nil
}

func (c *Cache) Delete(k string) {
	c.mu.Lock()
	c.del(k)
	c.mu.Unlock()
}

// 清空缓存
func (c *Cache) Clean(){
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items = map[string]Item{}
}

func (c *Cache) Count() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return len(c.items)
}

func (c *Cache) StopGC() {
	c.stopGC <- true
}

// 删除数据项
func (c *Cache) del(k string) {
	delete(c.items, k)
}

// 不加锁的方法，内部调用
func (c *Cache) get(k string) (interface{}, bool) {
	item, found := c.items[k]
	if !found {
		return nil, false
	}
	if item.Expired() {
		return nil, false
	}
	return item.Object, true
}

func (c *Cache) set(k string, v interface{}, d time.Duration) {
	var e int64
	if d == 0 {
		d = c.defaultExpiration
	}
	if d > 0 {
		e = time.Now().Add(d).UnixNano()
	}
	c.items[k] = Item{
		Object:     v,
		Expiration: e,
	}
}

func (c *Cache) gcLoop() {
	ticker := time.NewTicker(c.gcInterval)
	for {
		select {
		case <-ticker.C:
			c.GcExpired()
		case <-c.stopGC:
			ticker.Stop()
			return
		}
	}
}
<|endoftext|>


<commit_before>// Simple caching library with expiration capabilities
package cache2go

import (
	"errors"
	"log"
	"sync"
	"time"
)

// Structure of an item in the cache
// data contains the user-set value in the cache
type CacheItem struct {
	sync.RWMutex
	key            interface{}
	data           interface{}
	lifeSpan       time.Duration

	createdOn      time.Time
	accessedOn     time.Time
	accessCount    int64

	// Callback method triggered right before removing the item from the cache
	aboutToExpire func(interface{})
}

// Structure of a table with items in the cache
type CacheTable struct {
	sync.RWMutex
	name            string
	items           map[interface{}]*CacheItem
	cleanupTimer    *time.Timer
	cleanupInterval time.Duration

	logger          *log.Logger

	// Callback method triggered when trying to load a non-existing key
	loadData func(interface{}) *CacheItem

	// Callback method triggered when adding a new item to the cache
	addedItem func(*CacheItem)
	// Callback method triggered before deleting an item from the cache
	aboutToDeleteItem func(*CacheItem)
}

var (
	cache = make(map[string]*CacheTable)
	mutex sync.RWMutex
)

// Returns a newly created CacheItem
func CreateCacheItem(key interface{}, lifeSpan time.Duration, data interface{}) CacheItem {
	t := time.Now()
	item := CacheItem{
		key: key,
		lifeSpan: lifeSpan,
		createdOn: t,
		accessedOn: t,
		accessCount: 0,
		aboutToExpire: nil,
		data: data,
	}

	return item
}

// Mark item to be kept for another expireDuration period
func (item *CacheItem) KeepAlive() {
	item.Lock()
	defer item.Unlock()
	item.accessedOn = time.Now()
	item.accessCount++
}

// Returns this item's expiration duration
func (item *CacheItem) LifeSpan() time.Duration {
	// immutable
	return item.lifeSpan
}

// Returns when this item was last accessed
func (item *CacheItem) AccessedOn() time.Time {
	item.RLock()
	defer item.RUnlock()
	return item.accessedOn
}

// Returns when this item was added to the cache
func (item *CacheItem) CreatedOn() time.Time {
	// immutable
	return item.createdOn
}

// Returns how often this item has been accessed
func (item *CacheItem) AccessCount() int64 {
	item.RLock()
	defer item.RUnlock()
	return item.accessCount
}

// Returns the key of this cached item
func (item *CacheItem) Key() interface{} {
	// immutable
	return item.key
}

// Returns the value of this cached item
func (item *CacheItem) Data() interface{} {
	// immutable
	return item.data
}

// Configures a callback, which will be called right before the item
// is about to be removed from the cache
func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {
	item.Lock()
	defer item.Unlock()
	item.aboutToExpire = f
}

// Returns the existing cache table with given name or creates a new one
// if the table does not exist yet
func Cache(table string) *CacheTable {
	mutex.RLock()
	t, ok := cache[table]
	mutex.RUnlock()

	if !ok {
		t = &CacheTable{
			name:  table,
			items: make(map[interface{}]*CacheItem),
		}

		mutex.Lock()
		cache[table] = t
		mutex.Unlock()
	}

	return t
}

// Returns how many items are currently stored in the cache
func (table *CacheTable) Count() int {
	table.RLock()
	defer table.RUnlock()

	return len(table.items)
}

// Configures a data-loader callback, which will be called when trying
// to use access a non-existing key
func (table *CacheTable) SetDataLoader(f func(interface{}) *CacheItem) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// Configures a callback, which will be called every time a new item
// is added to the cache
func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = f
}

// Configures a callback, which will be called every time an item
// is about to be removed from the cache
func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = f
}

// Sets the logger to be used by this cache table
func (table *CacheTable) SetLogger(logger *log.Logger) {
	table.Lock()
	defer table.Unlock()
	table.logger = logger
}

// Expiration check loop, triggered by a self-adjusting timer
func (table *CacheTable) expirationCheck() {
	table.Lock()
	table.log("Expiration check triggered after", table.cleanupInterval , "for table", table.name)
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}

	// Take a copy of cache so we can iterate over it without blocking the mutex
	cc := table.items
	table.Unlock()

	// To be more accurate with timers, we would need to update 'now' on every
	// loop iteration. Not sure it's really efficient though.
	now := time.Now()
	smallestDuration := 0 * time.Second
	for key, c := range cc {
		c.RLock()
		defer c.RUnlock()

		if c.lifeSpan == 0 {
			continue
		}
		if now.Sub(c.accessedOn) >= c.lifeSpan {
			table.Delete(key)
		} else {
			if smallestDuration == 0 || c.lifeSpan < smallestDuration {
				smallestDuration = c.lifeSpan - now.Sub(c.accessedOn)
			}
		}
	}

	table.Lock()
	table.cleanupInterval = smallestDuration
	if smallestDuration > 0 {
		table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
			go table.expirationCheck()
		})
	}
	table.Unlock()
}

/* Adds a key/value pair to the cache
 / key is a unique cache-item key in the cache
 / lifeSpan indicates how long this item will remain in the cache after its
 / last access
 / data is the cache-item value
*/
func (table *CacheTable) Cache(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
	item := CreateCacheItem(key, lifeSpan, data)

	table.Lock()
	table.log("Adding item with key", key, "and lifespan of", lifeSpan, "to table", table.name)
	table.items[key] = &item
	expDur := table.cleanupInterval
	table.Unlock()

	// Trigger callback after adding an item to cache
	if table.addedItem != nil {
		table.addedItem(&item)
	}

	// If we haven't set up any expiration check timer or found a more imminent item
	if lifeSpan > 0 && (expDur == 0 || lifeSpan < expDur) {
		table.expirationCheck()
	}

	return &item
}

// Delete an item from the cache
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key]

	if !ok {
		table.RUnlock()
		return nil, errors.New("Key not found in cache")
	}

	// Trigger callbacks before deleting an item from cache
	if table.aboutToDeleteItem != nil {
		table.aboutToDeleteItem(r)
	}
	table.RUnlock()
	r.RLock()
	defer r.RUnlock()
	if r.aboutToExpire != nil {
		r.aboutToExpire(key)
	}

	table.Lock()
	defer table.Unlock()

	table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount,"times from table", table.name)
	delete(table.items, key)
	return r, nil
}

// Test whether an item exists in the cache. Unlike the Value method
// Exists never tries to fetch data via the loadData callback
func (table *CacheTable) Exists(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.items[key]

	return ok
}

// Get an item from the cache and mark it to be kept alive
func (table *CacheTable) Value(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key];
	table.RUnlock()

	if ok {
		r.KeepAlive()
		return r, nil
	}

	if table.loadData != nil {
		item := table.loadData(key)
		table.Cache(key, item.lifeSpan, item.data)
		if item != nil {
			return item, nil
		}

		return nil, errors.New("Key not found and could not be loaded into cache")
	}

	return nil, errors.New("Key not found in cache")
}

// Delete all items from cache
func (table *CacheTable) Flush() {
	table.Lock()
	defer table.Unlock()

	table.log("Flushing table", table.name)

	table.items = make(map[interface{}]*CacheItem)
	table.cleanupInterval = 0
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
}

// Get an item from the cache and mark it to be kept alive
func (table *CacheTable) log(v ...interface{}) {
	if table.logger == nil {
		return
	}

	table.logger.Println(v)
}
<commit_msg>* Different log for initial expiration check of a table.<commit_after>// Simple caching library with expiration capabilities
package cache2go

import (
	"errors"
	"log"
	"sync"
	"time"
)

// Structure of an item in the cache
// data contains the user-set value in the cache
type CacheItem struct {
	sync.RWMutex
	key            interface{}
	data           interface{}
	lifeSpan       time.Duration

	createdOn      time.Time
	accessedOn     time.Time
	accessCount    int64

	// Callback method triggered right before removing the item from the cache
	aboutToExpire func(interface{})
}

// Structure of a table with items in the cache
type CacheTable struct {
	sync.RWMutex
	name            string
	items           map[interface{}]*CacheItem
	cleanupTimer    *time.Timer
	cleanupInterval time.Duration

	logger          *log.Logger

	// Callback method triggered when trying to load a non-existing key
	loadData func(interface{}) *CacheItem

	// Callback method triggered when adding a new item to the cache
	addedItem func(*CacheItem)
	// Callback method triggered before deleting an item from the cache
	aboutToDeleteItem func(*CacheItem)
}

var (
	cache = make(map[string]*CacheTable)
	mutex sync.RWMutex
)

// Returns a newly created CacheItem
func CreateCacheItem(key interface{}, lifeSpan time.Duration, data interface{}) CacheItem {
	t := time.Now()
	item := CacheItem{
		key: key,
		lifeSpan: lifeSpan,
		createdOn: t,
		accessedOn: t,
		accessCount: 0,
		aboutToExpire: nil,
		data: data,
	}

	return item
}

// Mark item to be kept for another expireDuration period
func (item *CacheItem) KeepAlive() {
	item.Lock()
	defer item.Unlock()
	item.accessedOn = time.Now()
	item.accessCount++
}

// Returns this item's expiration duration
func (item *CacheItem) LifeSpan() time.Duration {
	// immutable
	return item.lifeSpan
}

// Returns when this item was last accessed
func (item *CacheItem) AccessedOn() time.Time {
	item.RLock()
	defer item.RUnlock()
	return item.accessedOn
}

// Returns when this item was added to the cache
func (item *CacheItem) CreatedOn() time.Time {
	// immutable
	return item.createdOn
}

// Returns how often this item has been accessed
func (item *CacheItem) AccessCount() int64 {
	item.RLock()
	defer item.RUnlock()
	return item.accessCount
}

// Returns the key of this cached item
func (item *CacheItem) Key() interface{} {
	// immutable
	return item.key
}

// Returns the value of this cached item
func (item *CacheItem) Data() interface{} {
	// immutable
	return item.data
}

// Configures a callback, which will be called right before the item
// is about to be removed from the cache
func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {
	item.Lock()
	defer item.Unlock()
	item.aboutToExpire = f
}

// Returns the existing cache table with given name or creates a new one
// if the table does not exist yet
func Cache(table string) *CacheTable {
	mutex.RLock()
	t, ok := cache[table]
	mutex.RUnlock()

	if !ok {
		t = &CacheTable{
			name:  table,
			items: make(map[interface{}]*CacheItem),
		}

		mutex.Lock()
		cache[table] = t
		mutex.Unlock()
	}

	return t
}

// Returns how many items are currently stored in the cache
func (table *CacheTable) Count() int {
	table.RLock()
	defer table.RUnlock()

	return len(table.items)
}

// Configures a data-loader callback, which will be called when trying
// to use access a non-existing key
func (table *CacheTable) SetDataLoader(f func(interface{}) *CacheItem) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// Configures a callback, which will be called every time a new item
// is added to the cache
func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = f
}

// Configures a callback, which will be called every time an item
// is about to be removed from the cache
func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = f
}

// Sets the logger to be used by this cache table
func (table *CacheTable) SetLogger(logger *log.Logger) {
	table.Lock()
	defer table.Unlock()
	table.logger = logger
}

// Expiration check loop, triggered by a self-adjusting timer
func (table *CacheTable) expirationCheck() {
	table.Lock()
	if table.cleanupInterval > 0 {
		table.log("Expiration check triggered after", table.cleanupInterval , "for table", table.name)
	} else {
		table.log("Expiration check installed for table", table.name)
	}
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}

	// Take a copy of cache so we can iterate over it without blocking the mutex
	cc := table.items
	table.Unlock()

	// To be more accurate with timers, we would need to update 'now' on every
	// loop iteration. Not sure it's really efficient though.
	now := time.Now()
	smallestDuration := 0 * time.Second
	for key, c := range cc {
		c.RLock()
		defer c.RUnlock()

		if c.lifeSpan == 0 {
			continue
		}
		if now.Sub(c.accessedOn) >= c.lifeSpan {
			table.Delete(key)
		} else {
			if smallestDuration == 0 || c.lifeSpan < smallestDuration {
				smallestDuration = c.lifeSpan - now.Sub(c.accessedOn)
			}
		}
	}

	table.Lock()
	table.cleanupInterval = smallestDuration
	if smallestDuration > 0 {
		table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
			go table.expirationCheck()
		})
	}
	table.Unlock()
}

/* Adds a key/value pair to the cache
 / key is a unique cache-item key in the cache
 / lifeSpan indicates how long this item will remain in the cache after its
 / last access
 / data is the cache-item value
*/
func (table *CacheTable) Cache(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
	item := CreateCacheItem(key, lifeSpan, data)

	table.Lock()
	table.log("Adding item with key", key, "and lifespan of", lifeSpan, "to table", table.name)
	table.items[key] = &item
	expDur := table.cleanupInterval
	table.Unlock()

	// Trigger callback after adding an item to cache
	if table.addedItem != nil {
		table.addedItem(&item)
	}

	// If we haven't set up any expiration check timer or found a more imminent item
	if lifeSpan > 0 && (expDur == 0 || lifeSpan < expDur) {
		table.expirationCheck()
	}

	return &item
}

// Delete an item from the cache
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key]

	if !ok {
		table.RUnlock()
		return nil, errors.New("Key not found in cache")
	}

	// Trigger callbacks before deleting an item from cache
	if table.aboutToDeleteItem != nil {
		table.aboutToDeleteItem(r)
	}
	table.RUnlock()
	r.RLock()
	defer r.RUnlock()
	if r.aboutToExpire != nil {
		r.aboutToExpire(key)
	}

	table.Lock()
	defer table.Unlock()

	table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount,"times from table", table.name)
	delete(table.items, key)
	return r, nil
}

// Test whether an item exists in the cache. Unlike the Value method
// Exists never tries to fetch data via the loadData callback
func (table *CacheTable) Exists(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.items[key]

	return ok
}

// Get an item from the cache and mark it to be kept alive
func (table *CacheTable) Value(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key];
	table.RUnlock()

	if ok {
		r.KeepAlive()
		return r, nil
	}

	if table.loadData != nil {
		item := table.loadData(key)
		table.Cache(key, item.lifeSpan, item.data)
		if item != nil {
			return item, nil
		}

		return nil, errors.New("Key not found and could not be loaded into cache")
	}

	return nil, errors.New("Key not found in cache")
}

// Delete all items from cache
func (table *CacheTable) Flush() {
	table.Lock()
	defer table.Unlock()

	table.log("Flushing table", table.name)

	table.items = make(map[interface{}]*CacheItem)
	table.cleanupInterval = 0
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
}

// Get an item from the cache and mark it to be kept alive
func (table *CacheTable) log(v ...interface{}) {
	if table.logger == nil {
		return
	}

	table.logger.Println(v)
}
<|endoftext|>