<commit_before>package manager

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/pkg/errors"
)

type RenderInput struct {
	Backup    bool
	Contents  []byte
	Dry       bool
	DryStream io.Writer
	Path      string
	Perms     os.FileMode
}

type RenderResult struct {
	DidRender   bool
	WouldRender bool
}

// Render atomically renders a file contents to disk, returning a result of
// whether it would have rendered and actually did render.
func Render(i *RenderInput) (*RenderResult, error) {
	existing, err := ioutil.ReadFile(i.Path)
	if err != nil && !os.IsNotExist(err) {
		return nil, errors.Wrap(err, "failed reading file")
	}

	if bytes.Equal(existing, i.Contents) {
		return &RenderResult{
			DidRender:   false,
			WouldRender: true,
		}, nil
	}

	if i.Dry {
		fmt.Fprintf(i.DryStream, "> %s\n%s", i.Path, i.Contents)
	} else {
		if err := AtomicWrite(i.Path, i.Contents, i.Perms, i.Backup); err != nil {
			return nil, errors.Wrap(err, "failed writing file")
		}
	}

	return &RenderResult{
		DidRender:   true,
		WouldRender: true,
	}, nil
}

// AtomicWrite accepts a destination path and the template contents. It writes
// the template contents to a TempFile on disk, returning if any errors occur.
//
// If the parent destination directory does not exist, it will be created
// automatically with permissions 0755. To use a different permission, create
// the directory first or use `chmod` in a Command.
//
// If the destination path exists, all attempts will be made to preserve the
// existing file permissions. If those permissions cannot be read, an error is
// returned. If the file does not exist, it will be created automatically with
// permissions 0644. To use a different permission, create the destination file
// first or use `chmod` in a Command.
//
// If no errors occur, the Tempfile is "renamed" (moved) to the destination
// path.
func AtomicWrite(path string, contents []byte, perms os.FileMode, backup bool) error {
	if path == "" {
		return fmt.Errorf("missing destination")
	}

	parent := filepath.Dir(path)
	if _, err := os.Stat(parent); os.IsNotExist(err) {
		if err := os.MkdirAll(parent, 0755); err != nil {
			return err
		}
	}

	f, err := ioutil.TempFile(parent, "")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())

	if _, err := f.Write(contents); err != nil {
		return err
	}

	if err := f.Sync(); err != nil {
		return err
	}

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

	if err := os.Chmod(f.Name(), perms); err != nil {
		return err
	}

	// If we got this far, it means we are about to save the file. Copy the
	// current contents of the file onto disk (if it exists) so we have a backup.
	if backup {
		if _, err := os.Stat(path); !os.IsNotExist(err) {
			if err := copyFile(path, path+".bak"); err != nil {
				return err
			}
		}
	}

	if err := os.Rename(f.Name(), path); err != nil {
		return err
	}

	return nil
}

// copyFile copies the file at src to the path at dst. Any errors that occur
// are returned.
func copyFile(src, dst string) error {
	s, err := os.Open(src)
	if err != nil {
		return err
	}
	defer s.Close()

	stat, err := s.Stat()
	if err != nil {
		return err
	}

	d, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, stat.Mode())
	if err != nil {
		return err
	}
	if _, err := io.Copy(d, s); err != nil {
		d.Close()
		return err
	}
	return d.Close()
}
<commit_msg>Return the rendered contents of the template as part of the render event<commit_after>package manager

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/pkg/errors"
)

// RenderInput is used as input to the render function.
type RenderInput struct {
	Backup    bool
	Contents  []byte
	Dry       bool
	DryStream io.Writer
	Path      string
	Perms     os.FileMode
}

// RenderResult is returned and stored. It contains the status of the render
// operationg.
type RenderResult struct {
	// DidRender indicates if the template rendered to disk. This will be false in
	// the event of an error, but it will also be false in dry mode or when the
	// template on disk matches the new result.
	DidRender bool

	// WouldRender indicates if the template would have rendered to disk. This
	// will return false in the event of an error, but will return true in dry
	// mode or when the template on disk matches the new result.
	WouldRender bool

	// Contents are the actual contents of the resulting template from the render
	// operation.
	Contents []byte
}

// Render atomically renders a file contents to disk, returning a result of
// whether it would have rendered and actually did render.
func Render(i *RenderInput) (*RenderResult, error) {
	existing, err := ioutil.ReadFile(i.Path)
	if err != nil && !os.IsNotExist(err) {
		return nil, errors.Wrap(err, "failed reading file")
	}

	if bytes.Equal(existing, i.Contents) {
		return &RenderResult{
			DidRender:   false,
			WouldRender: true,
			Contents:    existing,
		}, nil
	}

	if i.Dry {
		fmt.Fprintf(i.DryStream, "> %s\n%s", i.Path, i.Contents)
	} else {
		if err := AtomicWrite(i.Path, i.Contents, i.Perms, i.Backup); err != nil {
			return nil, errors.Wrap(err, "failed writing file")
		}
	}

	return &RenderResult{
		DidRender:   true,
		WouldRender: true,
		Contents:    existing,
	}, nil
}

// AtomicWrite accepts a destination path and the template contents. It writes
// the template contents to a TempFile on disk, returning if any errors occur.
//
// If the parent destination directory does not exist, it will be created
// automatically with permissions 0755. To use a different permission, create
// the directory first or use `chmod` in a Command.
//
// If the destination path exists, all attempts will be made to preserve the
// existing file permissions. If those permissions cannot be read, an error is
// returned. If the file does not exist, it will be created automatically with
// permissions 0644. To use a different permission, create the destination file
// first or use `chmod` in a Command.
//
// If no errors occur, the Tempfile is "renamed" (moved) to the destination
// path.
func AtomicWrite(path string, contents []byte, perms os.FileMode, backup bool) error {
	if path == "" {
		return fmt.Errorf("missing destination")
	}

	parent := filepath.Dir(path)
	if _, err := os.Stat(parent); os.IsNotExist(err) {
		if err := os.MkdirAll(parent, 0755); err != nil {
			return err
		}
	}

	f, err := ioutil.TempFile(parent, "")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())

	if _, err := f.Write(contents); err != nil {
		return err
	}

	if err := f.Sync(); err != nil {
		return err
	}

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

	if err := os.Chmod(f.Name(), perms); err != nil {
		return err
	}

	// If we got this far, it means we are about to save the file. Copy the
	// current contents of the file onto disk (if it exists) so we have a backup.
	if backup {
		if _, err := os.Stat(path); !os.IsNotExist(err) {
			if err := copyFile(path, path+".bak"); err != nil {
				return err
			}
		}
	}

	if err := os.Rename(f.Name(), path); err != nil {
		return err
	}

	return nil
}

// copyFile copies the file at src to the path at dst. Any errors that occur
// are returned.
func copyFile(src, dst string) error {
	s, err := os.Open(src)
	if err != nil {
		return err
	}
	defer s.Close()

	stat, err := s.Stat()
	if err != nil {
		return err
	}

	d, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, stat.Mode())
	if err != nil {
		return err
	}
	if _, err := io.Copy(d, s); err != nil {
		d.Close()
		return err
	}
	return d.Close()
}
<|endoftext|>


<commit_before>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/
package param

import (
	"strconv"
)

type StringSlice []string

func (p StringSlice) String() []string {
	return []string(p)
}

func (p StringSlice) Int(filters ...func(int) bool) []int {
	var filter func(int) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []int
	for _, id := range p {
		i, _ := strconv.Atoi(id)
		if filter == nil || filter(i) {
			ids = append(ids, i)
		}
	}
	return ids
}

func (p StringSlice) Int64(filters ...func(int64) bool) []int64 {
	var filter func(int64) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []int64
	for _, id := range p {
		i, _ := strconv.ParseInt(id, 10, 64)
		if filter == nil || filter(i) {
			ids = append(ids, i)
		}
	}
	return ids
}

func (p StringSlice) Int32(filters ...func(int32) bool) []int32 {
	var filter func(int32) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []int32
	for _, id := range p {
		i, _ := strconv.ParseInt(id, 10, 32)
		iv := int32(i)
		if filter == nil || filter(iv) {
			ids = append(ids, iv)
		}
	}
	return ids
}

func (p StringSlice) Uint(filters ...func(uint) bool) []uint {
	var filter func(uint) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []uint
	for _, id := range p {
		i, _ := strconv.ParseUint(id, 10, 64)
		iv := uint(i)
		if filter == nil || filter(iv) {
			ids = append(ids, iv)
		}
	}
	return ids
}

func (p StringSlice) Uint64(filters ...func(uint64) bool) []uint64 {
	var filter func(uint64) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []uint64
	for _, id := range p {
		i, _ := strconv.ParseUint(id, 10, 64)
		if filter == nil || filter(i) {
			ids = append(ids, i)
		}
	}
	return ids
}

func (p StringSlice) Uint32(filters ...func(uint32) bool) []uint32 {
	var filter func(uint32) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []uint32
	for _, id := range p {
		i, _ := strconv.ParseUint(id, 10, 32)
		iv := uint32(i)
		if filter == nil || filter(iv) {
			ids = append(ids, iv)
		}
	}
	return ids
}

func (p StringSlice) Float32(filters ...func(float32) bool) []float32 {
	var filter func(float32) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var values []float32
	for _, v := range p {
		i, _ := strconv.ParseFloat(v, 32)
		iv := float32(i)
		if filter == nil || filter(iv) {
			values = append(values, iv)
		}
	}
	return values
}

func (p StringSlice) Float64(filters ...func(float64) bool) []float64 {
	var filter func(float64) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var values []float64
	for _, v := range p {
		i, _ := strconv.ParseFloat(v, 64)
		if filter == nil || filter(i) {
			values = append(values, i)
		}
	}
	return values
}

func (p StringSlice) Bool(filters ...func(bool) bool) []bool {
	var filter func(bool) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var values []bool
	for _, v := range p {
		i, e := strconv.ParseBool(v)
		if e != nil {
			continue
		}
		if filter == nil || filter(i) {
			values = append(values, i)
		}
	}
	return values
}
<commit_msg>update<commit_after>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/
package param

import (
	"strconv"
)

type StringSlice []string

func (p StringSlice) String() []string {
	return []string(p)
}

func (p StringSlice) Int(filters ...func(int, int) bool) []int {
	var filter func(int, int) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []int
	for idx, id := range p {
		i, _ := strconv.Atoi(id)
		if filter == nil || filter(idx, i) {
			ids = append(ids, i)
		}
	}
	return ids
}

func (p StringSlice) Int64(filters ...func(int, int64) bool) []int64 {
	var filter func(int, int64) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []int64
	for idx, id := range p {
		i, _ := strconv.ParseInt(id, 10, 64)
		if filter == nil || filter(idx, i) {
			ids = append(ids, i)
		}
	}
	return ids
}

func (p StringSlice) Int32(filters ...func(int, int32) bool) []int32 {
	var filter func(int, int32) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []int32
	for idx, id := range p {
		i, _ := strconv.ParseInt(id, 10, 32)
		iv := int32(i)
		if filter == nil || filter(idx, iv) {
			ids = append(ids, iv)
		}
	}
	return ids
}

func (p StringSlice) Uint(filters ...func(int, uint) bool) []uint {
	var filter func(int, uint) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []uint
	for idx, id := range p {
		i, _ := strconv.ParseUint(id, 10, 64)
		iv := uint(i)
		if filter == nil || filter(idx, iv) {
			ids = append(ids, iv)
		}
	}
	return ids
}

func (p StringSlice) Uint64(filters ...func(int, uint64) bool) []uint64 {
	var filter func(int, uint64) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []uint64
	for idx, id := range p {
		i, _ := strconv.ParseUint(id, 10, 64)
		if filter == nil || filter(idx, i) {
			ids = append(ids, i)
		}
	}
	return ids
}

func (p StringSlice) Uint32(filters ...func(int, uint32) bool) []uint32 {
	var filter func(int, uint32) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var ids []uint32
	for idx, id := range p {
		i, _ := strconv.ParseUint(id, 10, 32)
		iv := uint32(i)
		if filter == nil || filter(idx, iv) {
			ids = append(ids, iv)
		}
	}
	return ids
}

func (p StringSlice) Float32(filters ...func(int, float32) bool) []float32 {
	var filter func(int, float32) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var values []float32
	for idx, v := range p {
		i, _ := strconv.ParseFloat(v, 32)
		iv := float32(i)
		if filter == nil || filter(idx, iv) {
			values = append(values, iv)
		}
	}
	return values
}

func (p StringSlice) Float64(filters ...func(int, float64) bool) []float64 {
	var filter func(int, float64) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var values []float64
	for idx, v := range p {
		i, _ := strconv.ParseFloat(v, 64)
		if filter == nil || filter(idx, i) {
			values = append(values, i)
		}
	}
	return values
}

func (p StringSlice) Bool(filters ...func(int, bool) bool) []bool {
	var filter func(int, bool) bool
	if len(filters) > 0 {
		filter = filters[0]
	}
	var values []bool
	for idx, v := range p {
		i, e := strconv.ParseBool(v)
		if e != nil {
			continue
		}
		if filter == nil || filter(idx, i) {
			values = append(values, i)
		}
	}
	return values
}
<|endoftext|>


<commit_before>package parser

import (
	"testing"
)

var repoMap = map[string]string{
	"df":  "zerowidth/dotfiles",
	"df2": "zerowidth/dotfiles2", // prefix collision
}

func TestParse(t *testing.T) {
	repoTests := []struct {
		input   string // the input
		repo    string // the expected repo match or expansion
		example string // what the case is an example of
	}{
		{"", "", "no input, no repo match"},
		{"df", "zerowidth/dotfiles", "match shorthand"},
		{" df", "", "no match, leading space"},
		{"foo/bar", "foo/bar", "fully qualified repo name"},
	}

	repoIssueTests := []struct {
		input   string // the input
		repo    string // the expected repo match or expansion
		issue   string // the expected issue match
		example string // what the case is an example of
	}{
		{"", "", "", "no issue no repo"},
		{"df 123", "zerowidth/dotfiles", "123", "normal expansion"},
		{"df#123", "zerowidth/dotfiles", "123", "expansion with #"},
		{"df #123", "zerowidth/dotfiles", "123", "space and # both"},
		{"df123", "zerowidth/dotfiles", "123", "prefix match"},
		{"df2 34", "zerowidth/dotfiles2", "34", "numeric suffix on shorthand"},
		{"df234", "zerowidth/dotfiles2", "34", "numerix suffix with no space"},
		{"foo/bar 123", "foo/bar", "123", "fully qualified repo"},
		{"df 0123", "zerowidth/dotfiles", "", "invalid issue"},
	}

	for _, tc := range repoTests {
		result := Parse(repoMap, tc.input)
		if result.Repo != tc.repo {
			t.Errorf("Parse(%#v): %s: expected repo %#v, got %#v", tc.input, tc.example, tc.repo, result.Repo)
		}
	}

	for _, tc := range repoIssueTests {
		result := Parse(repoMap, tc.input)
		if result.Repo != tc.repo {
			t.Errorf("Parse(%#v): %s: expected repo %#v, got %#v", tc.input, tc.example, tc.repo, result.Repo)
		}
		if result.Issue != tc.issue {
			t.Errorf("Parse(%#v): %s: expected issue %#v, got %#v", tc.input, tc.example, tc.issue, result.Issue)
		}
	}

}
<commit_msg>Subtests, because why not<commit_after>package parser

import (
	"fmt"
	"testing"
)

var repoMap = map[string]string{
	"df":  "zerowidth/dotfiles",
	"df2": "zerowidth/dotfiles2", // prefix collision
}

func TestParse(t *testing.T) {
	repoTests := []struct {
		input   string // the input
		repo    string // the expected repo match or expansion
		example string // what the case is an example of
	}{
		{"", "", "no input, no repo match"},
		{"df", "zerowidth/dotfiles", "match shorthand"},
		{" df", "", "no match, leading space"},
		{"foo/bar", "foo/bar", "fully qualified repo name"},
	}

	repoIssueTests := []struct {
		input   string // the input
		repo    string // the expected repo match or expansion
		issue   string // the expected issue match
		example string // what the case is an example of
	}{
		{"", "", "", "no issue no repo"},
		{"df 123", "zerowidth/dotfiles", "123", "normal expansion"},
		{"df#123", "zerowidth/dotfiles", "123", "expansion with #"},
		{"df #123", "zerowidth/dotfiles", "123", "space and # both"},
		{"df123", "zerowidth/dotfiles", "123", "prefix match"},
		{"df2 34", "zerowidth/dotfiles2", "34", "numeric suffix on shorthand"},
		{"df234", "zerowidth/dotfiles2", "34", "numerix suffix with no space"},
		{"foo/bar 123", "foo/bar", "123", "fully qualified repo"},
		{"df 0123", "zerowidth/dotfiles", "", "invalid issue"},
	}

	for _, tc := range repoTests {
		t.Run(fmt.Sprintf("Parse(%#v): %s", tc.input, tc.example), func(t *testing.T) {
			result := Parse(repoMap, tc.input)
			if result.Repo != tc.repo {
				t.Errorf("expected repo %#v, got %#v", tc.repo, result.Repo)
			}
		})
	}

	for _, tc := range repoIssueTests {
		t.Run(fmt.Sprintf("Parse(%#v): %s", tc.input, tc.example), func(t *testing.T) {
			result := Parse(repoMap, tc.input)
			if result.Repo != tc.repo {
				t.Errorf("expected repo %#v, got %#v", tc.repo, result.Repo)
			}
			if result.Issue != tc.issue {
				t.Errorf("expected issue %#v, got %#v", tc.issue, result.Issue)
			}
		})
	}

}
<|endoftext|>


<commit_before>package the_platinum_searcher

import (
	"bufio"
	"fmt"
	"io"
	"path/filepath"
	"strings"
)

type GitIgnore struct {
	ignorePatterns patterns
	acceptPatterns patterns
	depth          int
}

func (g *GitIgnore) Parse(r io.Reader) {
	reader := bufio.NewReader(r)
	for {
		buf, _, err := reader.ReadLine()
		if err != nil {
			break
		}
		line := strings.Trim(string(buf), " ")
		if len(line) == 0 || strings.HasPrefix(line, "#") {
			continue
		}

		if strings.HasPrefix(line, "!") {
			g.acceptPatterns = append(g.acceptPatterns,
				pattern(strings.TrimPrefix(line, "!")))
		} else {
			g.ignorePatterns = append(g.ignorePatterns, pattern(line))
		}
	}
}

func (g GitIgnore) IsMatch(path string, isDir bool, depth int) bool {
	if match := g.acceptPatterns.IsMatch(path, isDir, depth == g.depth); match {
		return false
	}
	return g.ignorePatterns.IsMatch(path, isDir, depth == g.depth)
}

type patterns []pattern

func (ps patterns) IsMatch(path string, isDir, isRoot bool) bool {
	for _, p := range ps {
		match := p.IsMatch(path, isDir, isRoot)
		if match {
			return true
		}
	}
	return false
}

type pattern string

func (p pattern) IsMatch(path string, isDir, isRoot bool) bool {

	if p.hasRootPrefix() && !isRoot {
		return false
	}

	if p.hasDirSuffix() && !isDir {
		return false
	}

	pattern := p.trimedPattern()

	match, _ := filepath.Match(pattern, p.equalizeDepth(path))
	fmt.Printf("ptn:%s path:%s(%s) => %t\n", pattern, p.equalizeDepth(path), path, match)
	return match
}

func (p pattern) equalizeDepth(path string) string {
	patternDepth := strings.Count(string(p), "/")
	pathDepth := strings.Count(path, string(filepath.Separator))
	start := 0
	if diff := pathDepth - patternDepth; diff >= 0 {
		start = diff
	}
	return filepath.Join(strings.Split(path, "/")[start:]...)
}

func (p pattern) prefix() string {
	return string(p[0])
}

func (p pattern) suffix() string {
	return string(p[len(p)-1])
}

func (p pattern) hasRootPrefix() bool {
	return p.prefix() == "/"
}

func (p pattern) hasNegativePrefix() bool {
	return p.prefix() == "!"
}

func (p pattern) hasDirSuffix() bool {
	return p.suffix() == "/"
}

func (p pattern) trimedPattern() string {
	return strings.Trim(string(p), "/")
}
<commit_msg>Added New function for GitIgnore struct.<commit_after>package the_platinum_searcher

import (
	"bufio"
	"fmt"
	"io"
	"path/filepath"
	"strings"
)

type GitIgnore struct {
	ignorePatterns patterns
	acceptPatterns patterns
	depth          int
}

func NewGitIgnore(depth int, patterns io.Reader) GitIgnore {
	g := GitIgnore{depth: depth}
	g.Parse(patterns)
	return g
}

func (g *GitIgnore) Parse(r io.Reader) {
	reader := bufio.NewReader(r)
	for {
		buf, _, err := reader.ReadLine()
		if err != nil {
			break
		}
		line := strings.Trim(string(buf), " ")
		if len(line) == 0 || strings.HasPrefix(line, "#") {
			continue
		}

		if strings.HasPrefix(line, "!") {
			g.acceptPatterns = append(g.acceptPatterns,
				pattern(strings.TrimPrefix(line, "!")))
		} else {
			g.ignorePatterns = append(g.ignorePatterns, pattern(line))
		}
	}
}

func (g GitIgnore) IsMatch(path string, isDir bool, depth int) bool {
	if match := g.acceptPatterns.IsMatch(path, isDir, depth == g.depth); match {
		return false
	}
	return g.ignorePatterns.IsMatch(path, isDir, depth == g.depth)
}

type patterns []pattern

func (ps patterns) IsMatch(path string, isDir, isRoot bool) bool {
	for _, p := range ps {
		match := p.IsMatch(path, isDir, isRoot)
		if match {
			return true
		}
	}
	return false
}

type pattern string

func (p pattern) IsMatch(path string, isDir, isRoot bool) bool {

	if p.hasRootPrefix() && !isRoot {
		return false
	}

	if p.hasDirSuffix() && !isDir {
		return false
	}

	pattern := p.trimedPattern()

	match, _ := filepath.Match(pattern, p.equalizeDepth(path))
	fmt.Printf("ptn:%s path:%s(%s) => %t\n", pattern, p.equalizeDepth(path), path, match)
	return match
}

func (p pattern) equalizeDepth(path string) string {
	patternDepth := strings.Count(string(p), "/")
	pathDepth := strings.Count(path, string(filepath.Separator))
	start := 0
	if diff := pathDepth - patternDepth; diff >= 0 {
		start = diff
	}
	return filepath.Join(strings.Split(path, "/")[start:]...)
}

func (p pattern) prefix() string {
	return string(p[0])
}

func (p pattern) suffix() string {
	return string(p[len(p)-1])
}

func (p pattern) hasRootPrefix() bool {
	return p.prefix() == "/"
}

func (p pattern) hasNegativePrefix() bool {
	return p.prefix() == "!"
}

func (p pattern) hasDirSuffix() bool {
	return p.suffix() == "/"
}

func (p pattern) trimedPattern() string {
	return strings.Trim(string(p), "/")
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"
)

var thePath = flag.String("dir", "/tmp", "working directory")
var git = flag.String("git", "/usr/bin/git", "path to git")

type CommandRequest struct {
	w       http.ResponseWriter
	abspath string
	bg      bool
	after   time.Time
	cmds    []*exec.Cmd
	ch      chan bool
}

var reqch = make(chan CommandRequest, 100)
var updates = map[string]time.Time{}

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

func runCommands(w http.ResponseWriter, bg bool,
	abspath string, cmds []*exec.Cmd) {

	var stderr io.Writer = ioutil.Discard
	var stdout io.Writer = ioutil.Discard

	if !bg {
		stderr = &bytes.Buffer{}
		stdout = &bytes.Buffer{}
	}

	for _, cmd := range cmds {
		if exists(cmd.Path) {
			log.Printf("Running %v in %v", cmd.Args, abspath)
			fmt.Fprintf(stdout, "# Running %v\n", cmd.Args)
			fmt.Fprintf(stderr, "# Running %v\n", cmd.Args)

			cmd.Stdout = stdout
			cmd.Stderr = stderr
			cmd.Dir = abspath
			err := cmd.Run()

			if err != nil {
				log.Printf("Error running %v in %v:  %v",
					cmd.Args, abspath, err)
				if !bg {
					fmt.Fprintf(stderr,
						"\n[gitmirror internal error:  %v]\n", err)
				}
			}

		}
	}

	if !bg {
		fmt.Fprintf(w, "---- stdout ----\n")
		stdout.(*bytes.Buffer).WriteTo(w)
		fmt.Fprintf(w, "\n----\n\n\n---- stderr ----\n")
		stderr.(*bytes.Buffer).WriteTo(w)
		fmt.Fprintf(w, "\n----\n")
	}
}

func shouldRun(path string, after time.Time) bool {
	if path == "/tmp" {
		return true
	}
	lastRun := updates[path]
	return lastRun.Before(after)
}

func didRun(path string, t time.Time) {
	updates[path] = t
}

func pathRunner(ch chan CommandRequest) {
	for r := range ch {
		if shouldRun(r.abspath, r.after) {
			t := time.Now()
			runCommands(r.w, r.bg, r.abspath, r.cmds)
			didRun(r.abspath, t)
		} else {
			log.Printf("Skipping redundant update: %v", r.abspath)
			if !r.bg {
				fmt.Fprintf(r.w, "Redundant request.")
			}
		}
		select {
		case r.ch <- true:
		default:
		}
	}
}

func commandRunner() {
	m := map[string]chan CommandRequest{}

	for r := range reqch {
		ch, running := m[r.abspath]
		if !running {
			ch = make(chan CommandRequest, 10)
			m[r.abspath] = ch
			go pathRunner(ch)
		}
		ch <- r
	}
}

func queueCommand(w http.ResponseWriter, bg bool,
	abspath string, cmds []*exec.Cmd) chan bool {
	req := CommandRequest{w, abspath, bg, time.Now(),
		cmds, make(chan bool)}
	reqch <- req
	return req.ch
}

func updateGit(w http.ResponseWriter, section string,
	bg bool, payload []byte) bool {

	time.Sleep(5 * time.Second)

	abspath := filepath.Join(*thePath, section)

	if !exists(abspath) {
		if !bg {
			http.Error(w, "Not found", http.StatusNotFound)
		}
		return false
	}

	cmds := []*exec.Cmd{
		exec.Command(*git, "remote", "update", "-p"),
		exec.Command(*git, "gc", "--auto"),
		exec.Command(filepath.Join(abspath, "hooks/post-fetch")),
		exec.Command(filepath.Join(*thePath, "bin/post-fetch")),
	}

	cmds[2].Stdin = bytes.NewBuffer(payload)
	cmds[3].Stdin = bytes.NewBuffer(payload)

	return <-queueCommand(w, bg, abspath, cmds)
}

func getPath(req *http.Request) string {
	return filepath.Clean(filepath.FromSlash(req.URL.Path))[1:]
}

func createRepo(w http.ResponseWriter, section string,
	bg bool, payload []byte) {

	p := struct {
		Repository struct {
			Owner   interface{}
			Private bool
			Name    string
		}
	}{}

	err := json.Unmarshal(payload, &p)
	if err != nil {
		log.Printf("Error unmarshalling data: %v", err)
		http.Error(w, "Error parsing JSON", http.StatusInternalServerError)
		return
	}

	var ownerName string
	switch i := p.Repository.Owner.(type) {
	case string:
		ownerName = i
	case map[string]interface{}:
		ownerName = fmt.Sprintf("%v", i["name"])
	}

	repo := fmt.Sprintf("git://github.com/%v/%v.git",
		ownerName, p.Repository.Name)
	if p.Repository.Private {
		repo = fmt.Sprintf("git@github.com:%v/%v.git",
			ownerName, p.Repository.Name)
	}

	cmds := []*exec.Cmd{
		exec.Command(*git, "clone", "--mirror", "--bare", repo,
			filepath.Join(*thePath, section)),
	}

	if bg {
		w.WriteHeader(201)
	}
	queueCommand(w, true, "/tmp", cmds)
}

func doUpdate(w http.ResponseWriter, path string,
	bg bool, payload []byte) {
	if bg {
		go updateGit(w, path, bg, payload)
		w.WriteHeader(201)
	} else {
		updateGit(w, path, bg, payload)
	}
}

func handleGet(w http.ResponseWriter, req *http.Request, bg bool) {
	path := getPath(req)
	doUpdate(w, path, bg, nil)
}

func handlePost(w http.ResponseWriter, req *http.Request, bg bool) {
	b := []byte(req.FormValue("payload"))

	path := getPath(req)
	abspath := filepath.Join(*thePath, path)

	if exists(abspath) {
		doUpdate(w, path, bg, b)
	} else {
		createRepo(w, path, bg, b)
	}
}

func handleReq(w http.ResponseWriter, req *http.Request) {
	backgrounded := req.FormValue("bg") != "false"

	log.Printf("Handling %v %v", req.Method, req.URL.Path)

	switch req.Method {
	case "GET":
		handleGet(w, req, backgrounded)
	case "POST":
		handlePost(w, req, backgrounded)
	default:
		http.Error(w, "Method not allowed",
			http.StatusMethodNotAllowed)
	}
}

func main() {
	flag.Parse()

	log.SetFlags(log.Lmicroseconds)

	go commandRunner()

	http.HandleFunc("/", handleReq)
	http.HandleFunc("/favicon.ico",
		func(w http.ResponseWriter, req *http.Request) {
			http.Error(w, "No favicon", http.StatusGone)
		})
	log.Fatal(http.ListenAndServe(":8124", nil))
}
<commit_msg>In the unlikely event I can't write to a buf, panic<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"
)

var thePath = flag.String("dir", "/tmp", "working directory")
var git = flag.String("git", "/usr/bin/git", "path to git")

type CommandRequest struct {
	w       http.ResponseWriter
	abspath string
	bg      bool
	after   time.Time
	cmds    []*exec.Cmd
	ch      chan bool
}

var reqch = make(chan CommandRequest, 100)
var updates = map[string]time.Time{}

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

func maybePanic(err error) {
	if err != nil {
		panic(err)
	}
}

func runCommands(w http.ResponseWriter, bg bool,
	abspath string, cmds []*exec.Cmd) {

	var stderr io.Writer = ioutil.Discard
	var stdout io.Writer = ioutil.Discard

	if !bg {
		stderr = &bytes.Buffer{}
		stdout = &bytes.Buffer{}
	}

	for _, cmd := range cmds {
		if exists(cmd.Path) {
			log.Printf("Running %v in %v", cmd.Args, abspath)
			fmt.Fprintf(stdout, "# Running %v\n", cmd.Args)
			fmt.Fprintf(stderr, "# Running %v\n", cmd.Args)

			cmd.Stdout = stdout
			cmd.Stderr = stderr
			cmd.Dir = abspath
			err := cmd.Run()

			if err != nil {
				log.Printf("Error running %v in %v:  %v",
					cmd.Args, abspath, err)
				if !bg {
					fmt.Fprintf(stderr,
						"\n[gitmirror internal error:  %v]\n", err)
				}
			}

		}
	}

	if !bg {
		fmt.Fprintf(w, "---- stdout ----\n")
		_, err := stdout.(*bytes.Buffer).WriteTo(w)
		maybePanic(err)
		fmt.Fprintf(w, "\n----\n\n\n---- stderr ----\n")
		_, err = stderr.(*bytes.Buffer).WriteTo(w)
		maybePanic(err)
		fmt.Fprintf(w, "\n----\n")
	}
}

func shouldRun(path string, after time.Time) bool {
	if path == "/tmp" {
		return true
	}
	lastRun := updates[path]
	return lastRun.Before(after)
}

func didRun(path string, t time.Time) {
	updates[path] = t
}

func pathRunner(ch chan CommandRequest) {
	for r := range ch {
		if shouldRun(r.abspath, r.after) {
			t := time.Now()
			runCommands(r.w, r.bg, r.abspath, r.cmds)
			didRun(r.abspath, t)
		} else {
			log.Printf("Skipping redundant update: %v", r.abspath)
			if !r.bg {
				fmt.Fprintf(r.w, "Redundant request.")
			}
		}
		select {
		case r.ch <- true:
		default:
		}
	}
}

func commandRunner() {
	m := map[string]chan CommandRequest{}

	for r := range reqch {
		ch, running := m[r.abspath]
		if !running {
			ch = make(chan CommandRequest, 10)
			m[r.abspath] = ch
			go pathRunner(ch)
		}
		ch <- r
	}
}

func queueCommand(w http.ResponseWriter, bg bool,
	abspath string, cmds []*exec.Cmd) chan bool {
	req := CommandRequest{w, abspath, bg, time.Now(),
		cmds, make(chan bool)}
	reqch <- req
	return req.ch
}

func updateGit(w http.ResponseWriter, section string,
	bg bool, payload []byte) bool {

	time.Sleep(5 * time.Second)

	abspath := filepath.Join(*thePath, section)

	if !exists(abspath) {
		if !bg {
			http.Error(w, "Not found", http.StatusNotFound)
		}
		return false
	}

	cmds := []*exec.Cmd{
		exec.Command(*git, "remote", "update", "-p"),
		exec.Command(*git, "gc", "--auto"),
		exec.Command(filepath.Join(abspath, "hooks/post-fetch")),
		exec.Command(filepath.Join(*thePath, "bin/post-fetch")),
	}

	cmds[2].Stdin = bytes.NewBuffer(payload)
	cmds[3].Stdin = bytes.NewBuffer(payload)

	return <-queueCommand(w, bg, abspath, cmds)
}

func getPath(req *http.Request) string {
	return filepath.Clean(filepath.FromSlash(req.URL.Path))[1:]
}

func createRepo(w http.ResponseWriter, section string,
	bg bool, payload []byte) {

	p := struct {
		Repository struct {
			Owner   interface{}
			Private bool
			Name    string
		}
	}{}

	err := json.Unmarshal(payload, &p)
	if err != nil {
		log.Printf("Error unmarshalling data: %v", err)
		http.Error(w, "Error parsing JSON", http.StatusInternalServerError)
		return
	}

	var ownerName string
	switch i := p.Repository.Owner.(type) {
	case string:
		ownerName = i
	case map[string]interface{}:
		ownerName = fmt.Sprintf("%v", i["name"])
	}

	repo := fmt.Sprintf("git://github.com/%v/%v.git",
		ownerName, p.Repository.Name)
	if p.Repository.Private {
		repo = fmt.Sprintf("git@github.com:%v/%v.git",
			ownerName, p.Repository.Name)
	}

	cmds := []*exec.Cmd{
		exec.Command(*git, "clone", "--mirror", "--bare", repo,
			filepath.Join(*thePath, section)),
	}

	if bg {
		w.WriteHeader(201)
	}
	queueCommand(w, true, "/tmp", cmds)
}

func doUpdate(w http.ResponseWriter, path string,
	bg bool, payload []byte) {
	if bg {
		go updateGit(w, path, bg, payload)
		w.WriteHeader(201)
	} else {
		updateGit(w, path, bg, payload)
	}
}

func handleGet(w http.ResponseWriter, req *http.Request, bg bool) {
	path := getPath(req)
	doUpdate(w, path, bg, nil)
}

func handlePost(w http.ResponseWriter, req *http.Request, bg bool) {
	b := []byte(req.FormValue("payload"))

	path := getPath(req)
	abspath := filepath.Join(*thePath, path)

	if exists(abspath) {
		doUpdate(w, path, bg, b)
	} else {
		createRepo(w, path, bg, b)
	}
}

func handleReq(w http.ResponseWriter, req *http.Request) {
	backgrounded := req.FormValue("bg") != "false"

	log.Printf("Handling %v %v", req.Method, req.URL.Path)

	switch req.Method {
	case "GET":
		handleGet(w, req, backgrounded)
	case "POST":
		handlePost(w, req, backgrounded)
	default:
		http.Error(w, "Method not allowed",
			http.StatusMethodNotAllowed)
	}
}

func main() {
	flag.Parse()

	log.SetFlags(log.Lmicroseconds)

	go commandRunner()

	http.HandleFunc("/", handleReq)
	http.HandleFunc("/favicon.ico",
		func(w http.ResponseWriter, req *http.Request) {
			http.Error(w, "No favicon", http.StatusGone)
		})
	log.Fatal(http.ListenAndServe(":8124", nil))
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "io/ioutil"
    "encoding/gob"
    "encoding/json"
    "bytes"
    "strings"
    "strconv"
    "os"
    "time"
    "os/exec"
)

type LongTestConfig struct {
    Executable     string
    FollowChuldren string
    Test           string
}

type Repository struct {
    Repository      string
    Source          string
    Name            string
    Location        string
    Build           string
    Test            string
    LongTest        LongTestConfig
    OutputDirectory string
    NotifyEmail     string
}

type SystemSnapshot struct {
    Name        string
    Pid         int
    CpuUsage    float64
    MemoryUsage int
    OpenFiles   int
    MemoryHistory []int
}

var currentCpuUtilization float64
var MemoryHistory []int
var running bool

func GetOpenFiles(pid int) int {
    pid_str := strconv.Itoa(pid)
    cmd := exec.Command("lsof", "-F", "pcn", "-p", pid_str)
    output, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("Error executing 'lsof': %s\n", err.Error())
        return -1
    }

    lines := strings.Split(string(output), "\n")
    /*
    for _, line := range lines {
        fmt.Printf("'%s'\n", line)
    }
    */
    return len(lines) - 2
}

func GetPidFromProcess(processName string) int {
    pidString := ""
    cmd := exec.Command("pidof", processName)
    output, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("Error executing 'pidof': %s\n", err.Error())
        return -1
    }
    lines := strings.Split(string(output), " ")
    if len(lines) > 1 {
        fmt.Printf("WARN - running multiple '%s' instances, selecting the first (pid %s)\n", processName, lines[0])
        pidString = lines[0]
    } else {
        pidString = string(output)
    }
    pid, err := strconv.Atoi(pidString[:len(pidString) - 1])
    if err != nil {
        fmt.Printf("Error converting pid: %s\n", err.Error())
    }
    return pid
}

func GetRSS(pid int) int {
	fileCtx, err := ioutil.ReadFile("/proc/" + strconv.Itoa(pid) + "/statm")
    if err != nil {
        fmt.Printf(":: Error reading process %d memory statistics: %s\n", pid, err.Error())
        return -1
    }
    tokens := strings.Split(string(fileCtx), " ")
    rss , _ := strconv.Atoi(tokens[1])
    return (rss * os.Getpagesize()) / 1024 // convert to KiB
}

func fetchCpuData(pid int) (int, int, int, int) {
    fileCtx, err := ioutil.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
    if err != nil {
        fmt.Printf(":: Error reading process %d statistics: %s\n", pid, err.Error())
        return -1, -1, -1, -1
    }
    tokens := strings.Split(string(fileCtx), " ")
    utime, _ := strconv.Atoi(tokens[13])
    //stime, _ := strconv.Atoi(tokens[14])
    cutime, _ := strconv.Atoi(tokens[15])
    //cstime, _ := strconv.Atoi(tokens[16])

    fileCtx, err = ioutil.ReadFile("/proc/stat")
    if err != nil {
        fmt.Printf(":: Error reading system statistics: %s\n", pid, err.Error())
        return -1, -1, -1, -1
    }
    lines := strings.Split(string(fileCtx), "\n")
    cpu_utime := 0
    cpu_stime := 0

    for _, line := range lines {
        tokens := strings.Split(line, " ")
        if tokens[0] == "cpu" {
            // get total cpu time
            cpu_utime, err = strconv.Atoi(tokens[2])
            if err != nil {
                fmt.Printf("Error parsing integer: %s\n", err.Error())
            }

            cpu_stime, _ = strconv.Atoi(tokens[4])
            break
        }
    }
    return utime, cutime, cpu_utime, cpu_stime
}

func UpdateCpuUsage(ticker *time.Ticker, cmdPid int) {

    var out bytes.Buffer

    currentCpuUtilization = 0
    for {
        cmd := exec.Command("ps", "aux")
        cmd.Stdout = &out
        err := cmd.Run()
        if err != nil {
            fmt.Println(":: Error getting CPU utilization from 'ps':", err)
            return
        }
        line, err := out.ReadString('\n')
        if err!=nil {
            break;
        }
        tokens := strings.Split(line, " ")
        ft := make([]string, 0)
        for _, t := range(tokens) {
            if t!="" && t!="\t" {
                ft = append(ft, t)
            }
        }
        pid, err := strconv.Atoi(ft[1])
        if err!=nil {
            continue
        }
        if pid != cmdPid {
            continue
        }
        cpu, err := strconv.ParseFloat(ft[2], 64)
        if err!=nil {
            fmt.Println("Error parsing CPU float value:", err)
        }
        currentCpuUtilization = cpu
        <-ticker.C
    }
}

func LoadRepository(configFile string) Repository {

	gob.Register(&Repository{})
	r := strings.NewReader(configFile)
	decoder := json.NewDecoder(r)

	repo := Repository{}
	e := decoder.Decode(&repo)
	if e != nil {
        fmt.Printf("LoadRepository: error decoding repository configuration from file: %s\n", e)
	}
	return repo
}

func GetSample(repository *Repository) *SystemSnapshot {
    sample := SystemSnapshot{}
    //sample.Pid = GetPidFromProcess(repository.LongTest.Executable)
    sample.Name = repository.Name
    sample.Pid = GetPidFromProcess(repository.LongTest.Executable)
    sample.OpenFiles = GetOpenFiles(sample.Pid)
    sample.MemoryUsage = GetRSS(sample.Pid)
    MemoryHistory = append(MemoryHistory, sample.MemoryUsage)
    sample.MemoryHistory = append(sample.MemoryHistory, MemoryHistory...)
    fmt.Println(sample.MemoryHistory)
    sample.CpuUsage = currentCpuUtilization

    return &sample
}

func WriteToFile(repository *Repository, snapshot *SystemSnapshot) {
	gob.Register(&SystemSnapshot{})

    file, err := os.Create(repository.OutputDirectory + "/" + repository.Name + "-system-output.txt")
    if err != nil {
        fmt.Printf("Error creating system output file: %s\n", err.Error())
        return
    }
    defer file.Close()

	encoder := json.NewEncoder(file)
	e := encoder.Encode(snapshot)
	if e != nil {
        fmt.Printf("LoadRepository: error decoding repository configuration from file: %s\n", e)
	}
}

func Sampler(repo *Repository) {
        for !running {
            time.Sleep(time.Second)
        }
        for {
            sample := GetSample(repo)
            //fmt.Printf("--- process %s (pid %d) ---\n", sample.Name, sample.Pid)
            //fmt.Printf("Number of opened files: %d\n", sample.OpenFiles)
            //fmt.Printf("Used memory (RSS): %d KiB\n", sample.MemoryUsage)
            //fmt.Printf("CPU utilization: %0.2f%%\n", sample.CpuUsage)
            WriteToFile(repo, sample)
            time.Sleep(time.Second)
        }
}

func main() {
    // os.Args[1] - configuration path
    // os.Args[2] - repository checkout temporary path

    running = false
    if len(os.Args) < 3 {
        fmt.Printf(":: Error: too few arguments\n")
        return
    }

    fmt.Println(os.Args)
	fileCtx, err := ioutil.ReadFile(os.Args[1])
    if err != nil {
        fmt.Printf(":: Error loading configuration file '%s': %s\n", os.Args[1], err)
        return
    }

    repo := LoadRepository(string(fileCtx))

    if strings.Index(repo.LongTest.Test, "{TMPDIR}") == 0 {
        repo.LongTest.Test = os.Args[2] + repo.LongTest.Test[8:]
    }
    // Execute the test program and wait for it's termination
    fmt.Println("Will run script: " + repo.LongTest.Test + " in dir. " + os.Args[2])
    cmd := exec.Command(repo.LongTest.Test, os.Args[2])
    cmd.Env = os.Environ()
    cmd.Env = append(cmd.Env, "LONGTEST=1")
    err = cmd.Start()

    time.Sleep(5*time.Second)
    // start collecting CPU usage
    ticker := time.NewTicker(time.Second)
    pid := GetPidFromProcess(repo.LongTest.Executable)

    if pid == -1 {
        fmt.Printf("Error: process not found\n")
        return
    }
    go UpdateCpuUsage(ticker, pid)
    go Sampler(&repo)

    if err != nil {
        fmt.Printf(":: Error executing test program '%s': %s\n", repo.Test, err.Error())
    }

    running = true
    cmd.Wait()
}
<commit_msg>iterate throught all newlines<commit_after>package main

import (
    "fmt"
    "io/ioutil"
    "encoding/gob"
    "encoding/json"
    "bytes"
    "strings"
    "strconv"
    "os"
    "time"
    "os/exec"
)

type LongTestConfig struct {
    Executable     string
    FollowChuldren string
    Test           string
}

type Repository struct {
    Repository      string
    Source          string
    Name            string
    Location        string
    Build           string
    Test            string
    LongTest        LongTestConfig
    OutputDirectory string
    NotifyEmail     string
}

type SystemSnapshot struct {
    Name        string
    Pid         int
    CpuUsage    float64
    MemoryUsage int
    OpenFiles   int
    MemoryHistory []int
}

var currentCpuUtilization float64
var MemoryHistory []int
var running bool

func GetOpenFiles(pid int) int {
    pid_str := strconv.Itoa(pid)
    cmd := exec.Command("lsof", "-F", "pcn", "-p", pid_str)
    output, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("Error executing 'lsof': %s\n", err.Error())
        return -1
    }

    lines := strings.Split(string(output), "\n")
    /*
    for _, line := range lines {
        fmt.Printf("'%s'\n", line)
    }
    */
    return len(lines) - 2
}

func GetPidFromProcess(processName string) int {
    pidString := ""
    cmd := exec.Command("pidof", processName)
    output, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("Error executing 'pidof': %s\n", err.Error())
        return -1
    }
    lines := strings.Split(string(output), " ")
    if len(lines) > 1 {
        fmt.Printf("WARN - running multiple '%s' instances, selecting the first (pid %s)\n", processName, lines[0])
        pidString = lines[0]
    } else {
        pidString = string(output)
    }
    pid, err := strconv.Atoi(pidString[:len(pidString) - 1])
    if err != nil {
        fmt.Printf("Error converting pid: %s\n", err.Error())
    }
    return pid
}

func GetRSS(pid int) int {
	fileCtx, err := ioutil.ReadFile("/proc/" + strconv.Itoa(pid) + "/statm")
    if err != nil {
        fmt.Printf(":: Error reading process %d memory statistics: %s\n", pid, err.Error())
        return -1
    }
    tokens := strings.Split(string(fileCtx), " ")
    rss , _ := strconv.Atoi(tokens[1])
    return (rss * os.Getpagesize()) / 1024 // convert to KiB
}

func fetchCpuData(pid int) (int, int, int, int) {
    fileCtx, err := ioutil.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
    if err != nil {
        fmt.Printf(":: Error reading process %d statistics: %s\n", pid, err.Error())
        return -1, -1, -1, -1
    }
    tokens := strings.Split(string(fileCtx), " ")
    utime, _ := strconv.Atoi(tokens[13])
    //stime, _ := strconv.Atoi(tokens[14])
    cutime, _ := strconv.Atoi(tokens[15])
    //cstime, _ := strconv.Atoi(tokens[16])

    fileCtx, err = ioutil.ReadFile("/proc/stat")
    if err != nil {
        fmt.Printf(":: Error reading system statistics: %s\n", pid, err.Error())
        return -1, -1, -1, -1
    }
    lines := strings.Split(string(fileCtx), "\n")
    cpu_utime := 0
    cpu_stime := 0

    for _, line := range lines {
        tokens := strings.Split(line, " ")
        if tokens[0] == "cpu" {
            // get total cpu time
            cpu_utime, err = strconv.Atoi(tokens[2])
            if err != nil {
                fmt.Printf("Error parsing integer: %s\n", err.Error())
            }

            cpu_stime, _ = strconv.Atoi(tokens[4])
            break
        }
    }
    return utime, cutime, cpu_utime, cpu_stime
}

func UpdateCpuUsage(ticker *time.Ticker, cmdPid int) {

    var out bytes.Buffer

    currentCpuUtilization = 0
    for {
        cmd := exec.Command("ps", "aux")
        cmd.Stdout = &out
        err := cmd.Run()
        if err != nil {
            fmt.Println(":: Error getting CPU utilization from 'ps':", err)
            return
        }
        for {
            line, err := out.ReadString('\n')
            if err!=nil {
                break;
            }
            tokens := strings.Split(line, " ")
            ft := make([]string, 0)
            for _, t := range(tokens) {
                if t!="" && t!="\t" {
                    ft = append(ft, t)
                }
            }
            pid, err := strconv.Atoi(ft[1])
            if err!=nil {
                continue
            }
            if pid != cmdPid {
                continue
            }
            cpu, err := strconv.ParseFloat(ft[2], 64)
            if err!=nil {
                fmt.Println("Error parsing CPU float value:", err)
            }
            currentCpuUtilization = cpu
            break
        }
        <-ticker.C
    }
}

func LoadRepository(configFile string) Repository {

	gob.Register(&Repository{})
	r := strings.NewReader(configFile)
	decoder := json.NewDecoder(r)

	repo := Repository{}
	e := decoder.Decode(&repo)
	if e != nil {
        fmt.Printf("LoadRepository: error decoding repository configuration from file: %s\n", e)
	}
	return repo
}

func GetSample(repository *Repository) *SystemSnapshot {
    sample := SystemSnapshot{}
    //sample.Pid = GetPidFromProcess(repository.LongTest.Executable)
    sample.Name = repository.Name
    sample.Pid = GetPidFromProcess(repository.LongTest.Executable)
    sample.OpenFiles = GetOpenFiles(sample.Pid)
    sample.MemoryUsage = GetRSS(sample.Pid)
    MemoryHistory = append(MemoryHistory, sample.MemoryUsage)
    sample.MemoryHistory = append(sample.MemoryHistory, MemoryHistory...)
    fmt.Println(sample.MemoryHistory)
    sample.CpuUsage = currentCpuUtilization

    return &sample
}

func WriteToFile(repository *Repository, snapshot *SystemSnapshot) {
	gob.Register(&SystemSnapshot{})

    file, err := os.Create(repository.OutputDirectory + "/" + repository.Name + "-system-output.txt")
    if err != nil {
        fmt.Printf("Error creating system output file: %s\n", err.Error())
        return
    }
    defer file.Close()

	encoder := json.NewEncoder(file)
	e := encoder.Encode(snapshot)
	if e != nil {
        fmt.Printf("LoadRepository: error decoding repository configuration from file: %s\n", e)
	}
}

func Sampler(repo *Repository) {
        for !running {
            time.Sleep(time.Second)
        }
        for {
            sample := GetSample(repo)
            //fmt.Printf("--- process %s (pid %d) ---\n", sample.Name, sample.Pid)
            //fmt.Printf("Number of opened files: %d\n", sample.OpenFiles)
            //fmt.Printf("Used memory (RSS): %d KiB\n", sample.MemoryUsage)
            //fmt.Printf("CPU utilization: %0.2f%%\n", sample.CpuUsage)
            WriteToFile(repo, sample)
            time.Sleep(time.Second)
        }
}

func main() {
    // os.Args[1] - configuration path
    // os.Args[2] - repository checkout temporary path

    running = false
    if len(os.Args) < 3 {
        fmt.Printf(":: Error: too few arguments\n")
        return
    }

    fmt.Println(os.Args)
	fileCtx, err := ioutil.ReadFile(os.Args[1])
    if err != nil {
        fmt.Printf(":: Error loading configuration file '%s': %s\n", os.Args[1], err)
        return
    }

    repo := LoadRepository(string(fileCtx))

    if strings.Index(repo.LongTest.Test, "{TMPDIR}") == 0 {
        repo.LongTest.Test = os.Args[2] + repo.LongTest.Test[8:]
    }
    // Execute the test program and wait for it's termination
    fmt.Println("Will run script: " + repo.LongTest.Test + " in dir. " + os.Args[2])
    cmd := exec.Command(repo.LongTest.Test, os.Args[2])
    cmd.Env = os.Environ()
    cmd.Env = append(cmd.Env, "LONGTEST=1")
    err = cmd.Start()

    time.Sleep(5*time.Second)
    // start collecting CPU usage
    ticker := time.NewTicker(time.Second)
    pid := GetPidFromProcess(repo.LongTest.Executable)

    if pid == -1 {
        fmt.Printf("Error: process not found\n")
        return
    }
    go UpdateCpuUsage(ticker, pid)
    go Sampler(&repo)

    if err != nil {
        fmt.Printf(":: Error executing test program '%s': %s\n", repo.Test, err.Error())
    }

    running = true
    cmd.Wait()
}
<|endoftext|>


<commit_before>package pagerduty

/*
 * Copyright 2016 Albert P. Tobey <atobey@netflix.com>
 *
 * 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 (
	"bytes"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/netflix/hal-9001/hal"
)

const OncallUsage = `!oncall <alias>

Find out who is oncall. If only one argument is provided, it must match
a known alias for a Pagerduty service. Otherwise, it is expected to be
a subcommand.

!oncall core
`

const DefaultTopicInterval = "10m"

// TODO: add the service key to the output such that someone trying to contact a team
// can page them from within Slack without having to set up a page alias or go out
// to a web page. !page should be able to take a service key so the output can include something
// like: "To page <team> use the command: !page <servicekey> <message>"
func oncall(msg hal.Evt) {
	parts := msg.BodyAsArgv()

	if len(parts) == 1 {
		msg.Reply(OncallUsage)
		return
	}

	// make sure the pagerduty token is setup in hal.Secrets
	token, err := getSecrets()
	if err != nil || token == "" {
		msg.Replyf("pagerduty: %s is not set up in hal.Secrets. Cannot continue.", PagerdutyTokenKey)
		return
	}

	if parts[1] == "cache-now" {
		msg.Reply("Updating Pagerduty policy cache now.")
		cacheNow(token, msg.RoomId)
		msg.Reply("Pagerduty policy cache update complete.")
		return
	} else if parts[1] == "cache-status" {
		age := int(hal.Cache().Age(CacheKey).Seconds())
		next := time.Time{}
		status := "broken"
		pf := hal.GetPeriodicFunc(cacheFuncName(msg.RoomId))
		if pf != nil {
			next = pf.Last().Add(pf.Interval)
			status = pf.Status()
		}
		msg.Replyf("The cache is %d seconds old. Auto-update is %s and its next update is at %s.",
			age, status, next.Format(time.UnixDate))
		return
	} else if len(parts) > 2 {
		// flatten split words back into position 1, parts isn't used after the ToLower
		parts[1] = strings.Join(parts[1:], " ")
	}

	// TODO: look at the aliases set up for !page and try for an exact match
	// before doing fuzzy search -- move fuzzy search to a "search" subcommand
	// so it's clear that it is not precise
	want := strings.ToLower(parts[1])

	// see if there's an exact match on an alias, e.g. "!oncall core" -> alias.core
	/*
		aliasPref := msg.AsPref().SetUser("").FindKey(aliasKey(want)).One()
		if aliasPref.Success {
			svc, err := GetServiceByKey(token, aliasPref.Value)
			if err == nil {
			}
			// all through to search ...
		}
	*/

	// search over all policies looking for matching policy name, escalation
	// rule name, or service name
	matches := make([]Oncall, 0)
	oncalls := getOncallCache(token, false)
	var exactMatchFound bool

	for _, oncall := range oncalls {
		schedSummary := strings.ToLower(oncall.Schedule.Summary)
		if schedSummary == want {
			matches = append(matches, oncall)
			exactMatchFound = true
			continue
		} else if !exactMatchFound && strings.Contains(schedSummary, want) {
			matches = append(matches, oncall)
			continue
		}

		epSummary := strings.ToLower(oncall.EscalationPolicy.Summary)
		if epSummary == want {
			matches = append(matches, oncall)
			exactMatchFound = true
			continue
		} else if !exactMatchFound && strings.Contains(epSummary, want) {
			matches = append(matches, oncall)
			continue
		}
	}

	// check team names if there were no matches
	// TODO: cache some of these results and always check team names
	if len(matches) == 0 {
		log.Printf("GETTING TEAMS!")
		teams, err := GetTeams(token)
		if err != nil {
			log.Printf("REST call to Pagerduty /teams failed: %s", err)
		} else {
			for _, team := range teams {
				ltname := strings.ToLower(team.Name)
				ltdesc := strings.ToLower(team.Description)

				if strings.Contains(ltname, want) || strings.Contains(ltdesc, want) {
					matches = getTeamOncalls(token, team)
				}
			}
		}
	}

	reply := formatOncallReply(want, exactMatchFound, matches)
	msg.Reply(reply)
}

// getTeamOncalls fetches escalation policies for the team then the oncalls for those
// policies and returns a list.
func getTeamOncalls(token string, team Team) []Oncall {
	out := make([]Oncall, 0)
	log.Printf("Returning empty list but should have fetched oncalls for team %+v", team)

	params := map[string]string{"team_ids[]": team.Id}
	policies, err := GetEscalationPolicies(token, params)
	if err != nil {
		log.Printf("Error while fetching escalation policies for team id %q: %s", team.Id, err)
		return out
	}

	policy_ids := make([]string, 0)
	for _, policy := range policies {
		policy_ids = append(policy_ids, policy.Id)
	}

	params = map[string]string{"escalation_policy_ids[]": strings.Join(policy_ids, ",")}
	oncalls, err := GetOncalls(token, params)
	if err != nil {
		log.Printf("Error while fetching oncalls for team id %q's policies: %s", team.Id, err)
	} else {
		// insert oncalls into the hal directory
		for _, oncall := range oncalls {
			attrs := map[string]string{
				"email":          oncall.User.Email,
				"name":           oncall.User.Name,
				"pd-user-id":     oncall.User.Id,
				"pd-schedule-id": oncall.Schedule.Id,
				"pd-policy-id":   oncall.EscalationPolicy.Id,
			}

			// plug in the contact methods
			// per PD docs, types can be: email_contact_method, phone_contact_method, push_notification_contact_method, or sms_contact_method
			for _, cm := range oncall.User.ContactMethods {
				attrs["contact-method-id"] = cm.Id
				attrs[cm.Type] = cm.Address
			}

			edges := []string{"name", "email", "phone_contact_method", "sms_contact_method"}
			hal.Directory().Put(oncall.User.Id, "pd-oncall", attrs, edges)
		}

		return oncalls
	}

	return out
}

func getOncallCache(token string, forceUpdate bool) []Oncall {
	oncalls := []Oncall{}

	// see if there's a copy cached
	if hal.Cache().Exists(CacheKey) {
		ttl, err := hal.Cache().Get(CacheKey, &oncalls)
		if err != nil {
			log.Printf("Error retreiving oncalls from the Hal TTL cache: %s", err)
			oncalls = []Oncall{}
		} else if ttl == 0 || forceUpdate {
			oncalls = []Oncall{}
		}
	}

	// the cache exists and is still valid, return it now
	if len(oncalls) > 0 {
		return oncalls
	}

	// get all of the defined policies
	var err error
	oncalls, err = GetOncalls(token, nil)
	if err != nil {
		log.Printf("Returning empty list. REST call to Pagerduty failed: %s", err)
		return []Oncall{}
	}

	// always update the cache regardless of ttl
	hal.Cache().Set(CacheKey, &oncalls, cacheExpire)

	return oncalls
}

func oncallInit(i *hal.Instance) {
	cacheFreq := hal.GetPref("", "", i.RoomId, "pagerduty", "cache-update-frequency", DefaultCacheInterval)
	cd, err := time.ParseDuration(cacheFreq.Value)
	if err != nil {
		log.Panicf("BUG: could not parse cache update frequency preference: %q", cacheFreq.Value)
	}

	topicFreq := hal.GetPref("", "", i.RoomId, "pagerduty", "topic-update-frequency", DefaultTopicInterval)
	td, err := time.ParseDuration(topicFreq.Value)
	if err != nil {
		log.Panicf("BUG: could not parse topic update frequency preference: %q", topicFreq.Value)
	}

	token, err := getSecrets()
	if err != nil || token == "" {
		return // getSecrets will log the error
	}

	go func() {
		pf := hal.PeriodicFunc{
			Name:     cacheFuncName(i.RoomId),
			Interval: cd,
			Function: func() { cacheNow(token, i.RoomId) },
		}

		pf.Register()
		pf.Start()
	}()

	go func() {
		pf := hal.PeriodicFunc{
			Name:     topicFuncName(i.RoomId),
			Interval: td,
			Function: func() { topicUpdater(token, i.RoomId, i.Broker.Name()) },
		}

		pf.Register()
		pf.Start()
	}()

	// TODO: add a command to stop, etc.
}

func cacheNow(token, roomId string) {
	getOncallCache(token, true)
}

// topicUpdater runs periodically to update the topic in the room
// it's configured in.
// To fully enable it, you need the oncall schedule id from the pagerduty API.
// !prefs set --room * --broker slack --plugin pagerduty --key topic-updater-schedule-id --value <schedule id>
// !prefs set --room * --broker slack --plugin pagerduty --key topic-prefix --value <text>
// !prefs set --room * --broker slack --plugin pagerduty --key topic-suffix --value <text>
// TODO: see if there's a way to also resolve integration keys instead of using the schedule id
func topicUpdater(token, roomId, brokerName string) {
	log.Printf("ENTER topicUpdater(token, %q, %q)", roomId, brokerName)

	pref := hal.GetPref("", brokerName, roomId, "pagerduty", "topic-updater-schedule-id", "-")
	// probably not configured, nothing to see here...
	if !pref.Success || pref.Value == "-" {
		log.Printf("The pref ''/%q/%q/pagerduty/topic-updater-schedule-id does not seem to be set. Returning without taking action.",
			brokerName, roomId)
		return
	}

	params := map[string]string{
		"include[]":      "users",
		"schedule_ids[]": pref.Value,
	}

	oncalls, err := GetOncalls(token, params)
	if err != nil {
		log.Printf("Failed to fetch oncalls for schedule id %q: %s", pref.Value, err)
		return
	}

	log.Printf("Got %d users for schedule id %q", len(oncalls), pref.Value)

	// there may be more than one entry but if they're both on the same
	// schedule it should be the same primary oncall so ignore all but the first
	if len(oncalls) == 0 {
		log.Printf("no oncall results for id %q", pref.Value)
		return
	}

	// TODO: yet another place some kind of templating support would be handy
	prefix := hal.GetPref("", brokerName, roomId, "pagerduty", "topic-prefix", "")
	suffix := hal.GetPref("", brokerName, roomId, "pagerduty", "topic-suffix", "")

	// e.g. prefix = "", summary = "Al Tobey", suffix = " [team-dl@company.com] !pageus"
	topic := prefix.Value + oncalls[0].User.Summary + suffix.Value

	broker := hal.Router().GetBroker(brokerName)

	oldTopic, err := broker.GetTopic(roomId)
	if err != nil {
		log.Printf("Could not fetch current topic for room %q: %s", roomId, err)
		return
	}

	// only do the update if the topic has changed
	if topic != oldTopic {
		broker.SetTopic(roomId, topic)
	}
}

func cacheFuncName(roomId string) string {
	return fmt.Sprintf("pagerduty-cache-updater-%s", roomId)
}

func topicFuncName(roomId string) string {
	return fmt.Sprintf("pagerduty-topic-updater-%s", roomId)
}

// OncallsByLevel provides sorting by oncall level for []Oncall.
type OncallsByLevel []Oncall

func (a OncallsByLevel) Len() int      { return len(a) }
func (a OncallsByLevel) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a OncallsByLevel) Less(i, j int) bool {
	// sort "always on call" users to the end of the list
	if a[j].Schedule.Summary == "" {
		return true
	}

	return a[i].EscalationLevel < a[j].EscalationLevel
}

func formatOncallReply(wanted string, exactMatchFound bool, oncalls []Oncall) string {
	buf := bytes.NewBuffer([]byte{})

	if exactMatchFound {
		fmt.Fprintf(buf, "exact match found for %q\n", oncalls[0].EscalationPolicy.Summary)
	} else {
		fmt.Fprintf(buf, "%d records matched for query: %q\n", len(oncalls), wanted)
	}

	sort.Sort(OncallsByLevel(oncalls))

	for _, oncall := range oncalls {
		indent := strings.Repeat("    ", oncall.EscalationLevel)
		sched := oncall.Schedule.Summary
		if sched == "" {
			sched = "always on call"
		}

		if exactMatchFound {
			fmt.Fprintf(buf, "%s%s - %s\n", indent, oncall.User.Summary, sched)
		} else {
			fmt.Fprintf(buf, "%s%s - %s - %s\n", indent,
				oncall.EscalationPolicy.Summary, oncall.User.Summary, sched)
		}
	}

	return buf.String()
}
<commit_msg>update to new params type, get the directory code working<commit_after>package pagerduty

/*
 * Copyright 2016 Albert P. Tobey <atobey@netflix.com>
 *
 * 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 (
	"bytes"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/netflix/hal-9001/hal"
)

const OncallUsage = `!oncall <alias>

Find out who is oncall. If only one argument is provided, it must match
a known alias for a Pagerduty service. Otherwise, it is expected to be
a subcommand.

!oncall core
`

const DefaultTopicInterval = "10m"

// TODO: add the service key to the output such that someone trying to contact a team
// can page them from within Slack without having to set up a page alias or go out
// to a web page. !page should be able to take a service key so the output can include something
// like: "To page <team> use the command: !page <servicekey> <message>"
func oncall(msg hal.Evt) {
	parts := msg.BodyAsArgv()

	if len(parts) == 1 {
		msg.Reply(OncallUsage)
		return
	}

	// make sure the pagerduty token is setup in hal.Secrets
	token, err := getSecrets()
	if err != nil || token == "" {
		msg.Replyf("pagerduty: %s is not set up in hal.Secrets. Cannot continue.", PagerdutyTokenKey)
		return
	}

	if parts[1] == "cache-now" {
		msg.Reply("Updating Pagerduty policy cache now.")
		cacheNow(token, msg.RoomId)
		msg.Reply("Pagerduty policy cache update complete.")
		return
	} else if parts[1] == "cache-status" {
		age := int(hal.Cache().Age(CacheKey).Seconds())
		next := time.Time{}
		status := "broken"
		pf := hal.GetPeriodicFunc(cacheFuncName(msg.RoomId))
		if pf != nil {
			next = pf.Last().Add(pf.Interval)
			status = pf.Status()
		}
		msg.Replyf("The cache is %d seconds old. Auto-update is %s and its next update is at %s.",
			age, status, next.Format(time.UnixDate))
		return
	} else if len(parts) > 2 {
		// flatten split words back into position 1, parts isn't used after the ToLower
		parts[1] = strings.Join(parts[1:], " ")
	}

	// TODO: look at the aliases set up for !page and try for an exact match
	// before doing fuzzy search -- move fuzzy search to a "search" subcommand
	// so it's clear that it is not precise
	want := strings.ToLower(parts[1])

	// see if there's an exact match on an alias, e.g. "!oncall core" -> alias.core
	/*
		aliasPref := msg.AsPref().SetUser("").FindKey(aliasKey(want)).One()
		if aliasPref.Success {
			svc, err := GetServiceByKey(token, aliasPref.Value)
			if err == nil {
			}
			// all through to search ...
		}
	*/

	// search over all policies looking for matching policy name, escalation
	// rule name, or service name
	matches := make([]Oncall, 0)
	oncalls := getOncallCache(token, false)
	var exactMatchFound bool

	for _, oncall := range oncalls {
		schedSummary := strings.ToLower(oncall.Schedule.Summary)
		if schedSummary == want {
			matches = append(matches, oncall)
			exactMatchFound = true
			continue
		} else if !exactMatchFound && strings.Contains(schedSummary, want) {
			matches = append(matches, oncall)
			continue
		}

		epSummary := strings.ToLower(oncall.EscalationPolicy.Summary)
		if epSummary == want {
			matches = append(matches, oncall)
			exactMatchFound = true
			continue
		} else if !exactMatchFound && strings.Contains(epSummary, want) {
			matches = append(matches, oncall)
			continue
		}
	}

	// check team names if there were no matches
	// TODO: cache some of these results and always check team names
	if len(matches) == 0 {
		teams, err := GetTeams(token)
		if err != nil {
			log.Printf("REST call to Pagerduty /teams failed: %s", err)
		} else {
			for _, team := range teams {
				ltname := strings.ToLower(team.Name)
				ltdesc := strings.ToLower(team.Description)

				if strings.Contains(ltname, want) || strings.Contains(ltdesc, want) {
					matches = getTeamOncalls(token, team)
				}
			}
		}
	}

	reply := formatOncallReply(want, exactMatchFound, matches)
	msg.Reply(reply)
}

// getTeamOncalls fetches escalation policies for the team then the oncalls for those
// policies and returns a list.
func getTeamOncalls(token string, team Team) []Oncall {
	out := make([]Oncall, 0)
	log.Printf("Returning empty list but should have fetched oncalls for team %+v", team)

	params := map[string][]string{"team_ids[]": []string{team.Id}}
	policies, err := GetEscalationPolicies(token, params)
	if err != nil {
		log.Printf("Error while fetching escalation policies for team id %q: %s", team.Id, err)
		return out
	}

	policy_ids := make([]string, 0)
	for _, policy := range policies {
		policy_ids = append(policy_ids, policy.Id)
	}

	params = map[string][]string{
		"include[]":               []string{"users", "schedules", "escalation_policies"},
		"escalation_policy_ids[]": policy_ids,
	}

	oncalls, err := GetOncalls(token, params)
	if err != nil {
		log.Printf("Error while fetching oncalls for team id %q's policies: %s", team.Id, err)
	} else {
		oncalls2directory(oncalls)
		return oncalls
	}

	return out
}

func getOncallCache(token string, forceUpdate bool) []Oncall {
	oncalls := []Oncall{}

	// see if there's a copy cached
	if hal.Cache().Exists(CacheKey) {
		ttl, err := hal.Cache().Get(CacheKey, &oncalls)
		if err != nil {
			log.Printf("Error retreiving oncalls from the Hal TTL cache: %s", err)
			oncalls = []Oncall{}
		} else if ttl == 0 || forceUpdate {
			oncalls = []Oncall{}
		}
	}

	// the cache exists and is still valid, return it now
	if len(oncalls) > 0 {
		return oncalls
	}

	// get all of the defined policies
	var err error
	params := map[string][]string{
		"include[]": []string{"users", "schedules", "escalation_policies"},
	}
	oncalls, err = GetOncalls(token, params)
	if err != nil {
		log.Printf("Returning empty list. REST call to Pagerduty failed: %s", err)
		return []Oncall{}
	}

	// always update the cache regardless of ttl
	hal.Cache().Set(CacheKey, &oncalls, cacheExpire)

	oncalls2directory(oncalls)

	return oncalls
}

func oncallInit(i *hal.Instance) {
	cacheFreq := hal.GetPref("", "", i.RoomId, "pagerduty", "cache-update-frequency", DefaultCacheInterval)
	cd, err := time.ParseDuration(cacheFreq.Value)
	if err != nil {
		log.Panicf("BUG: could not parse cache update frequency preference: %q", cacheFreq.Value)
	}

	topicFreq := hal.GetPref("", "", i.RoomId, "pagerduty", "topic-update-frequency", DefaultTopicInterval)
	td, err := time.ParseDuration(topicFreq.Value)
	if err != nil {
		log.Panicf("BUG: could not parse topic update frequency preference: %q", topicFreq.Value)
	}

	token, err := getSecrets()
	if err != nil || token == "" {
		return // getSecrets will log the error
	}

	go func() {
		pf := hal.PeriodicFunc{
			Name:     cacheFuncName(i.RoomId),
			Interval: cd,
			Function: func() { cacheNow(token, i.RoomId) },
		}

		pf.Register()
		pf.Start()
	}()

	go func() {
		pf := hal.PeriodicFunc{
			Name:     topicFuncName(i.RoomId),
			Interval: td,
			Function: func() { topicUpdater(token, i.RoomId, i.Broker.Name()) },
		}

		pf.Register()
		pf.Start()
	}()

	// TODO: add a command to stop, etc.
}

func cacheNow(token, roomId string) {
	getOncallCache(token, true)
}

// topicUpdater runs periodically to update the topic in the room
// it's configured in.
// To fully enable it, you need the oncall schedule id from the pagerduty API.
// !prefs set --room * --broker slack --plugin pagerduty --key topic-updater-schedule-id --value <schedule id>
// !prefs set --room * --broker slack --plugin pagerduty --key topic-prefix --value <text>
// !prefs set --room * --broker slack --plugin pagerduty --key topic-suffix --value <text>
// TODO: see if there's a way to also resolve integration keys instead of using the schedule id
func topicUpdater(token, roomId, brokerName string) {
	log.Printf("ENTER topicUpdater(token, %q, %q)", roomId, brokerName)

	pref := hal.GetPref("", brokerName, roomId, "pagerduty", "topic-updater-schedule-id", "-")
	// probably not configured, nothing to see here...
	if !pref.Success || pref.Value == "-" {
		log.Printf("The pref ''/%q/%q/pagerduty/topic-updater-schedule-id does not seem to be set. Returning without taking action.",
			brokerName, roomId)
		return
	}

	params := map[string][]string{
		"include[]":      []string{"users", "schedules", "escalation_policies"},
		"schedule_ids[]": []string{pref.Value},
	}

	oncalls, err := GetOncalls(token, params)
	if err != nil {
		log.Printf("Failed to fetch oncalls for schedule id %q: %s", pref.Value, err)
		return
	}

	log.Printf("Got %d users for schedule id %q", len(oncalls), pref.Value)

	// there may be more than one entry but if they're both on the same
	// schedule it should be the same primary oncall so ignore all but the first
	if len(oncalls) == 0 {
		log.Printf("no oncall results for id %q", pref.Value)
		return
	}

	// TODO: yet another place some kind of templating support would be handy
	prefix := hal.GetPref("", brokerName, roomId, "pagerduty", "topic-prefix", "")
	suffix := hal.GetPref("", brokerName, roomId, "pagerduty", "topic-suffix", "")

	// e.g. prefix = "", summary = "Al Tobey", suffix = " [team-dl@company.com] !pageus"
	topic := prefix.Value + oncalls[0].User.Summary + suffix.Value

	broker := hal.Router().GetBroker(brokerName)

	oldTopic, err := broker.GetTopic(roomId)
	if err != nil {
		log.Printf("Could not fetch current topic for room %q: %s", roomId, err)
		return
	}

	// only do the update if the topic has changed
	if topic != oldTopic {
		broker.SetTopic(roomId, topic)
	}
}

func cacheFuncName(roomId string) string {
	return fmt.Sprintf("pagerduty-cache-updater-%s", roomId)
}

func topicFuncName(roomId string) string {
	return fmt.Sprintf("pagerduty-topic-updater-%s", roomId)
}

// OncallsByLevel provides sorting by oncall level for []Oncall.
type OncallsByLevel []Oncall

func (a OncallsByLevel) Len() int      { return len(a) }
func (a OncallsByLevel) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a OncallsByLevel) Less(i, j int) bool {
	// sort "always on call" users to the end of the list
	if a[j].Schedule.Summary == "" {
		return true
	}

	return a[i].EscalationLevel < a[j].EscalationLevel
}

func formatOncallReply(wanted string, exactMatchFound bool, oncalls []Oncall) string {
	buf := bytes.NewBuffer([]byte{})

	if exactMatchFound {
		fmt.Fprintf(buf, "exact match found for %q\n", oncalls[0].EscalationPolicy.Summary)
	} else {
		fmt.Fprintf(buf, "%d records matched for query: %q\n", len(oncalls), wanted)
	}

	sort.Sort(OncallsByLevel(oncalls))

	for _, oncall := range oncalls {
		indent := strings.Repeat("    ", oncall.EscalationLevel)
		sched := oncall.Schedule.Summary
		if sched == "" {
			sched = "always on call"
		}

		if exactMatchFound {
			fmt.Fprintf(buf, "%s%s - %s\n", indent, oncall.User.Summary, sched)
		} else {
			fmt.Fprintf(buf, "%s%s - %s - %s\n", indent,
				oncall.EscalationPolicy.Summary, oncall.User.Summary, sched)
		}
	}

	return buf.String()
}

func oncalls2directory(oncalls []Oncall) {
	// insert oncalls into the hal directory
	for _, oncall := range oncalls {
		attrs := map[string]string{
			"email":          oncall.User.Email,
			"name":           oncall.User.Name,
			"pd-user-id":     oncall.User.Id,
			"pd-schedule-id": oncall.Schedule.Id,
			"pd-policy-id":   oncall.EscalationPolicy.Id,
		}

		// plug in the contact methods
		// per PD docs, types can be: email_contact_method, phone_contact_method, push_notification_contact_method, or sms_contact_method
		for _, cm := range oncall.User.ContactMethods {
			attrs["contact-method-id"] = cm.Id
			attrs[cm.Type] = cm.Address
		}

		edges := []string{"name", "email", "phone_contact_method", "sms_contact_method"}
		err := hal.Directory().Put(oncall.User.Id, "pd-oncall", attrs, edges)
		if err != nil {
			log.Println(err)
		}
	}
}
<|endoftext|>


<commit_before>package bbs

import "fmt"

//This has all the structs for various commands

type BBSCommand struct {
	Command string `json:"cmd"`
}

type UserCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session"`
}

//From start to end inclusive, starting from 1.
type Range struct {
	Start int `json:"start"`
	End   int `json:"end"`
}

func (r *Range) String() string {
	return fmt.Sprintf("%d-%d", r.Start, r.End)
}

func (r *Range) Validate() bool {
	if r.Start > r.End {
		return false
	}
	return true
}

// "hello" message (server -> client)
type HelloMessage struct {
	Command         string     `json:"cmd"`
	Name            string     `json:"name"`
	ProtocolVersion int        `json:"version"`
	Description     string     `json:"desc"`
	SecureURL       string     `json:"secure,omitempty"` //https URL, if any
	Options         []string   `json:"options,omitempty"`
	Access          AccessInfo `json:"access"`
	Formats         []string   `json:"format"` //formats the server accepts, the first one should be the primary one
	Lists           []string   `json:"lists"`
	ServerVersion   string     `json:"server"`
	IconURL         string     `json:"icon"`
	//for option "range"
	DefaultRange *Range `json:"default_range,omitempty"`
}

// guest commands are commands you can use without logging on (e.g. "list", "get")
// user commands require being logged in first (usually "post" and "reply")
type AccessInfo struct {
	GuestCommands []string `json:"guest,omitempty"`
	UserCommands  []string `json:"user,omitempty"`
}

// "error" message (server -> client)
type ErrorMessage struct {
	Command string `json:"cmd"`
	ReplyTo string `json:"wrt"`
	Error   string `json:"error"`
}

// session expired or invalid? use this
var SessionErrorMessage *ErrorMessage = &ErrorMessage{"error", "session", "Invalid session."}

// "ok" message (server -> client)
type OKMessage struct {
	Command string `json:"cmd"`
	ReplyTo string `json:"wrt"`
	Result  string `json:"result,omitempty"`
}

// "login" command (client -> server)
type LoginCommand struct {
	Command         string `json:"cmd"`
	Username        string `json:"username"`
	Password        string `json:"password"`
	ProtocolVersion int    `json:"version"`
}

// "welcome" message (server -> client)
type WelcomeMessage struct {
	Command  string `json:"cmd"`
	Username string `json:"username,omitempty"` //omit for option 'anon'
	Session  string `json:"session"`
}

// "logout" command (client -> server)
type LogoutCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session"`
}

// "register" command (client -> server)
type RegisterCommand struct {
	Command  string `json:"cmd"`
	Username string `json:"username"`
	Password string `json:"password"`
	Email    string `json:"email"`
}

// "get" command (client -> server)
type GetCommand struct {
	Command   string `json:"cmd"`
	Session   string `json:"session,omitempty"`
	ThreadID  string `json:"id"`
	Range     *Range `json:"range"`            //option: "range"
	Filter    string `json:"filter,omitempty"` //option: "filter"
	Format    string `json:"format,omitempty"`
	NextToken string `json:"next,omitempty"`
}

// "list" command (client -> server)
type ListCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session,omitempty"`
	Type    string `json:"type"`
	Query   string `json:"query"` //board for "boards", tag expression for "tags" (like "Dogs+Pizza-Anime")
	Token   string `json:"token,omitempty"`
}

// "reply" command (client -> server)
type ReplyCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session,omitempty"`
	To      string `json:"to"`
	Text    string `json:"body"`
	Format  string `json:"format,omitempty"`
}

// "post" command (client -> server)
type PostCommand struct {
	Command string   `json:"cmd"`
	Session string   `json:"session,omitempty"`
	Title   string   `json:"title"`
	Text    string   `json:"body"`
	Format  string   `json:"format,omitempty"`
	Board   string   `json:"board,omitempty"` //option: "boards"
	Tags    []string `json:"tags,omitempty"`  //option: "tags"
}

// "msg" message (server -> client) [response to "get"]
type ThreadMessage struct {
	Command   string     `json:"cmd"`
	ID        string     `json:"id"`
	Title     string     `json:"title,omitempty"`
	Range     *Range     `json:"range,omitempty"`
	Closed    bool       `json:"closed,omitempty"`
	Filter    string     `json:"filter,omitempty"` //option: "filter"
	Board     string     `json:"board,omitempty"`  //option: "boards"
	Tags      []string   `json:"tags,omitempty"`   //option: "tags"
	Format    string     `json:"format,omitempty"`
	Messages  []*Message `json:"messages"`
	More      bool       `json:"more,omitempty"`
	NextToken string     `json:"next,omitempty"`
}

func (t *ThreadMessage) Size() int {
	if t.Messages != nil {
		return len(t.Messages)
	}
	return 0
}

// format for posts used in "msg"
type Message struct {
	ID                 string `json:"id"`
	Author             string `json:"user"`
	AuthorID           string `json:"user_id,omitempty"`
	Date               string `json:"date,omitempty"`
	Text               string `json:"body"`
	Signature          string `json:"sig,omitempty"`
	AuthorTitle        string `json:"user_title,omitempty"`   //option: "usertitles"
	AvatarURL          string `json:"avatar,omitempty"`       //option: "avatars"
	AvatarThumbnailURL string `json:"avatar_thumb,omitempty"` //option: "avatars"
	PictureURL         string `json:"img,omitempty"`          //option: "imageboard"
	ThumbnailURL       string `json:"thumb,omitempty"`        //option: "imageboard"
}

type TypedMessage struct {
	Command string `json:"cmd"`
	Type    string `json:"type"`
}

// "list" message where type = "thread" (server -> client)
type ListMessage struct {
	Command   string           `json:"cmd"`
	Type      string           `json:"type"`
	Query     string           `json:"query,omitempty"`
	Threads   []*ThreadListing `json:"threads"`
	NextToken string           `json:"next,omitempty"`
}

// "list" message where type = "board" (server -> client)
type BoardListMessage struct {
	Command string          `json:"cmd"`
	Type    string          `json:"type"`
	Query   string          `json:"query,omitempty"`
	Boards  []*BoardListing `json:"boards"`
}

// format for threads in "list"
type ThreadListing struct {
	ID           string   `json:"id"`
	Title        string   `json:"title"`
	Author       string   `json:"user,omitempty"`
	AuthorID     string   `json:"user_id,omitempty"`
	Date         string   `json:"date,omitempty"`
	PostCount    int      `json:"posts,omitempty"`
	UnreadPosts  int      `json:"unread_posts,omitempty"`
	Sticky       bool     `json:"sticky,omitempty"` //a sticky (aka pinned) topic
	Closed       bool     `json:"closed,omitempty"` //a closed (aka locked) topic
	Tags         []string `json:"tags,omitempty"`   //option: "tags"
	PictureURL   string   `json:"img,omitempty"`    //option: "imageboard"
	ThumbnailURL string   `json:"thumb,omitempty"`  //option: "imageboard"
}

// format for boards in "list"
type BoardListing struct {
	ID          string `json:"id"`
	Name        string `json:"name,omitempty"`
	Description string `json:"desc,omitempty"`
	ThreadCount int    `json:"threads,omitempty"`
	PostCount   int    `json:"posts,omitempty"`
	Date        string `json:"date,omitempty"`
}
<commit_msg>make naming make more sense<commit_after>package bbs

import "fmt"

//This has all the structs for various commands

type BBSCommand struct {
	Command string `json:"cmd"`
}

type UserCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session"`
}

//From start to end inclusive, starting from 1.
type Range struct {
	Start int `json:"start"`
	End   int `json:"end"`
}

func (r *Range) String() string {
	return fmt.Sprintf("%d-%d", r.Start, r.End)
}

func (r *Range) Validate() bool {
	if r.Start > r.End {
		return false
	}
	return true
}

// "hello" message (server -> client)
type HelloMessage struct {
	Command         string     `json:"cmd"`
	Name            string     `json:"name"`
	ProtocolVersion int        `json:"version"`
	Description     string     `json:"desc"`
	SecureURL       string     `json:"secure,omitempty"` //https URL, if any
	Options         []string   `json:"options,omitempty"`
	Access          AccessInfo `json:"access"`
	Formats         []string   `json:"format"` //formats the server accepts, the first one should be the primary one
	Lists           []string   `json:"lists"`
	ServerVersion   string     `json:"server"`
	IconURL         string     `json:"icon"`
	//for option "range"
	DefaultRange *Range `json:"default_range,omitempty"`
}

// guest commands are commands you can use without logging on (e.g. "list", "get")
// user commands require being logged in first (usually "post" and "reply")
type AccessInfo struct {
	GuestCommands []string `json:"guest,omitempty"`
	UserCommands  []string `json:"user,omitempty"`
}

// "error" message (server -> client)
type ErrorMessage struct {
	Command string `json:"cmd"`
	ReplyTo string `json:"wrt"`
	Error   string `json:"error"`
}

// session expired or invalid? use this
var SessionErrorMessage *ErrorMessage = &ErrorMessage{"error", "session", "Invalid session."}

// "ok" message (server -> client)
type OKMessage struct {
	Command string `json:"cmd"`
	ReplyTo string `json:"wrt"`
	Result  string `json:"result,omitempty"`
}

// "login" command (client -> server)
type LoginCommand struct {
	Command         string `json:"cmd"`
	Username        string `json:"username"`
	Password        string `json:"password"`
	ProtocolVersion int    `json:"version"`
}

// "welcome" message (server -> client)
type WelcomeMessage struct {
	Command  string `json:"cmd"`
	Username string `json:"username,omitempty"` //omit for option 'anon'
	Session  string `json:"session"`
}

// "logout" command (client -> server)
type LogoutCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session"`
}

// "register" command (client -> server)
type RegisterCommand struct {
	Command  string `json:"cmd"`
	Username string `json:"username"`
	Password string `json:"password"`
	Email    string `json:"email"`
}

// "get" command (client -> server)
type GetCommand struct {
	Command  string `json:"cmd"`
	Session  string `json:"session,omitempty"`
	ThreadID string `json:"id"`
	Range    *Range `json:"range"`            //option: "range"
	Filter   string `json:"filter,omitempty"` //option: "filter"
	Format   string `json:"format,omitempty"`
	Token    string `json:"token,omitempty"`
}

// "list" command (client -> server)
type ListCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session,omitempty"`
	Type    string `json:"type"`
	Query   string `json:"query"` //board for "boards", tag expression for "tags" (like "Dogs+Pizza-Anime")
	Token   string `json:"token,omitempty"`
}

// "reply" command (client -> server)
type ReplyCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session,omitempty"`
	To      string `json:"to"`
	Text    string `json:"body"`
	Format  string `json:"format,omitempty"`
}

// "post" command (client -> server)
type PostCommand struct {
	Command string   `json:"cmd"`
	Session string   `json:"session,omitempty"`
	Title   string   `json:"title"`
	Text    string   `json:"body"`
	Format  string   `json:"format,omitempty"`
	Board   string   `json:"board,omitempty"` //option: "boards"
	Tags    []string `json:"tags,omitempty"`  //option: "tags"
}

// "msg" message (server -> client) [response to "get"]
type ThreadMessage struct {
	Command   string     `json:"cmd"`
	ID        string     `json:"id"`
	Title     string     `json:"title,omitempty"`
	Range     *Range     `json:"range,omitempty"`
	Closed    bool       `json:"closed,omitempty"`
	Filter    string     `json:"filter,omitempty"` //option: "filter"
	Board     string     `json:"board,omitempty"`  //option: "boards"
	Tags      []string   `json:"tags,omitempty"`   //option: "tags"
	Format    string     `json:"format,omitempty"`
	Messages  []*Message `json:"messages"`
	More      bool       `json:"more,omitempty"`
	NextToken string     `json:"next,omitempty"`
}

func (t *ThreadMessage) Size() int {
	if t.Messages != nil {
		return len(t.Messages)
	}
	return 0
}

// format for posts used in "msg"
type Message struct {
	ID                 string `json:"id"`
	Author             string `json:"user"`
	AuthorID           string `json:"user_id,omitempty"`
	Date               string `json:"date,omitempty"`
	Text               string `json:"body"`
	Signature          string `json:"sig,omitempty"`
	AuthorTitle        string `json:"user_title,omitempty"`   //option: "usertitles"
	AvatarURL          string `json:"avatar,omitempty"`       //option: "avatars"
	AvatarThumbnailURL string `json:"avatar_thumb,omitempty"` //option: "avatars"
	PictureURL         string `json:"img,omitempty"`          //option: "imageboard"
	ThumbnailURL       string `json:"thumb,omitempty"`        //option: "imageboard"
}

type TypedMessage struct {
	Command string `json:"cmd"`
	Type    string `json:"type"`
}

// "list" message where type = "thread" (server -> client)
type ListMessage struct {
	Command   string           `json:"cmd"`
	Type      string           `json:"type"`
	Query     string           `json:"query,omitempty"`
	Threads   []*ThreadListing `json:"threads"`
	NextToken string           `json:"next,omitempty"`
}

// "list" message where type = "board" (server -> client)
type BoardListMessage struct {
	Command string          `json:"cmd"`
	Type    string          `json:"type"`
	Query   string          `json:"query,omitempty"`
	Boards  []*BoardListing `json:"boards"`
}

// format for threads in "list"
type ThreadListing struct {
	ID           string   `json:"id"`
	Title        string   `json:"title"`
	Author       string   `json:"user,omitempty"`
	AuthorID     string   `json:"user_id,omitempty"`
	Date         string   `json:"date,omitempty"`
	PostCount    int      `json:"posts,omitempty"`
	UnreadPosts  int      `json:"unread_posts,omitempty"`
	Sticky       bool     `json:"sticky,omitempty"` //a sticky (aka pinned) topic
	Closed       bool     `json:"closed,omitempty"` //a closed (aka locked) topic
	Tags         []string `json:"tags,omitempty"`   //option: "tags"
	PictureURL   string   `json:"img,omitempty"`    //option: "imageboard"
	ThumbnailURL string   `json:"thumb,omitempty"`  //option: "imageboard"
}

// format for boards in "list"
type BoardListing struct {
	ID          string `json:"id"`
	Name        string `json:"name,omitempty"`
	Description string `json:"desc,omitempty"`
	ThreadCount int    `json:"threads,omitempty"`
	PostCount   int    `json:"posts,omitempty"`
	Date        string `json:"date,omitempty"`
}
<|endoftext|>


<commit_before>// Package glob implements globbing for elvish.
package glob

import (
	"io/ioutil"
	"os"
	"sort"
	"unicode/utf8"
)

// Pattern is a glob pattern.
type Pattern struct {
	Segments []Segment
}

// Segment is the constituent unit of a Pattern.
type Segment struct {
	// Type of the Segment.
	Type    SegmentType
	Literal string // Valid for Literal only.
}

// SegmentType is the type of a Segment.
type SegmentType int

// Values for SegmentType.
const (
	Literal SegmentType = iota
	Star
	Question
	StarStar
	Slash
)

// Glob returns a list of file names satisfying the given pattern.
func Glob(p string) []string {
	return Parse(p).Glob()
}

// Glob returns a list of file names satisfying the Pattern.
func (p Pattern) Glob() []string {
	segs := p.Segments
	dir := ""
	if len(segs) > 0 && segs[0].Type == Slash {
		segs = segs[1:]
		dir = "/"
	}

	results := []string{}
	ch := make(chan string)
	go func() {
		glob(segs, dir, ch)
		close(ch)
	}()

	for s := range ch {
		results = append(results, s)
	}
	return results
}

func glob(segs []Segment, dir string, results chan<- string) {
	// Empty segment, resulting from a trailing slash. Generate the starting
	// directory.
	if len(segs) == 0 {
		results <- dir + "/"
		return
	}

	var prefix string
	if dir == "" {
		prefix = ""
		dir = "."
	} else if dir == "/" {
		prefix = "/"
	} else {
		// dir never has a trailing slash unless it is /.
		prefix = dir + "/"
	}

	i := -1
	nexti := func() {
		for i++; i < len(segs); i++ {
			if segs[i].Type == Slash || segs[i].Type == StarStar {
				break
			}
		}
	}
	nexti()

	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		// XXX Silently drop the error
		return
	}

	// Enumerate the position of the first slash.
	for i < len(segs) {
		slash := segs[i].Type == Slash
		var first, rest []Segment
		if slash {
			// segs = x/y. Match dir with x, recurse on y.
			first, rest = segs[:i], segs[i+1:]
		} else {
			// segs = x**y. Match dir with x*, recurse on **y.
			first, rest = segs[:i+1], segs[i:]
		}

		for _, info := range infos {
			name := info.Name()
			if match(first, name) && info.IsDir() {
				glob(rest, prefix+name, results)
			}
		}

		if slash {
			// First slash cannot appear later than a slash in the pattern.
			return
		}
		nexti()
	}

	// If we reach here, it is possible to have no slashes at all.
	for _, info := range infos {
		name := info.Name()
		if match(segs, name) {
			results <- prefix + name
		}
	}
}

func filenames(dir string) ([]string, error) {
	f, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	names, err := f.Readdirnames(-1)
	f.Close()
	if err != nil {
		return nil, err
	}
	sort.Strings(names)
	return names, nil
}

// match matches a name against segments. It treats StarStar segments as they
// are Star segments. The segments may not contain Slash'es.
func match(segs []Segment, name string) bool {
segs:
	for len(segs) > 0 {
		// Find a chunk. A chunk is a run of Literal and Question, with an
		// optional leading Star.
		var i int
		for i = 1; i < len(segs); i++ {
			if segs[i].Type == Star || segs[i].Type == StarStar {
				break
			}
		}

		chunk := segs[:i]
		star := chunk[0].Type == Star || chunk[0].Type == StarStar
		if star {
			chunk = chunk[1:]
		}
		segs = segs[i:]

		// NOTE A quick path when len(segs) == 0 can be implemented: match
		// backwards.

		// Match at the current position. If this is the last chunk, we need to
		// make sure name is exhausted by the matching.
		ok, rest := matchChunk(chunk, name)
		if ok && (rest == "" || len(segs) > 0) {
			name = rest
			continue
		}

		if star {
			// NOTE An optimization is to make the upper bound not len(names),
			// but rather len(names) - LB(# bytes segs can match)
			for i := 1; i <= len(name); i++ {
				// Match after skipping i bytes.
				ok, rest := matchChunk(chunk, name[i:])
				if ok && (rest == "" || len(segs) > 0) {
					name = rest
					continue segs
				}
			}
		}
		return false
	}
	return name == ""
}

// matchChunk returns whether a chunk matches a prefix of name. If suceeded, it
// also returns the remaining part of name.
func matchChunk(chunk []Segment, name string) (bool, string) {
	for _, seg := range chunk {
		if name == "" {
			return false, ""
		}
		switch seg.Type {
		case Literal:
			n := len(seg.Literal)
			if len(name) < n || name[:n] != seg.Literal {
				return false, ""
			}
			name = name[n:]
		case Question:
			_, n := utf8.DecodeRuneInString(name)
			name = name[n:]
		}
	}
	return true, name
}
<commit_msg>glob: Remove unused func.<commit_after>// Package glob implements globbing for elvish.
package glob

import (
	"io/ioutil"
	"unicode/utf8"
)

// Pattern is a glob pattern.
type Pattern struct {
	Segments []Segment
}

// Segment is the constituent unit of a Pattern.
type Segment struct {
	// Type of the Segment.
	Type    SegmentType
	Literal string // Valid for Literal only.
}

// SegmentType is the type of a Segment.
type SegmentType int

// Values for SegmentType.
const (
	Literal SegmentType = iota
	Star
	Question
	StarStar
	Slash
)

// Glob returns a list of file names satisfying the given pattern.
func Glob(p string) []string {
	return Parse(p).Glob()
}

// Glob returns a list of file names satisfying the Pattern.
func (p Pattern) Glob() []string {
	segs := p.Segments
	dir := ""
	if len(segs) > 0 && segs[0].Type == Slash {
		segs = segs[1:]
		dir = "/"
	}

	results := []string{}
	ch := make(chan string)
	go func() {
		glob(segs, dir, ch)
		close(ch)
	}()

	for s := range ch {
		results = append(results, s)
	}
	return results
}

func glob(segs []Segment, dir string, results chan<- string) {
	// Empty segment, resulting from a trailing slash. Generate the starting
	// directory.
	if len(segs) == 0 {
		results <- dir + "/"
		return
	}

	var prefix string
	if dir == "" {
		prefix = ""
		dir = "."
	} else if dir == "/" {
		prefix = "/"
	} else {
		// dir never has a trailing slash unless it is /.
		prefix = dir + "/"
	}

	i := -1
	nexti := func() {
		for i++; i < len(segs); i++ {
			if segs[i].Type == Slash || segs[i].Type == StarStar {
				break
			}
		}
	}
	nexti()

	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		// XXX Silently drop the error
		return
	}

	// Enumerate the position of the first slash.
	for i < len(segs) {
		slash := segs[i].Type == Slash
		var first, rest []Segment
		if slash {
			// segs = x/y. Match dir with x, recurse on y.
			first, rest = segs[:i], segs[i+1:]
		} else {
			// segs = x**y. Match dir with x*, recurse on **y.
			first, rest = segs[:i+1], segs[i:]
		}

		for _, info := range infos {
			name := info.Name()
			if match(first, name) && info.IsDir() {
				glob(rest, prefix+name, results)
			}
		}

		if slash {
			// First slash cannot appear later than a slash in the pattern.
			return
		}
		nexti()
	}

	// If we reach here, it is possible to have no slashes at all.
	for _, info := range infos {
		name := info.Name()
		if match(segs, name) {
			results <- prefix + name
		}
	}
}

// match matches a name against segments. It treats StarStar segments as they
// are Star segments. The segments may not contain Slash'es.
func match(segs []Segment, name string) bool {
segs:
	for len(segs) > 0 {
		// Find a chunk. A chunk is a run of Literal and Question, with an
		// optional leading Star.
		var i int
		for i = 1; i < len(segs); i++ {
			if segs[i].Type == Star || segs[i].Type == StarStar {
				break
			}
		}

		chunk := segs[:i]
		star := chunk[0].Type == Star || chunk[0].Type == StarStar
		if star {
			chunk = chunk[1:]
		}
		segs = segs[i:]

		// NOTE A quick path when len(segs) == 0 can be implemented: match
		// backwards.

		// Match at the current position. If this is the last chunk, we need to
		// make sure name is exhausted by the matching.
		ok, rest := matchChunk(chunk, name)
		if ok && (rest == "" || len(segs) > 0) {
			name = rest
			continue
		}

		if star {
			// NOTE An optimization is to make the upper bound not len(names),
			// but rather len(names) - LB(# bytes segs can match)
			for i := 1; i <= len(name); i++ {
				// Match after skipping i bytes.
				ok, rest := matchChunk(chunk, name[i:])
				if ok && (rest == "" || len(segs) > 0) {
					name = rest
					continue segs
				}
			}
		}
		return false
	}
	return name == ""
}

// matchChunk returns whether a chunk matches a prefix of name. If suceeded, it
// also returns the remaining part of name.
func matchChunk(chunk []Segment, name string) (bool, string) {
	for _, seg := range chunk {
		if name == "" {
			return false, ""
		}
		switch seg.Type {
		case Literal:
			n := len(seg.Literal)
			if len(name) < n || name[:n] != seg.Literal {
				return false, ""
			}
			name = name[n:]
		case Question:
			_, n := utf8.DecodeRuneInString(name)
			name = name[n:]
		}
	}
	return true, name
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/ViBiOh/funds-ob/go/morningStar"
	"log"
	"net/http"
	"runtime"
	"strconv"
)

const port = `1080`

func main() {
	numCpu := runtime.NumCPU()
	runtime.GOMAXPROCS(numCpu)
	log.Print(`MaxProc setted to ` + strconv.Itoa(numCpu))

	http.Handle(`/`, morningStar.Handler{})
	log.Print(`Starting server on port ` + port)
	log.Fatal(http.ListenAndServe(`:`+port, nil))
}
<commit_msg>Removing changing maxProc<commit_after>package main

import (
	"github.com/ViBiOh/funds-ob/go/morningStar"
	"log"
	"net/http"
)

const port = `1080`

func main() {
	http.Handle(`/`, morningStar.Handler{})
	log.Print(`Starting server on port ` + port)
	log.Fatal(http.ListenAndServe(`:`+port, nil))
}
<|endoftext|>