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

package button

// options.go contains configurable options for Button.

import (
	"fmt"
	"time"

	"github.com/mum4k/termdash/cell"
	"github.com/mum4k/termdash/keyboard"
	"github.com/mum4k/termdash/private/runewidth"
	"github.com/mum4k/termdash/widgetapi"
)

// Option is used to provide options.
type Option interface {
	// set sets the provided option.
	set(*options)
}

// option implements Option.
type option func(*options)

// set implements Option.set.
func (o option) set(opts *options) {
	o(opts)
}

// options holds the provided options.
type options struct {
	fillColor   cell.Color
	textColor   cell.Color
	shadowColor cell.Color
	height      int
	width       int
	key         keyboard.Key
	keyScope    widgetapi.KeyScope
	keyUpDelay  time.Duration
}

// validate validates the provided options.
func (o *options) validate() error {
	if min := 1; o.height < min {
		return fmt.Errorf("invalid height %d, must be %d <= height", o.height, min)
	}
	if min := 1; o.width < min {
		return fmt.Errorf("invalid width %d, must be %d <= width", o.width, min)
	}
	if min := time.Duration(0); o.keyUpDelay < min {
		return fmt.Errorf("invalid keyUpDelay %v, must be %v <= keyUpDelay", o.keyUpDelay, min)
	}
	return nil
}

// newOptions returns options with the default values set.
func newOptions(text string) *options {
	return &options{
		fillColor:   cell.ColorNumber(117),
		textColor:   cell.ColorBlack,
		shadowColor: cell.ColorNumber(240),
		height:      DefaultHeight,
		width:       widthFor(text),
		keyUpDelay:  DefaultKeyUpDelay,
	}
}

// FillColor sets the fill color of the button.
func FillColor(c cell.Color) Option {
	return option(func(opts *options) {
		opts.fillColor = c
	})
}

// TextColor sets the color of the text label in the button.
func TextColor(c cell.Color) Option {
	return option(func(opts *options) {
		opts.textColor = c
	})
}

// ShadowColor sets the color of the shadow under the button.
func ShadowColor(c cell.Color) Option {
	return option(func(opts *options) {
		opts.shadowColor = c
	})
}

// DefaultHeight is the default for the Height option.
const DefaultHeight = 3

// Height sets the height of the button in cells.
// Must be a positive non-zero integer.
// Defaults to DefaultHeight.
func Height(cells int) Option {
	return option(func(opts *options) {
		opts.height = cells
	})
}

// Width sets the width of the button in cells.
// Must be a positive non-zero integer.
// Defaults to the auto-width based on the length of the text label.
func Width(cells int) Option {
	return option(func(opts *options) {
		opts.width = cells
	})
}

// WidthFor sets the width of the button as if it was displaying the provided text.
// Useful when displaying multiple buttons with the intention to set all of
// their sizes equal to the one with the longest text.
func WidthFor(text string) Option {
	return option(func(opts *options) {
		opts.width = widthFor(text)
	})
}

// Key configures the keyboard key that presses the button.
// The widget responds to this key only if its container if focused.
// When not provided, the widget ignores all keyboard events.
func Key(k keyboard.Key) Option {
	return option(func(opts *options) {
		opts.key = k
		opts.keyScope = widgetapi.KeyScopeFocused
	})
}

// GlobalKey is like Key, but makes the widget respond to the key even if its
// container isn't focused.
// When not provided, the widget ignores all keyboard events.
func GlobalKey(k keyboard.Key) Option {
	return option(func(opts *options) {
		opts.key = k
		opts.keyScope = widgetapi.KeyScopeGlobal
	})
}

// DefaultKeyUpDelay is the default value for the KeyUpDelay option.
const DefaultKeyUpDelay = 250 * time.Millisecond

// KeyUpDelay is the amount of time the button will remain "pressed down" after
// triggered by the configured key. Termbox doesn't emit events for key
// releases so the button simulates it by timing it.
// This only works if the manual termdash redraw or the periodic redraw
// interval are reasonably close to this delay.
// The duration cannot be negative.
// Defaults to DefaultKeyUpDelay.
func KeyUpDelay(d time.Duration) Option {
	return option(func(opts *options) {
		opts.keyUpDelay = d
	})
}

// widthFor returns the required width for the specified text.
func widthFor(text string) int {
	return runewidth.StringWidth(text) + 2 // One empty cell at each side.
}
<commit_msg>Fixing typo in a comment.<commit_after>// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package button

// options.go contains configurable options for Button.

import (
	"fmt"
	"time"

	"github.com/mum4k/termdash/cell"
	"github.com/mum4k/termdash/keyboard"
	"github.com/mum4k/termdash/private/runewidth"
	"github.com/mum4k/termdash/widgetapi"
)

// Option is used to provide options.
type Option interface {
	// set sets the provided option.
	set(*options)
}

// option implements Option.
type option func(*options)

// set implements Option.set.
func (o option) set(opts *options) {
	o(opts)
}

// options holds the provided options.
type options struct {
	fillColor   cell.Color
	textColor   cell.Color
	shadowColor cell.Color
	height      int
	width       int
	key         keyboard.Key
	keyScope    widgetapi.KeyScope
	keyUpDelay  time.Duration
}

// validate validates the provided options.
func (o *options) validate() error {
	if min := 1; o.height < min {
		return fmt.Errorf("invalid height %d, must be %d <= height", o.height, min)
	}
	if min := 1; o.width < min {
		return fmt.Errorf("invalid width %d, must be %d <= width", o.width, min)
	}
	if min := time.Duration(0); o.keyUpDelay < min {
		return fmt.Errorf("invalid keyUpDelay %v, must be %v <= keyUpDelay", o.keyUpDelay, min)
	}
	return nil
}

// newOptions returns options with the default values set.
func newOptions(text string) *options {
	return &options{
		fillColor:   cell.ColorNumber(117),
		textColor:   cell.ColorBlack,
		shadowColor: cell.ColorNumber(240),
		height:      DefaultHeight,
		width:       widthFor(text),
		keyUpDelay:  DefaultKeyUpDelay,
	}
}

// FillColor sets the fill color of the button.
func FillColor(c cell.Color) Option {
	return option(func(opts *options) {
		opts.fillColor = c
	})
}

// TextColor sets the color of the text label in the button.
func TextColor(c cell.Color) Option {
	return option(func(opts *options) {
		opts.textColor = c
	})
}

// ShadowColor sets the color of the shadow under the button.
func ShadowColor(c cell.Color) Option {
	return option(func(opts *options) {
		opts.shadowColor = c
	})
}

// DefaultHeight is the default for the Height option.
const DefaultHeight = 3

// Height sets the height of the button in cells.
// Must be a positive non-zero integer.
// Defaults to DefaultHeight.
func Height(cells int) Option {
	return option(func(opts *options) {
		opts.height = cells
	})
}

// Width sets the width of the button in cells.
// Must be a positive non-zero integer.
// Defaults to the auto-width based on the length of the text label.
func Width(cells int) Option {
	return option(func(opts *options) {
		opts.width = cells
	})
}

// WidthFor sets the width of the button as if it was displaying the provided text.
// Useful when displaying multiple buttons with the intention to set all of
// their sizes equal to the one with the longest text.
func WidthFor(text string) Option {
	return option(func(opts *options) {
		opts.width = widthFor(text)
	})
}

// Key configures the keyboard key that presses the button.
// The widget responds to this key only if its container is focused.
// When not provided, the widget ignores all keyboard events.
func Key(k keyboard.Key) Option {
	return option(func(opts *options) {
		opts.key = k
		opts.keyScope = widgetapi.KeyScopeFocused
	})
}

// GlobalKey is like Key, but makes the widget respond to the key even if its
// container isn't focused.
// When not provided, the widget ignores all keyboard events.
func GlobalKey(k keyboard.Key) Option {
	return option(func(opts *options) {
		opts.key = k
		opts.keyScope = widgetapi.KeyScopeGlobal
	})
}

// DefaultKeyUpDelay is the default value for the KeyUpDelay option.
const DefaultKeyUpDelay = 250 * time.Millisecond

// KeyUpDelay is the amount of time the button will remain "pressed down" after
// triggered by the configured key. Termbox doesn't emit events for key
// releases so the button simulates it by timing it.
// This only works if the manual termdash redraw or the periodic redraw
// interval are reasonably close to this delay.
// The duration cannot be negative.
// Defaults to DefaultKeyUpDelay.
func KeyUpDelay(d time.Duration) Option {
	return option(func(opts *options) {
		opts.keyUpDelay = d
	})
}

// widthFor returns the required width for the specified text.
func widthFor(text string) int {
	return runewidth.StringWidth(text) + 2 // One empty cell at each side.
}
<|endoftext|>


<commit_before>package wordwrap

import (
	"strings"
	"testing"
)

func TestSimple(t *testing.T) {
	for _, tt := range []struct {
		In    string
		Width int
		Out   string
	}{
		{"", 3, ""},
		{"a", 3, "a"},
		{"aa", 3, "aa"},
		{"aa bb", 3, "aa \nbb"},
		{"aa bb ddd", 7, "aa bb \nddd"},
		{"aaa bb cc ddddd", 7, "aaa bb \ncc \nddddd"},
		{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis egestas nisl urna, vel accumsan libero bibendum id. In consectetur facilisis iaculis. Vivamus cursus hendrerit neque, et bibendum leo accumsan ac.", 20, "Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit. \nDuis egestas nisl \nurna, vel accumsan \nlibero bibendum id. \nIn consectetur \nfacilisis iaculis. \nVivamus cursus \nhendrerit neque, et \nbibendum leo \naccumsan ac."},
		{"aaa bb\n\ncc ddddd", 7, "aaa bb\n\ncc \nddddd"},
		{"Nulla lorem magna, efficitur interdum ante at, convallis sodales nulla. Sed maximus tempor condimentum.\n\nNam et risus est. Cras ornare iaculis orci, sit amet fringilla nisl pharetra quis.", 30, "Nulla lorem magna, efficitur \ninterdum ante at, convallis \nsodales nulla. Sed maximus \ntempor condimentum.\n\nNam et risus est. Cras \nornare iaculis orci, sit amet \nfringilla nisl pharetra quis."},
		{"Nulla lorem magna, efficitur interdum ante at, convallis sodales nulla. Sed maximus tempor condimentum.\n\nNam et risus est. Cras ornare iaculis orci, sit amet fringilla nisl pharetra quis.", 35, "Nulla lorem magna, efficitur \ninterdum ante at, convallis \nsodales nulla. Sed maximus tempor \ncondimentum.\n\nNam et risus est. Cras ornare \niaculis orci, sit amet fringilla \nnisl pharetra quis."},
		{"\n\nNam et risus est.", 30, "\n\nNam et risus est."},
		{"a\n\na\n\n", 6, "a\n\na\n\n"},
	} {
		t.Run("", func(t *testing.T) {
			if got := WrapString(tt.In, tt.Width); got != tt.Out {
				padding := strings.Repeat(".", tt.Width)
				t.Fatalf("\n\ngot = \n\n%s\n%s\n%s\n\nwant = \n\n%s\n%s\n%s", padding, got, padding, padding, tt.Out, padding)
			}
		})
	}
}
<commit_msg>add a wordwrap test against unicode characters<commit_after>package wordwrap

import (
	"strings"
	"testing"
)

func TestSimple(t *testing.T) {
	for _, tt := range []struct {
		In    string
		Width int
		Out   string
	}{
		{"", 3, ""},
		{"a", 3, "a"},
		{"aa", 3, "aa"},
		{"aa bb", 3, "aa \nbb"},
		{"aa bb ddd", 7, "aa bb \nddd"},
		{"aaa bb cc ddddd", 7, "aaa bb \ncc \nddddd"},
		{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis egestas nisl urna, vel accumsan libero bibendum id. In consectetur facilisis iaculis. Vivamus cursus hendrerit neque, et bibendum leo accumsan ac.", 20, "Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit. \nDuis egestas nisl \nurna, vel accumsan \nlibero bibendum id. \nIn consectetur \nfacilisis iaculis. \nVivamus cursus \nhendrerit neque, et \nbibendum leo \naccumsan ac."},
		{"aaa bb\n\ncc ddddd", 7, "aaa bb\n\ncc \nddddd"},
		{"Nulla lorem magna, efficitur interdum ante at, convallis sodales nulla. Sed maximus tempor condimentum.\n\nNam et risus est. Cras ornare iaculis orci, sit amet fringilla nisl pharetra quis.", 30, "Nulla lorem magna, efficitur \ninterdum ante at, convallis \nsodales nulla. Sed maximus \ntempor condimentum.\n\nNam et risus est. Cras \nornare iaculis orci, sit amet \nfringilla nisl pharetra quis."},
		{"Nulla lorem magna, efficitur interdum ante at, convallis sodales nulla. Sed maximus tempor condimentum.\n\nNam et risus est. Cras ornare iaculis orci, sit amet fringilla nisl pharetra quis.", 35, "Nulla lorem magna, efficitur \ninterdum ante at, convallis \nsodales nulla. Sed maximus tempor \ncondimentum.\n\nNam et risus est. Cras ornare \niaculis orci, sit amet fringilla \nnisl pharetra quis."},
		{"\n\nNam et risus est.", 30, "\n\nNam et risus est."},
		{"a\n\na\n\n", 6, "a\n\na\n\n"},
		{"null set ∅", 11, "null set ∅"},
	} {
		t.Run("", func(t *testing.T) {
			if got := WrapString(tt.In, tt.Width); got != tt.Out {
				padding := strings.Repeat(".", tt.Width)
				t.Fatalf("\n\ngot = \n\n%s\n%s\n%s\n\nwant = \n\n%s\n%s\n%s", padding, got, padding, padding, tt.Out, padding)
			}
		})
	}
}
<|endoftext|>


<commit_before>// An interface for the Basic Linear Algebra Subprograms (BLAS)
//
// Reference: http://www.netlib.org/blas/
package blas

// #cgo LDFLAGS: -L. -lgfortran -lblas
// #include "blas.h"
import "C"

import "unsafe"

// General matrix-vector multiplication according to the formula
//
// y := alpha * a * x + beta * y
//
// where
//
// a is an (m x n) matrix,
// x is an n-element vector, and
// y is an m-element vector.
//
// Reference: http://www.netlib.org/lapack/explore-html/dc/da8/dgemv_8f.html
func Dgemv(trans byte, m, n int, alpha float64, a []float64, lda int,
	x []float64, incx int, beta float64, y []float64, incy int) {

	C.dgemv_(
		(*C.char)(unsafe.Pointer(&trans)),
		(*C.int)(unsafe.Pointer(&m)),
		(*C.int)(unsafe.Pointer(&n)),
		(*C.double)(unsafe.Pointer(&alpha)),
		(*C.double)(unsafe.Pointer(&a[0])),
		(*C.int)(unsafe.Pointer(&lda)),
		(*C.double)(unsafe.Pointer(&x[0])),
		(*C.int)(unsafe.Pointer(&incx)),
		(*C.double)(unsafe.Pointer(&beta)),
		(*C.double)(unsafe.Pointer(&y[0])),
		(*C.int)(unsafe.Pointer(&incy)))
}

// General matrix-matrix multiplication according to the formula
//
// c := alpha * a * b + beta * c
//
// where
//
// a is an (m x k) matrix,
// b is a (k x n) matrix, and
// c is an (m x n) matrix.
//
// Reference: http://www.netlib.org/lapack/explore-html/d7/d2b/dgemm_8f.html
func Dgemm(transa, transb byte, m, n, k int, alpha float64, a []float64,
	lda int, b []float64, ldb int, beta float64, c []float64, ldc int) {

	C.dgemm_(
		(*C.char)(unsafe.Pointer(&transa)),
		(*C.char)(unsafe.Pointer(&transb)),
		(*C.int)(unsafe.Pointer(&m)),
		(*C.int)(unsafe.Pointer(&n)),
		(*C.int)(unsafe.Pointer(&k)),
		(*C.double)(unsafe.Pointer(&alpha)),
		(*C.double)(unsafe.Pointer(&a[0])),
		(*C.int)(unsafe.Pointer(&lda)),
		(*C.double)(unsafe.Pointer(&b[0])),
		(*C.int)(unsafe.Pointer(&ldb)),
		(*C.double)(unsafe.Pointer(&beta)),
		(*C.double)(unsafe.Pointer(&c[0])),
		(*C.int)(unsafe.Pointer(&ldc)))
}
<commit_msg>Changed the order of -l in LDFLAGS for blas<commit_after>// An interface for the Basic Linear Algebra Subprograms (BLAS)
//
// Reference: http://www.netlib.org/blas/
package blas

// #cgo LDFLAGS: -L. -lblas -lgfortran
// #include "blas.h"
import "C"

import "unsafe"

// General matrix-vector multiplication according to the formula
//
// y := alpha * a * x + beta * y
//
// where
//
// a is an (m x n) matrix,
// x is an n-element vector, and
// y is an m-element vector.
//
// Reference: http://www.netlib.org/lapack/explore-html/dc/da8/dgemv_8f.html
func Dgemv(trans byte, m, n int, alpha float64, a []float64, lda int,
	x []float64, incx int, beta float64, y []float64, incy int) {

	C.dgemv_(
		(*C.char)(unsafe.Pointer(&trans)),
		(*C.int)(unsafe.Pointer(&m)),
		(*C.int)(unsafe.Pointer(&n)),
		(*C.double)(unsafe.Pointer(&alpha)),
		(*C.double)(unsafe.Pointer(&a[0])),
		(*C.int)(unsafe.Pointer(&lda)),
		(*C.double)(unsafe.Pointer(&x[0])),
		(*C.int)(unsafe.Pointer(&incx)),
		(*C.double)(unsafe.Pointer(&beta)),
		(*C.double)(unsafe.Pointer(&y[0])),
		(*C.int)(unsafe.Pointer(&incy)))
}

// General matrix-matrix multiplication according to the formula
//
// c := alpha * a * b + beta * c
//
// where
//
// a is an (m x k) matrix,
// b is a (k x n) matrix, and
// c is an (m x n) matrix.
//
// Reference: http://www.netlib.org/lapack/explore-html/d7/d2b/dgemm_8f.html
func Dgemm(transa, transb byte, m, n, k int, alpha float64, a []float64,
	lda int, b []float64, ldb int, beta float64, c []float64, ldc int) {

	C.dgemm_(
		(*C.char)(unsafe.Pointer(&transa)),
		(*C.char)(unsafe.Pointer(&transb)),
		(*C.int)(unsafe.Pointer(&m)),
		(*C.int)(unsafe.Pointer(&n)),
		(*C.int)(unsafe.Pointer(&k)),
		(*C.double)(unsafe.Pointer(&alpha)),
		(*C.double)(unsafe.Pointer(&a[0])),
		(*C.int)(unsafe.Pointer(&lda)),
		(*C.double)(unsafe.Pointer(&b[0])),
		(*C.int)(unsafe.Pointer(&ldb)),
		(*C.double)(unsafe.Pointer(&beta)),
		(*C.double)(unsafe.Pointer(&c[0])),
		(*C.int)(unsafe.Pointer(&ldc)))
}
<|endoftext|>


<commit_before>// Copyright ©2013 The bíogo Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package blast

import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"

	"github.com/biogo/ncbi"

	"golang.org/x/net/html"
)

// Blast server usage policy requires that users not poll for any single RID more often than once
// a minute. The blast package Get requests honour this policy.
const RidPollLimit = 60 * time.Second

// Rid implements RID recovery and waiting functions associated with Blast Put and Get requests.
type Rid struct {
	rid   string
	rtoe  time.Time
	delay <-chan time.Time
	limit *ncbi.Limiter
}

// NewRid returns a Rid with the given request ID string. The returned Rid has a
// zero RTOE but is subject to the usage policy poll limiter. It is intended to
// be used to retrieve results from queries submitted without a call to Put.
func NewRid(rid string) *Rid {
	delay := make(chan time.Time)
	close(delay)
	return &Rid{
		rid:   rid,
		delay: delay,
		limit: ncbi.NewLimiter(RidPollLimit),
	}
}

var (
	messageIDBytes = []byte("Message ID")
	errorBytes     = []byte("Error:")
)

type ErrBadRequest string

func (e ErrBadRequest) Error() string { return string(e) }

func (rid *Rid) unmarshal(r io.Reader) error {
	z := html.NewTokenizer(r)
	for {
		tt := z.Next()
		if tt == html.ErrorToken {
			return z.Err()
		}
		if tt == html.TextToken {
			text := z.Text()
			if bytes.HasPrefix(text, messageIDBytes) && bytes.Contains(text, errorBytes) {
				rid.setElapsedDelay()
				return ErrBadRequest(text)
			}
		}
		if tt != html.CommentToken {
			continue
		}
		d := z.Token().Data
		if !strings.Contains(d, "QBlastInfoBegin") {
			continue
		}
		for _, l := range strings.Split(d, "\n") {
			l = strings.TrimSpace(l)
			kv := strings.Split(l, " = ")
			if len(kv) != 2 {
				continue
			}
			switch kv[0] {
			case "RID":
				rid.rid = kv[1]
			case "RTOE":
				rt, err := strconv.ParseInt(kv[1], 10, 64)
				if err != nil {
					return err
				}
				secs := time.Duration(rt) * time.Second
				rid.delay = time.After(secs)
				rid.rtoe = time.Now().Add(secs)
			}
		}
		if rid.rid == "" || rid.delay == nil {
			rid.setElapsedDelay()
			return ErrMissingRid
		}
		rid.limit = ncbi.NewLimiter(RidPollLimit)
		return nil
	}
}

// String returns the string representation of the Rid.
func (r *Rid) String() string { return r.rid }

// TimeOfExecution returns the expected time until the request can be satisfied.
func (r *Rid) TimeOfExecution() time.Duration {
	now := time.Now()
	if now.Before(r.rtoe) {
		return r.rtoe.Sub(now)
	}
	return 0
}

// setElapsedDelay sets r's delay to have already elapsed.
func (r *Rid) setElapsedDelay() {
	delay := make(chan time.Time)
	close(delay)
	r.delay = delay
}

// Ready returns a time.Time chan that will send when the estimated time for the
// Put request to be satisfied has elapsed. If the request has failed the channel
// is returned closed.
func (r *Rid) Ready() <-chan time.Time {
	defer func() {
		delay := make(chan time.Time)
		close(delay)
		r.delay = delay
	}()
	return r.delay
}

// SearchInfo holds search status information.
type SearchInfo struct {
	*Rid
	Status   string
	HaveHits bool
}

func (s *SearchInfo) String() string {
	return fmt.Sprintf("%s Status:%s Hits:%v", s.Rid, s.Status, s.HaveHits)
}

func (s *SearchInfo) unmarshal(r io.Reader) error {
	z := html.NewTokenizer(r)
	for {
		tt := z.Next()
		if tt == html.ErrorToken {
			err := z.Err()
			if err == io.EOF {
				break
			}
			return err
		}

		// The following block pulls out the following element and makes the delay equal to X seconds.
		//
		//
This page will be automatically updated in X seconds

		//
		// If the integer parse fails it means NCBI have changed the html returned by SearchInfo so we
		// silently fall back to the policy wait time.
		if tt == html.StartTagToken {
			attr := z.Token().Attr
			if len(attr) > 0 && attr[0].Val == "WAITING" {
				for i := 0; i < 3; i++ {
					z.Next()
				}
				data := z.Token().Data
				rt, err := strconv.ParseInt(data, 10, 64)
				if err != nil {
					continue
				}
				secs := time.Duration(rt) * time.Second
				s.Rid.delay = time.After(secs)
				s.Rid.rtoe = time.Now().Add(secs)
				continue
			}
		}

		if tt == html.CommentToken {
			d := z.Token().Data
			if strings.Contains(d, "QBlastInfoBegin") {
				for _, l := range strings.Split(d, "\n") {
					l = strings.TrimSpace(l)
					kv := strings.Split(l, "=")
					if len(kv) != 2 {
						continue
					}
					switch kv[0] {
					case "Status":
						s.Status = kv[1]
					case "ThereAreHits":
						s.HaveHits = true
					}
				}
			}
		}
	}

	if s.Status == "" {
		return ErrMissingStatus
	}
	return nil
}
<commit_msg>blast: make SearchInfo String method safe for nil Rid<commit_after>// Copyright ©2013 The bíogo Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package blast

import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"

	"github.com/biogo/ncbi"

	"golang.org/x/net/html"
)

// Blast server usage policy requires that users not poll for any single RID more often than once
// a minute. The blast package Get requests honour this policy.
const RidPollLimit = 60 * time.Second

// Rid implements RID recovery and waiting functions associated with Blast Put and Get requests.
type Rid struct {
	rid   string
	rtoe  time.Time
	delay <-chan time.Time
	limit *ncbi.Limiter
}

// NewRid returns a Rid with the given request ID string. The returned Rid has a
// zero RTOE but is subject to the usage policy poll limiter. It is intended to
// be used to retrieve results from queries submitted without a call to Put.
func NewRid(rid string) *Rid {
	delay := make(chan time.Time)
	close(delay)
	return &Rid{
		rid:   rid,
		delay: delay,
		limit: ncbi.NewLimiter(RidPollLimit),
	}
}

var (
	messageIDBytes = []byte("Message ID")
	errorBytes     = []byte("Error:")
)

type ErrBadRequest string

func (e ErrBadRequest) Error() string { return string(e) }

func (rid *Rid) unmarshal(r io.Reader) error {
	z := html.NewTokenizer(r)
	for {
		tt := z.Next()
		if tt == html.ErrorToken {
			return z.Err()
		}
		if tt == html.TextToken {
			text := z.Text()
			if bytes.HasPrefix(text, messageIDBytes) && bytes.Contains(text, errorBytes) {
				rid.setElapsedDelay()
				return ErrBadRequest(text)
			}
		}
		if tt != html.CommentToken {
			continue
		}
		d := z.Token().Data
		if !strings.Contains(d, "QBlastInfoBegin") {
			continue
		}
		for _, l := range strings.Split(d, "\n") {
			l = strings.TrimSpace(l)
			kv := strings.Split(l, " = ")
			if len(kv) != 2 {
				continue
			}
			switch kv[0] {
			case "RID":
				rid.rid = kv[1]
			case "RTOE":
				rt, err := strconv.ParseInt(kv[1], 10, 64)
				if err != nil {
					return err
				}
				secs := time.Duration(rt) * time.Second
				rid.delay = time.After(secs)
				rid.rtoe = time.Now().Add(secs)
			}
		}
		if rid.rid == "" || rid.delay == nil {
			rid.setElapsedDelay()
			return ErrMissingRid
		}
		rid.limit = ncbi.NewLimiter(RidPollLimit)
		return nil
	}
}

// String returns the string representation of the Rid.
func (r *Rid) String() string {
	if r == nil {
		return "<nil>"
	}
	return r.rid
}

// TimeOfExecution returns the expected time until the request can be satisfied.
func (r *Rid) TimeOfExecution() time.Duration {
	now := time.Now()
	if now.Before(r.rtoe) {
		return r.rtoe.Sub(now)
	}
	return 0
}

// setElapsedDelay sets r's delay to have already elapsed.
func (r *Rid) setElapsedDelay() {
	delay := make(chan time.Time)
	close(delay)
	r.delay = delay
}

// Ready returns a time.Time chan that will send when the estimated time for the
// Put request to be satisfied has elapsed. If the request has failed the channel
// is returned closed.
func (r *Rid) Ready() <-chan time.Time {
	defer func() {
		delay := make(chan time.Time)
		close(delay)
		r.delay = delay
	}()
	return r.delay
}

// SearchInfo holds search status information.
type SearchInfo struct {
	*Rid
	Status   string
	HaveHits bool
}

func (s *SearchInfo) String() string {
	return fmt.Sprintf("RID:%s Status:%s Hits:%v", s.Rid, s.Status, s.HaveHits)
}

func (s *SearchInfo) unmarshal(r io.Reader) error {
	z := html.NewTokenizer(r)
	for {
		tt := z.Next()
		if tt == html.ErrorToken {
			err := z.Err()
			if err == io.EOF {
				break
			}
			return err
		}

		// The following block pulls out the following element and makes the delay equal to X seconds.
		//
		//
This page will be automatically updated in X seconds

		//
		// If the integer parse fails it means NCBI have changed the html returned by SearchInfo so we
		// silently fall back to the policy wait time.
		if tt == html.StartTagToken {
			attr := z.Token().Attr
			if len(attr) > 0 && attr[0].Val == "WAITING" {
				for i := 0; i < 3; i++ {
					z.Next()
				}
				data := z.Token().Data
				rt, err := strconv.ParseInt(data, 10, 64)
				if err != nil {
					continue
				}
				secs := time.Duration(rt) * time.Second
				s.Rid.delay = time.After(secs)
				s.Rid.rtoe = time.Now().Add(secs)
				continue
			}
		}

		if tt == html.CommentToken {
			d := z.Token().Data
			if strings.Contains(d, "QBlastInfoBegin") {
				for _, l := range strings.Split(d, "\n") {
					l = strings.TrimSpace(l)
					kv := strings.Split(l, "=")
					if len(kv) != 2 {
						continue
					}
					switch kv[0] {
					case "Status":
						s.Status = kv[1]
					case "ThereAreHits":
						s.HaveHits = true
					}
				}
			}
		}
	}

	if s.Status == "" {
		return ErrMissingStatus
	}
	return nil
}
<|endoftext|>


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

package blob

import (
	"bufio"
	"io"
	"io/ioutil"
	"logger"
	"os"
	"path"
	"strings"
)

type Manager struct {
	blobPath string
}

func New(blobPath string) *Manager {
	return &Manager{blobPath: blobPath}
}

func (f *Manager) Save(id string, checksum string, rc io.ReadCloser) error {
	f.cleanup(id, checksum)
	if err := os.MkdirAll(f.getBlobDir(id), 0750); err != nil {
		return err
	}
	file, err := os.OpenFile(f.getBlobPath(id, checksum), os.O_CREATE|os.O_RDWR, 0750)
	if file == nil && err != nil {
		return err
	}
	defer file.Close()

	reader := bufio.NewReader(rc)
	writer := bufio.NewWriter(file)
	p := make([]byte, 4096)
	for {
		n, err := reader.Read(p)
		if err == io.EOF {
			break
		}
		_, err = writer.Write(p[:n])
		if err != nil {
			return err
		}
	}
	return nil
}

func (f *Manager) Read(id string, checksum string, seek int64, l int) (blob []byte, size int64, err error) {
	var file *os.File
	file, err = os.Open(f.getBlobPath(id, checksum))
	if err != nil {
		return
	}
	blob = make([]byte, l)
	file.Seek(seek, 0)
	var s int
	s, err = file.Read(blob)
	return blob, int64(s), err
}

func (f *Manager) cleanup(id string, checksum string) (err error) {
	var blobs []os.FileInfo
	if blobs, err = ioutil.ReadDir(f.getBlobDir(id)); err != nil {
		return
	}
	for _, file := range blobs {
		if file.Name() != f.getBlobName(id, checksum) && strings.Contains(file.Name(), f.getBlobName(id, "")) {
			logger.V("Deleting", file.Name())
			// errors are not show stoppers here, they will cost additional disk space
			// we can get rid of on the next removal try.
			if rmErr := os.Remove(path.Join(f.getBlobDir(id), file.Name())); rmErr != nil {
				logger.V(rmErr)
			}
		}
	}
	return nil
}

func (f *Manager) getBlobDir(id string) string {
	l := len(id)
	return path.Join(f.blobPath, id[l-6:l-3], id[l-3:l])
}

func (f *Manager) getBlobName(id string, checksum string) string {
	return id + "==" + checksum
}

func (f *Manager) getBlobPath(id string, checksum string) string {
	return path.Join(f.getBlobDir(id), f.getBlobName(id, checksum))
}
<commit_msg>Single level sharing with two digits.<commit_after>// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package blob

import (
	"bufio"
	"io"
	"io/ioutil"
	"logger"
	"os"
	"path"
	"strings"
)

type Manager struct {
	blobPath string
}

func New(blobPath string) *Manager {
	return &Manager{blobPath: blobPath}
}

func (f *Manager) Save(id string, checksum string, rc io.ReadCloser) error {
	f.cleanup(id, checksum)
	if err := os.MkdirAll(f.getBlobDir(id), 0750); err != nil {
		return err
	}
	file, err := os.OpenFile(f.getBlobPath(id, checksum), os.O_CREATE|os.O_RDWR, 0750)
	if file == nil && err != nil {
		return err
	}
	defer file.Close()

	reader := bufio.NewReader(rc)
	writer := bufio.NewWriter(file)
	p := make([]byte, 4096)
	for {
		n, err := reader.Read(p)
		if err == io.EOF {
			break
		}
		_, err = writer.Write(p[:n])
		if err != nil {
			return err
		}
	}
	return nil
}

func (f *Manager) Read(id string, checksum string, seek int64, l int) (blob []byte, size int64, err error) {
	var file *os.File
	file, err = os.Open(f.getBlobPath(id, checksum))
	if err != nil {
		return
	}
	blob = make([]byte, l)
	file.Seek(seek, 0)
	var s int
	s, err = file.Read(blob)
	return blob, int64(s), err
}

func (f *Manager) cleanup(id string, checksum string) (err error) {
	var blobs []os.FileInfo
	if blobs, err = ioutil.ReadDir(f.getBlobDir(id)); err != nil {
		return
	}
	for _, file := range blobs {
		if file.Name() != f.getBlobName(id, checksum) && strings.Contains(file.Name(), f.getBlobName(id, "")) {
			logger.V("Deleting", file.Name())
			// errors are not show stoppers here, they will cost additional disk space
			// we can get rid of on the next removal try.
			if rmErr := os.Remove(path.Join(f.getBlobDir(id), file.Name())); rmErr != nil {
				logger.V(rmErr)
			}
		}
	}
	return nil
}

func (f *Manager) getBlobDir(id string) string {
	l := len(id)
	return path.Join(f.blobPath, id[l-2:l])
}

func (f *Manager) getBlobName(id string, checksum string) string {
	return id + "==" + checksum
}

func (f *Manager) getBlobPath(id string, checksum string) string {
	return path.Join(f.getBlobDir(id), f.getBlobName(id, checksum))
}
<|endoftext|>


<commit_before>package bot

import (
	"fmt"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/demisto/alfred/conf"
	"github.com/demisto/alfred/domain"
	"github.com/demisto/alfred/repo"
	"github.com/demisto/slack"
)

const (
	poweredBy          = "\t-\tPowered by <http://slack.demisto.com|Demisto>"
	botName            = "DBot"
	reactionTooBig     = "warning"
	reactionGood       = "+1"
	reactionBad        = "imp"
	fileCommentGood    = "File (%s) is clean. Click %s for more details."
	fileCommentBig     = "File (%s) is too large to scan. Click %s for more details."
	fileCommentBad     = "Warning: File (%s) is malicious. Click %s for more details."
	fileCommentWarning = "Unable to find details regarding this file (%s). Click %s for more details."
	urlCommentGood     = "URL (%s) is clean: %s."
	urlCommentBad      = "Warning: URL (%s) is malicious: %s."
	urlCommentWarning  = "Unable to find details regarding this URL (%s): %s."
	ipCommentGood      = "IP (%s) is clean: %s."
	ipCommentBad       = "Warning: IP (%s) is malicious: %s."
	ipCommentWarning   = "Unable to find details regarding this IP (%s): %s."
	md5CommentGood     = "MD5 hash (%s) is clean: %s."
	md5CommentBad      = "Warning: MD5 hash (%s) is malicious: %s."
	md5CommentWarning  = "Unable to find details regarding this MD5 hash (%s): %s."
	mainMessage        = "Security check by DBot - Demisto Bot. Click <%s|here> for configuration and details."
	firstMessage       = "<@%s|%s> has added <%s|DBot> by Demisto to monitor this channel."
)

func joinMap(m map[string]bool) string {
	res := ""
	for k, v := range m {
		if v {
			res += k + ","
		}
	}
	if len(res) > 0 {
		return res[0 : len(res)-1]
	}
	return res
}

func joinMapInt(m map[string]int) string {
	res := ""
	for k, v := range m {
		res += fmt.Sprintf("%s (%d),", k, v)
	}
	if len(res) > 0 {
		return res[0 : len(res)-1]
	}
	return res
}

func mainMessageFormatted() string {
	return fmt.Sprintf(mainMessage, conf.Options.ExternalAddress)
}

func (b *Bot) handleFileReply(reply *domain.WorkReply, data *domain.Context) {
	link := fmt.Sprintf("%s/details?f=%s&u=%s", conf.Options.ExternalAddress, reply.File.Details.ID, data.User)
	if reply.File.FileTooLarge {
		err := b.fileComment(fmt.Sprintf(fileCommentBig, reply.File.Details.Name, link), reactionTooBig, reply)
		if err != nil {
			logrus.Warnf("Error commenting on file - %v\n", err)
		}
		return
	}
	color := "warning"
	comment := fileCommentWarning
	if reply.File.Result == domain.ResultDirty {
		color = "danger"
		comment = fileCommentBad
	} else if reply.File.Result == domain.ResultClean {
		// At least one of reputation services found this to be known good
		// Keep the default
		color = "good"
		comment = fileCommentGood
	}
	if data.Channel != "" {
		fileMessage := fmt.Sprintf(comment, reply.File.Details.Name, fmt.Sprintf("<%s|Details>", link))
		postMessage := &slack.PostMessageRequest{
			Channel:  data.Channel,
			Text:     mainMessageFormatted(),
			Username: botName,
			Attachments: []slack.Attachment{
				{
					Fallback: fileMessage,
					Text:     fileMessage,
					Color:    color,
				},
			},
		}
		err := b.post(postMessage, reply, data)
		if err != nil {
			logrus.Errorf("Unable to send message to Slack - %v\n", err)
			return
		}
	}
}

func (b *Bot) handleReplyStats(reply *domain.WorkReply, ctx *domain.Context) {
	b.smu.Lock()
	defer b.smu.Unlock()
	stats := b.stats[ctx.Team]
	if stats == nil {
		stats = &domain.Statistics{Team: ctx.Team}
		b.stats[ctx.Team] = stats
	}
	stats.Messages++
	if reply.Type&domain.ReplyTypeFile > 0 {
		if reply.File.Result == domain.ResultClean {
			stats.FilesClean++
		} else if reply.File.Result == domain.ResultDirty {
			stats.FilesDirty++
		} else {
			stats.FilesUnknown++
		}
	} else {
		if reply.Type&domain.ReplyTypeMD5 > 0 {
			if reply.MD5.Result == domain.ResultClean {
				stats.HashesClean++
			} else if reply.MD5.Result == domain.ResultDirty {
				stats.HashesDirty++
			} else {
				stats.HashesUnknown++
			}
		}
		if reply.Type&domain.ReplyTypeURL > 0 {
			if reply.URL.Result == domain.ResultClean {
				stats.URLsClean++
			} else if reply.URL.Result == domain.ResultDirty {
				stats.URLsDirty++
			} else {
				stats.URLsUnknown++
			}
		}
		if reply.Type&domain.ReplyTypeIP > 0 {
			if reply.IP.Result == domain.ResultClean {
				stats.IPsClean++
			} else if reply.IP.Result == domain.ResultDirty {
				stats.IPsDirty++
			} else {
				stats.IPsUnknown++
			}
		}
	}
}

func (b *Bot) handleReply(reply *domain.WorkReply) {
	logrus.Debugf("Handling reply - %+v\n", reply)
	logrus.Debugf("Context- %v\n", reply.Context)
	data, err := GetContext(reply.Context)
	if err != nil {
		logrus.Warnf("Error getting context from reply - %+v\n", reply)
		return
	}
	b.handleReplyStats(reply, data)
	if reply.Type&domain.ReplyTypeFile > 0 {
		b.handleFileReply(reply, data)
	} else {
		link := fmt.Sprintf("%s/details?c=%s&m=%s&u=%s", conf.Options.ExternalAddress, data.Channel, reply.MessageID, data.User)
		postMessage := &slack.PostMessageRequest{
			Channel:  data.Channel,
			Text:     mainMessageFormatted(),
			Username: botName,
		}
		if reply.Type&domain.ReplyTypeURL > 0 {
			color := "warning"
			comment := urlCommentWarning
			if reply.URL.Result == domain.ResultDirty {
				color = "danger"
				comment = urlCommentBad
			} else if reply.URL.Result == domain.ResultClean {
				color = "good"
				comment = urlCommentGood
			}
			urlMessage := fmt.Sprintf(comment, reply.URL.Details, fmt.Sprintf("<%s|Details>", link))
			postMessage.Attachments = append(postMessage.Attachments, slack.Attachment{
				Fallback: urlMessage,
				Text:     urlMessage,
				Color:    color,
			})
		}
		if reply.Type&domain.ReplyTypeIP > 0 {
			color := "warning"
			comment := ipCommentWarning
			if reply.IP.Result == domain.ResultDirty {
				color = "danger"
				comment = ipCommentBad
			} else if reply.IP.Result == domain.ResultClean {
				color = "good"
				comment = ipCommentGood
			}
			ipMessage := fmt.Sprintf(comment, reply.IP.Details, fmt.Sprintf("<%s|Details>", link))
			postMessage.Attachments = append(postMessage.Attachments, slack.Attachment{
				Fallback: ipMessage,
				Text:     ipMessage,
				Color:    color,
			})
		}
		if reply.Type&domain.ReplyTypeMD5 > 0 {
			color := "warning"
			comment := md5CommentWarning
			if reply.MD5.Result == domain.ResultDirty {
				color = "danger"
				comment = md5CommentBad
			} else if reply.MD5.Result == domain.ResultClean {
				color = "good"
				comment = md5CommentGood
			}
			md5Message := fmt.Sprintf(comment, reply.MD5.Details, fmt.Sprintf("<%s|Details>", link))
			postMessage.Attachments = append(postMessage.Attachments, slack.Attachment{
				Fallback: md5Message,
				Text:     md5Message,
				Color:    color,
			})
		}
		err = b.post(postMessage, reply, data)
		if err != nil {
			logrus.Errorf("Unable to send message to Slack - %v\n", err)
			return
		}
	}
}

func (b *Bot) maybeSendFirstMessage(u *domain.User, s *slack.Slack, data *domain.Context) error {
	if data.Channel != "" && !strings.HasPrefix(data.Channel, "D") {
		if b.firstMessages[data.Channel+"@"+data.Team] {
			return nil
		}
		sent, err := b.r.WasMessageSentOnChannel(data.Team, data.Channel)
		if err != nil {
			logrus.Infof("Error reading first message info - %v", err)
			return err
		}
		if !sent {
			// If there is an error here, it might happen because someone did this in parallel
			err = b.r.MessageSentOnChannel(data.Team, data.Channel)
			if err != nil {
				return nil
			}
			b.firstMessages[data.Channel+"@"+data.Team] = true
			postMessage := &slack.PostMessageRequest{
				Channel:  data.Channel,
				Text:     fmt.Sprintf(firstMessage, u.ExternalID, u.Name, conf.Options.ExternalAddress),
				Username: botName,
			}
			_, err = s.PostMessage(postMessage, false)
			if err != nil {
				logrus.Infof("Unable to send first message to Slack - %v\n", err)
				return err
			}
		}
	}
	return nil
}

// post uses the correct client to post to the channel
// See if the original message poster is subscribed and if so use him.
// If not, use the first user we have that is subscribed to the channel.
func (b *Bot) post(message *slack.PostMessageRequest, reply *domain.WorkReply, data *domain.Context) error {
	u, err := b.r.UserByExternalID(data.OriginalUser)
	if err != nil && err != repo.ErrNotFound {
		return err
	}
	if err != nil {
		u, err = b.r.User(data.User)
		if err != nil {
			return err
		}
	}
	s, err := slack.New(slack.SetToken(u.Token))
	if err != nil {
		return err
	}
	err = b.maybeSendFirstMessage(u, s, data)
	if err != nil {
		return err
	}
	_, err = s.PostMessage(message, false)
	return err
}

func (b *Bot) fileComment(comment, reaction string, reply *domain.WorkReply) error {
	var u *domain.User
	data, err := GetContext(reply.Context)
	if err != nil {
		return err
	}
	// If the context did not have any channel, it is a file_created event
	if data.Channel == "" {
		u, err = b.r.User(data.User)
		if err != nil {
			return err
		}
	} else {
		var err error
		u, err = b.r.UserByExternalID(data.OriginalUser)
		if err != nil && err != repo.ErrNotFound {
			return err
		}
		// If the user creating the file is our user than don't comment because we already did the comment on file create event
		if err == nil {
			return nil
		}
		u, err = b.r.User(data.User)
		if err != nil {
			return err
		}
	}
	s, err := slack.New(slack.SetToken(u.Token))
	if err != nil {
		return err
	}
	info, err := s.FileInfo(reply.File.Details.ID, 0, 0)
	if err != nil {
		return err
	}
	for i := range info.Comments {
		if strings.HasPrefix(info.Comments[i].Comment, botName) {
			return nil
		}
	}
	// We got here - means we do not have comment
	_, err = s.FileAddComment(reply.File.Details.ID, comment, false)
	if err != nil {
		return err
	}
	_, err = s.ReactionsAdd(reaction, reply.File.Details.ID, "", "", "")
	return err
}
<commit_msg>Fixed #51<commit_after>package bot

import (
	"fmt"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/demisto/alfred/conf"
	"github.com/demisto/alfred/domain"
	"github.com/demisto/alfred/repo"
	"github.com/demisto/slack"
)

const (
	poweredBy          = "\t-\tPowered by <http://slack.demisto.com|Demisto>"
	botName            = "DBot"
	reactionTooBig     = "warning"
	reactionGood       = "+1"
	reactionBad        = "imp"
	fileCommentGood    = "File (%s) is clean. Click %s for more details."
	fileCommentBig     = "File (%s) is too large to scan. Click %s for more details."
	fileCommentBad     = "Warning: File (%s) is malicious. Click %s for more details."
	fileCommentWarning = "Unable to find details regarding this file (%s). Click %s for more details."
	urlCommentGood     = "URL (%s) is clean: %s."
	urlCommentBad      = "Warning: URL (%s) is malicious: %s."
	urlCommentWarning  = "Unable to find details regarding this URL (%s): %s."
	ipCommentGood      = "IP (%s) is clean: %s."
	ipCommentBad       = "Warning: IP (%s) is malicious: %s."
	ipCommentWarning   = "Unable to find details regarding this IP (%s): %s."
	md5CommentGood     = "MD5 hash (%s) is clean: %s."
	md5CommentBad      = "Warning: MD5 hash (%s) is malicious: %s."
	md5CommentWarning  = "Unable to find details regarding this MD5 hash (%s): %s."
	mainMessage        = "Security check by DBot - Demisto Bot. Click <%s|here> for configuration and details."
	firstMessage       = "<@%s|%s> has added <%s|DBot> by Demisto to monitor this channel."
)

func joinMap(m map[string]bool) string {
	res := ""
	for k, v := range m {
		if v {
			res += k + ","
		}
	}
	if len(res) > 0 {
		return res[0 : len(res)-1]
	}
	return res
}

func joinMapInt(m map[string]int) string {
	res := ""
	for k, v := range m {
		res += fmt.Sprintf("%s (%d),", k, v)
	}
	if len(res) > 0 {
		return res[0 : len(res)-1]
	}
	return res
}

func mainMessageFormatted() string {
	return fmt.Sprintf(mainMessage, conf.Options.ExternalAddress)
}

func (b *Bot) handleFileReply(reply *domain.WorkReply, data *domain.Context) {
	link := fmt.Sprintf("%s/details?f=%s&u=%s", conf.Options.ExternalAddress, reply.File.Details.ID, data.User)
	if reply.File.FileTooLarge {
		err := b.fileComment(fmt.Sprintf(fileCommentBig, reply.File.Details.Name, link), reactionTooBig, reply)
		if err != nil {
			logrus.Warnf("Error commenting on file - %v\n", err)
		}
		return
	}
	color := "warning"
	comment := fileCommentWarning
	if reply.File.Result == domain.ResultDirty {
		color = "danger"
		comment = fileCommentBad
	} else if reply.File.Result == domain.ResultClean {
		// At least one of reputation services found this to be known good
		// Keep the default
		color = "good"
		comment = fileCommentGood
	}
	if data.Channel != "" {
		fileMessage := fmt.Sprintf(comment, reply.File.Details.Name, fmt.Sprintf("<%s|Details>", link))
		postMessage := &slack.PostMessageRequest{
			Channel:  data.Channel,
			Text:     mainMessageFormatted(),
			Username: botName,
			Attachments: []slack.Attachment{
				{
					Fallback: fileMessage,
					Text:     fileMessage,
					Color:    color,
				},
			},
		}
		err := b.post(postMessage, reply, data)
		if err != nil {
			logrus.Errorf("Unable to send message to Slack - %v\n", err)
			return
		}
	}
}

func (b *Bot) handleReplyStats(reply *domain.WorkReply, ctx *domain.Context) {
	b.smu.Lock()
	defer b.smu.Unlock()
	stats := b.stats[ctx.Team]
	if stats == nil {
		stats = &domain.Statistics{Team: ctx.Team}
		b.stats[ctx.Team] = stats
	}
	stats.Messages++
	if reply.Type&domain.ReplyTypeFile > 0 {
		if reply.File.Result == domain.ResultClean {
			stats.FilesClean++
		} else if reply.File.Result == domain.ResultDirty {
			stats.FilesDirty++
		} else {
			stats.FilesUnknown++
		}
	} else {
		if reply.Type&domain.ReplyTypeMD5 > 0 {
			if reply.MD5.Result == domain.ResultClean {
				stats.HashesClean++
			} else if reply.MD5.Result == domain.ResultDirty {
				stats.HashesDirty++
			} else {
				stats.HashesUnknown++
			}
		}
		if reply.Type&domain.ReplyTypeURL > 0 {
			if reply.URL.Result == domain.ResultClean {
				stats.URLsClean++
			} else if reply.URL.Result == domain.ResultDirty {
				stats.URLsDirty++
			} else {
				stats.URLsUnknown++
			}
		}
		if reply.Type&domain.ReplyTypeIP > 0 {
			if reply.IP.Result == domain.ResultClean {
				stats.IPsClean++
			} else if reply.IP.Result == domain.ResultDirty {
				stats.IPsDirty++
			} else {
				stats.IPsUnknown++
			}
		}
	}
}

func (b *Bot) handleReply(reply *domain.WorkReply) {
	logrus.Debugf("Handling reply - %+v\n", reply)
	logrus.Debugf("Context- %v\n", reply.Context)
	data, err := GetContext(reply.Context)
	if err != nil {
		logrus.Warnf("Error getting context from reply - %+v\n", reply)
		return
	}
	b.handleReplyStats(reply, data)
	if reply.Type&domain.ReplyTypeFile > 0 {
		b.handleFileReply(reply, data)
	} else {
		link := fmt.Sprintf("%s/details?c=%s&m=%s&u=%s", conf.Options.ExternalAddress, data.Channel, reply.MessageID, data.User)
		postMessage := &slack.PostMessageRequest{
			Channel:  data.Channel,
			Text:     mainMessageFormatted(),
			Username: botName,
		}
		if reply.Type&domain.ReplyTypeURL > 0 {
			color := "warning"
			comment := urlCommentWarning
			if reply.URL.Result == domain.ResultDirty {
				color = "danger"
				comment = urlCommentBad
			} else if reply.URL.Result == domain.ResultClean {
				color = "good"
				comment = urlCommentGood
			}
			urlMessage := fmt.Sprintf(comment, reply.URL.Details, fmt.Sprintf("<%s|Details>", link))
			postMessage.Attachments = append(postMessage.Attachments, slack.Attachment{
				Fallback: urlMessage,
				Text:     urlMessage,
				Color:    color,
			})
		}
		if reply.Type&domain.ReplyTypeIP > 0 {
			color := "warning"
			comment := ipCommentWarning
			if reply.IP.Result == domain.ResultDirty {
				color = "danger"
				comment = ipCommentBad
			} else if reply.IP.Result == domain.ResultClean {
				color = "good"
				comment = ipCommentGood
			}
			ipMessage := fmt.Sprintf(comment, reply.IP.Details, fmt.Sprintf("<%s|Details>", link))
			postMessage.Attachments = append(postMessage.Attachments, slack.Attachment{
				Fallback: ipMessage,
				Text:     ipMessage,
				Color:    color,
			})
		}
		if reply.Type&domain.ReplyTypeMD5 > 0 {
			color := "warning"
			comment := md5CommentWarning
			if reply.MD5.Result == domain.ResultDirty {
				color = "danger"
				comment = md5CommentBad
			} else if reply.MD5.Result == domain.ResultClean {
				color = "good"
				comment = md5CommentGood
			}
			md5Message := fmt.Sprintf(comment, reply.MD5.Details, fmt.Sprintf("<%s|Details>", link))
			postMessage.Attachments = append(postMessage.Attachments, slack.Attachment{
				Fallback: md5Message,
				Text:     md5Message,
				Color:    color,
			})
		}
		err = b.post(postMessage, reply, data)
		if err != nil {
			logrus.Errorf("Unable to send message to Slack - %v\n", err)
			return
		}
	}
}

func (b *Bot) maybeSendFirstMessage(u *domain.User, s *slack.Slack, data *domain.Context) error {
	if data.Channel != "" && !strings.HasPrefix(data.Channel, "D") {
		if b.firstMessages[data.Channel+"@"+data.Team] {
			return nil
		}
		sent, err := b.r.WasMessageSentOnChannel(data.Team, data.Channel)
		if err != nil {
			logrus.Infof("Error reading first message info - %v", err)
			return err
		}
		if !sent {
			// If there is an error here, it might happen because someone did this in parallel
			err = b.r.MessageSentOnChannel(data.Team, data.Channel)
			if err != nil {
				return nil
			}
			b.firstMessages[data.Channel+"@"+data.Team] = true
			postMessage := &slack.PostMessageRequest{
				Channel:  data.Channel,
				Text:     fmt.Sprintf(firstMessage, u.ExternalID, u.Name, conf.Options.ExternalAddress),
				Username: botName,
			}
			_, err = s.PostMessage(postMessage, false)
			if err != nil {
				logrus.Infof("Unable to send first message to Slack - %v\n", err)
				return err
			}
		}
	}
	return nil
}

// post uses the correct client to post to the channel
// See if the original message poster is subscribed and if so use him.
// If not, use the first user we have that is subscribed to the channel.
func (b *Bot) post(message *slack.PostMessageRequest, reply *domain.WorkReply, data *domain.Context) error {
	u, err := b.r.UserByExternalID(data.OriginalUser)
	if err != nil && err != repo.ErrNotFound {
		return err
	}
	if err != nil {
		u, err = b.r.User(data.User)
		if err != nil {
			return err
		}
	}
	message.IconURL = conf.Options.ExternalAddress + "/img/favicon/D icon 5757.png"
	s, err := slack.New(slack.SetToken(u.Token))
	if err != nil {
		return err
	}
	err = b.maybeSendFirstMessage(u, s, data)
	if err != nil {
		return err
	}
	_, err = s.PostMessage(message, false)
	return err
}

func (b *Bot) fileComment(comment, reaction string, reply *domain.WorkReply) error {
	var u *domain.User
	data, err := GetContext(reply.Context)
	if err != nil {
		return err
	}
	// If the context did not have any channel, it is a file_created event
	if data.Channel == "" {
		u, err = b.r.User(data.User)
		if err != nil {
			return err
		}
	} else {
		var err error
		u, err = b.r.UserByExternalID(data.OriginalUser)
		if err != nil && err != repo.ErrNotFound {
			return err
		}
		// If the user creating the file is our user than don't comment because we already did the comment on file create event
		if err == nil {
			return nil
		}
		u, err = b.r.User(data.User)
		if err != nil {
			return err
		}
	}
	s, err := slack.New(slack.SetToken(u.Token))
	if err != nil {
		return err
	}
	info, err := s.FileInfo(reply.File.Details.ID, 0, 0)
	if err != nil {
		return err
	}
	for i := range info.Comments {
		if strings.HasPrefix(info.Comments[i].Comment, botName) {
			return nil
		}
	}
	// We got here - means we do not have comment
	_, err = s.FileAddComment(reply.File.Details.ID, comment, false)
	if err != nil {
		return err
	}
	_, err = s.ReactionsAdd(reaction, reply.File.Details.ID, "", "", "")
	return err
}
<|endoftext|>


<commit_before>package bots

var bots = map[string]bool{
	"bitlybot":            true,
	"Googlebot-Image/1.0": true,
	"LinkedInBot/1.0 (compatible; Mozilla/5.0; Jakarta Commons-HttpClient/3.1 +http://www.linkedin.com)":                                                                                true,
	"LivelapBot/0.2 (http://site.livelap.com/crawler)":                                                                                true,
	"Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)":                                                                                true,
	"Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)":                                                                                true,
	"Mozilla/5.0 (compatible; Exabot/3.0; +http://www.exabot.com/go/robot)":                                                                                true,
	"Mozilla/5.0 (compatible; Findxbot/1.0; +http://www.findxbot.com)":                                                                                true,
	"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)":                                                                                true,
	"Mozilla/5.0 (compatible; Kraken/0.1; http://linkfluence.net/; bot@linkfluence.net)":                                                                                true,
	"Mozilla/5.0 (compatible; Linux x86_64; Mail.RU_Bot/2.0; +http://go.mail.ru/help/robots)":                                                                                true,
	"Mozilla/5.0 (compatible; MojeekBot/0.6; +https://www.mojeek.com/bot.html)":                                                                                true,
	"Mozilla/5.0 (compatible; OpenHoseBot/2.1; +http://www.openhose.org/bot.html)":                                                                                true,
	"Mozilla/5.0 (compatible; PaperLiBot/2.1; http://support.paper.li/entries/20023257-what-is-paper-li)":                                                                                true,
	"Mozilla/5.0 (compatible; redditbot/1.0; +http://www.reddit.com/feedback)":                                                                                true,
	"Mozilla/5.0 (compatible; SeznamBot/3.2; +http://fulltext.sblog.cz/)":                                                                                true,
	"Mozilla/5.0 (compatible; uMBot-LN/1.0; mailto: crawling@ubermetrics-technologies.com)":                                                                                true,
	"Mozilla/5.0+(compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)":                                                                                true,
	"Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)":                                                                                true,
	"Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)": true,
	"TelegramBot":     true,
	"Traackr.com Bot": true,
	"Twitterbot/1.0":  true,
}

func IsBot(ua string) bool {
	if _, ok := bots[ua]; ok {
		return true
	}
	return false
}
<commit_msg>Add more crawlers, bots, and voltron to bots map<commit_after>package bots

var bots = map[string]bool{
	"bitlybot":                   true,
	"crawlernutchtest/Nutch-1.9": true,
	"ExactSeekCrawler/1.0":       true,
	"Googlebot-Image/1.0":        true,
	"LinkedInBot/1.0 (compatible; Mozilla/5.0; Jakarta Commons-HttpClient/3.1 +http://www.linkedin.com)":                                                                                true,
	"LivelapBot/0.2 (http://site.livelap.com/crawler)":                                                                                true,
	"Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)":                                                                                true,
	"Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)":                                                                                true,
	"Mozilla/5.0 (compatible; Exabot/3.0; +http://www.exabot.com/go/robot)":                                                                                true,
	"Mozilla/5.0 (compatible; Findxbot/1.0; +http://www.findxbot.com)":                                                                                true,
	"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)":                                                                                true,
	"Mozilla/5.0 (compatible; Kraken/0.1; http://linkfluence.net/; bot@linkfluence.net)":                                                                                true,
	"Mozilla/5.0 (compatible; Linux x86_64; Mail.RU_Bot/2.0; +http://go.mail.ru/help/robots)":                                                                                true,
	"Mozilla/5.0 (compatible; MojeekBot/0.6; +https://www.mojeek.com/bot.html)":                                                                                true,
	"Mozilla/5.0 (compatible; OpenHoseBot/2.1; +http://www.openhose.org/bot.html)":                                                                                true,
	"Mozilla/5.0 (compatible; PaperLiBot/2.1; http://support.paper.li/entries/20023257-what-is-paper-li)":                                                                                true,
	"Mozilla/5.0 (compatible; redditbot/1.0; +http://www.reddit.com/feedback)":                                                                                true,
	"Mozilla/5.0 (compatible; SeznamBot/3.2; +http://fulltext.sblog.cz/)":                                                                                true,
	"Mozilla/5.0 (compatible; uMBot-LN/1.0; mailto: crawling@ubermetrics-technologies.com)":                                                                                true,
	"Mozilla/5.0+(compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)":                                                                                true,
	"Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)":                                                                                true,
	"Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)": true,
	"spiderbot":       true,
	"TelegramBot":     true,
	"Traackr.com Bot": true,
	"Twitterbot/1.0":  true,
	"voltron":         true,
}

func IsBot(ua string) bool {
	if _, ok := bots[ua]; ok {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package byteutil

import (
	"bytes"
	// "fmt"
)

// ReverseByteSlice reverses a byte slice
func ReverseByteSlice(s []byte) []byte {
	// make a copy of s
	l := len(s)
	t := make([]byte, l)
	for i := 0; i < l; i++ {
		t[i] = s[i]
	}

	// reverse
	for i, j := 0, l-1; i < j; i, j = i+1, j-1 {
		t[i], t[j] = t[j], t[i]
	}
	return t
}

// ReverseByteSliceInplace reverses a byte slice
func ReverseByteSliceInplace(s []byte) {
	// reverse
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
}

// WrapByteSlice wraps byte slice
func WrapByteSlice(s []byte, width int) []byte {
	if width < 1 {
		return s
	}
	var buffer bytes.Buffer
	l := len(s)
	var lines int
	if l%width == 0 {
		lines = l/width - 1
	} else {
		lines = int(l / width)
	}
	var start, end int
	for i := 0; i <= lines; i++ {
		start = i * width
		end = (i + 1) * width
		if end > l {
			end = l
		}

		buffer.Write(s[start:end])
		if i < lines {
			buffer.WriteString("\n")
		}
	}
	return buffer.Bytes()
}

// WrapByteSliceInplace wraps byte slice in place.
// Sadly, it's too slow. Never use this!
func WrapByteSliceInplace(s []byte, width int) []byte {
	if width < 1 {
		return s
	}
	var l, lines int

	l = len(s)
	if l%width == 0 {
		lines = l/width - 1
	} else {
		lines = int(l / width)
	}

	var end int
	j := 0
	for i := 0; i <= lines; i++ {
		end = (i+1)*width + j
		if end >= l {
			break
		}
		// fmt.Printf("len:%d, lines:%d, i:%d, j:%d, end:%d\n", l, lines, i, j, end)
		if i < lines {

			// https://github.com/golang/go/wiki/SliceTricks
			// Sadly, it's too slow
			// s = append(s, []byte(" ")[0])
			// copy(s[end+1:], s[end:])
			// s[end] = []byte("\n")[0]

			// slow too
			s = append(s[:end], append([]byte("\n"), s[end:]...)...)

			l = len(s)
			if l%width == 0 {
				lines = l/width - 1
			} else {
				lines = int(l / width)
			}

			j++
		}
	}
	return s
}

// SubSlice provides similar slice indexing as python with one exception
// that end could be equal to 0.
// So we could get the last element by SubSlice(s, -1, 0)
// or get the whole element by SubSlice(s, 0, 0)
func SubSlice(slice []byte, start int, end int) []byte {
	if start == 0 && end == 0 {
		return slice
	}
	if start == end || (start < 0 && end > 0) {
		return []byte{}
	}
	l := len(slice)
	s, e := start, end

	if s < 0 {
		s = l + s
		if s < 1 {
			s = 0
		}
	}
	if e < 0 {
		e = l + e
		if e < 0 {
			e = 0
		}
	}
	if e == 0 || e > l {
		e = l
	}
	return slice[s:e]
}

// ByteToLower lowers a byte
func ByteToLower(b byte) byte {
	if b <= '\u007F' {
		if 'A' <= b && b <= 'Z' {
			b += 'a' - 'A'
		}
		return b
	}
	return b
}

// ByteToUpper upper a byte
func ByteToUpper(b byte) byte {
	if b <= '\u007F' {
		if 'a' <= b && b <= 'z' {
			b -= 'a' - 'A'
		}
		return b
	}
	return b
}

// MakeQuerySlice is used to replace map.
// see: http://blog.shenwei.me/map-is-not-the-fastest-in-go/
func MakeQuerySlice(letters []byte) []byte {
	max := -1
	for i := 0; i < len(letters); i++ {
		j := int(letters[i])
		if max < j {
			max = j
		}
	}
	querySlice := make([]byte, max+1)
	for i := 0; i < len(letters); i++ {
		querySlice[int(letters[i])] = letters[i]
	}
	return querySlice
}

// Split splits a byte slice by giveen letters.
// It's much faster than regexp.Split
func Split(slice []byte, letters []byte) [][]byte {
	querySlice := MakeQuerySlice(letters)
	results := [][]byte{}
	tmp := []byte{}

	var j int
	var value byte
	var sliceSize = len(querySlice)
	for _, b := range slice {
		j = int(b)
		if j >= sliceSize { // not delimiter byte
			tmp = append(tmp, b)
			continue
		}
		value = querySlice[j]
		if value == 0 { // not delimiter byte
			tmp = append(tmp, b)
			continue
		} else {
			if len(tmp) > 0 {
				results = append(results, tmp)
				tmp = []byte{}
			}
		}
	}
	if len(tmp) > 0 {
		results = append(results, tmp)
	}
	return results
}
<commit_msg>improve byteutil.WrapByteSlice<commit_after>package byteutil

import (
	"bytes"
	// "fmt"
)

// ReverseByteSlice reverses a byte slice
func ReverseByteSlice(s []byte) []byte {
	// make a copy of s
	l := len(s)
	t := make([]byte, l)
	for i := 0; i < l; i++ {
		t[i] = s[i]
	}

	// reverse
	for i, j := 0, l-1; i < j; i, j = i+1, j-1 {
		t[i], t[j] = t[j], t[i]
	}
	return t
}

// ReverseByteSliceInplace reverses a byte slice
func ReverseByteSliceInplace(s []byte) {
	// reverse
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
}

// WrapByteSlice wraps byte slice
func WrapByteSlice(s []byte, width int) []byte {
	if width < 1 {
		return s
	}
	l := len(s)
	var lines int
	if l%width == 0 {
		lines = l/width - 1
	} else {
		lines = int(l / width)
	}
	// var buffer bytes.Buffer
	buffer := bytes.NewBuffer(make([]byte, 0, l+lines))
	var start, end int
	for i := 0; i <= lines; i++ {
		start = i * width
		end = (i + 1) * width
		if end > l {
			end = l
		}

		buffer.Write(s[start:end])
		if i < lines {
			buffer.WriteString("\n")
		}
	}
	return buffer.Bytes()
}

// WrapByteSliceInplace wraps byte slice in place.
// Sadly, it's too slow. Never use this!
func WrapByteSliceInplace(s []byte, width int) []byte {
	if width < 1 {
		return s
	}
	var l, lines int

	l = len(s)
	if l%width == 0 {
		lines = l/width - 1
	} else {
		lines = int(l / width)
	}

	var end int
	j := 0
	for i := 0; i <= lines; i++ {
		end = (i+1)*width + j
		if end >= l {
			break
		}
		// fmt.Printf("len:%d, lines:%d, i:%d, j:%d, end:%d\n", l, lines, i, j, end)
		if i < lines {

			// https://github.com/golang/go/wiki/SliceTricks
			// Sadly, it's too slow
			// s = append(s, []byte(" ")[0])
			// copy(s[end+1:], s[end:])
			// s[end] = []byte("\n")[0]

			// slow too
			s = append(s[:end], append([]byte("\n"), s[end:]...)...)

			l = len(s)
			if l%width == 0 {
				lines = l/width - 1
			} else {
				lines = int(l / width)
			}

			j++
		}
	}
	return s
}

// SubSlice provides similar slice indexing as python with one exception
// that end could be equal to 0.
// So we could get the last element by SubSlice(s, -1, 0)
// or get the whole element by SubSlice(s, 0, 0)
func SubSlice(slice []byte, start int, end int) []byte {
	if start == 0 && end == 0 {
		return slice
	}
	if start == end || (start < 0 && end > 0) {
		return []byte{}
	}
	l := len(slice)
	s, e := start, end

	if s < 0 {
		s = l + s
		if s < 1 {
			s = 0
		}
	}
	if e < 0 {
		e = l + e
		if e < 0 {
			e = 0
		}
	}
	if e == 0 || e > l {
		e = l
	}
	return slice[s:e]
}

// ByteToLower lowers a byte
func ByteToLower(b byte) byte {
	if b <= '\u007F' {
		if 'A' <= b && b <= 'Z' {
			b += 'a' - 'A'
		}
		return b
	}
	return b
}

// ByteToUpper upper a byte
func ByteToUpper(b byte) byte {
	if b <= '\u007F' {
		if 'a' <= b && b <= 'z' {
			b -= 'a' - 'A'
		}
		return b
	}
	return b
}

// MakeQuerySlice is used to replace map.
// see: http://blog.shenwei.me/map-is-not-the-fastest-in-go/
func MakeQuerySlice(letters []byte) []byte {
	max := -1
	for i := 0; i < len(letters); i++ {
		j := int(letters[i])
		if max < j {
			max = j
		}
	}
	querySlice := make([]byte, max+1)
	for i := 0; i < len(letters); i++ {
		querySlice[int(letters[i])] = letters[i]
	}
	return querySlice
}

// Split splits a byte slice by giveen letters.
// It's much faster than regexp.Split
func Split(slice []byte, letters []byte) [][]byte {
	querySlice := MakeQuerySlice(letters)
	results := [][]byte{}
	tmp := []byte{}

	var j int
	var value byte
	var sliceSize = len(querySlice)
	for _, b := range slice {
		j = int(b)
		if j >= sliceSize { // not delimiter byte
			tmp = append(tmp, b)
			continue
		}
		value = querySlice[j]
		if value == 0 { // not delimiter byte
			tmp = append(tmp, b)
			continue
		} else {
			if len(tmp) > 0 {
				results = append(results, tmp)
				tmp = []byte{}
			}
		}
	}
	if len(tmp) > 0 {
		results = append(results, tmp)
	}
	return results
}
<|endoftext|>


<commit_before>package cache

import(
	"time"

	"github.com/drone/drone-cache-lib/storage"

	log "github.com/Sirupsen/logrus"
)

type dirtyFunc func(storage.FileEntry) bool

type Flusher struct {
	store storage.Storage
	dirty func(storage.FileEntry) bool
}

func NewFlusher(s storage.Storage, fn dirtyFunc) Flusher {
	return Flusher{ store: s, dirty: fn }
}

func NewDefaultFlusher(s storage.Storage) Flusher {
	return Flusher{ store: s, dirty: TimeOp() }
}

func (f *Flusher) Flush(src string) error {
	log.Infof("Cleaning files from %s", src)

	files, err := f.store.List(src)
	if err != nil {
		return err
	}

	for _, file := range files {
		if f.dirty(file) {
			err := f.store.Delete(file.Path)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func TimeOp() dirtyFunc {
	return func(file storage.FileEntry) bool {
		// Do not delete directories
		if file.Info.IsDir() {
			return false
		}

		// Check if older then 30 days
		if file.Info.ModTime().Before(time.Now().AddDate(0, 0, -30)) {
			return true
		}

		// No match
		return false
	}
}
<commit_msg>Assign function directly in NewDefaultFlusher<commit_after>package cache

import(
	"time"

	"github.com/drone/drone-cache-lib/storage"

	log "github.com/Sirupsen/logrus"
)

type dirtyFunc func(storage.FileEntry) bool

type Flusher struct {
	store storage.Storage
	dirty func(storage.FileEntry) bool
}

func NewFlusher(s storage.Storage, fn dirtyFunc) Flusher {
	return Flusher{ store: s, dirty: fn }
}

func NewDefaultFlusher(s storage.Storage) Flusher {
	return Flusher{ store: s, dirty: IsExpired }
}

func (f *Flusher) Flush(src string) error {
	log.Infof("Cleaning files from %s", src)

	files, err := f.store.List(src)
	if err != nil {
		return err
	}

	for _, file := range files {
		if f.dirty(file) {
			err := f.store.Delete(file.Path)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func IsExpired(file storage.FileEntry) bool {
	// Do not delete directories
	if file.Info.IsDir() {
		return false
	}

	// Check if older then 30 days
	if file.Info.ModTime().Before(time.Now().AddDate(0, 0, -30)) {
		return true
	}

	// No match
	return false
}
<|endoftext|>


<commit_before>package main

import (
   "fmt"
   "io"
   "os"
   "time"
   "flag"
   "path"
   "os/exec"
   "math/rand"
   "path/filepath"
   "encoding/json"
   "github.com/GlenKelley/buildtools/git"
)

func main() {
   env := flags()

   groot, err := git.GitRoot()
   panicOnError(err)

   buildDir, err := mkdirRandom()
   panicOnError(err)
   defer os.RemoveAll(buildDir)

   deployDir := path.Join(os.Getenv("HOME"), "Sites")
   c, err := walkDir(groot, buildDir)
   panicOnError(err)
   err = <- c
   panicOnError(err)

   err = os.RemoveAll(deployDir)
   panicOnError(err)
   err = exec.Command("mv", buildDir, deployDir).Run()
   panicOnError(err)
}

func flags() string {
   envPtr := flag.String("env", "local", "environment")
   flag.Parse()
   fmt.Printf("building for environment [%s]\n", *envPtr)
   return *env
}

func mkdirRandom() (string, error) {
    randomName := fmt.Sprintf("%v", rand.Int())
    dirPath := path.Join(os.TempDir(), randomName)
    err := os.Mkdir(dirPath, os.ModeDir | os.ModeTemporary)
    err = os.Chmod(dirPath, 0755)
    return dirPath, err
}

func walkDir(srcDir string, buildDir string) (chan error, error) {
    onFinish := make(chan error)
    run := func (f ProcessFile, path string, info os.FileInfo) {
        err := f(srcDir, buildDir, path, info)
        if err != nil { fmt.Printf("%s : %s\n", path, err) }
        onFinish <- err
    }

    handlers := map[string]ProcessFile {
        ".less": compileLess,
        ".html": copyAndZip,
        ".css": copyAndZip,
        ".js": copyAndZip,
        ".jpg": copyToBuild,
        ".png": copyToBuild,
        ".gif": copyToBuild,
        ".coffee": compileCoffeeScript,
        ".json": copyAndZip,
        ".svg": copyAndZip,
        ".go": compileGo,
        ".coffeejson": compileCoffeeJson,
        ".fs": copyToBuild,
        ".vs": copyToBuild,
        ".dae": copyToBuild,
        ".DS_Store": ignore,
    }
    ignore := map[string]bool {
       ".git":true,
    }

    visited := 0
    err := filepath.Walk(srcDir, func (path string, info os.FileInfo, err error) error {
        if err != nil {
            fmt.Println(err)
            return nil
        }
        if ignore[info.Name()] { return filepath.SkipDir }
        if info.IsDir() { return nil }
        ext := filepath.Ext(info.Name())
        handler := handlers[ext]
        if handler != nil {
            visited++;
            go run(handler, path, info)
        } else {
            fmt.Printf("unknown ext %s\n", ext)
        }
        return nil
    })
    if err != nil {
        return nil, err
    }
    finished := make(chan error)
    go func(v int) {
        fail := error(nil)
        for ; v > 0; v-- {
            e := <- onFinish
            if e != nil {
               fail = e
            }
        }
        finished <- fail
    }(visited)
    return finished, nil
}

type ProcessFile func(srcDir string, buildDir string, path string, info os.FileInfo) error

func compileGo(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err := replacePathAndExtention(srcDir, buildDir, path, "")
    if err != nil { return err }

    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return err }

    cmd := exec.Command("go", "build", "-o", dest, path)
	stdout, err := cmd.StdoutPipe()
    if err != nil { return err }
	stderr, err := cmd.StderrPipe()
    if err != nil { return err }

    err = cmd.Start()
    if err != nil { return err }

    go io.Copy(os.Stdout, stdout)
    go io.Copy(os.Stdout, stderr)

	err = cmd.Wait()
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func ignore(srcDir string, buildDir string, path string, info os.FileInfo) error {
    return nil
}

func compileLess(srcDir string, buildDir string, path string, info os.FileInfo) error {
    base := filepath.Base(path)
    isChild, err := filepath.Match("_*", base)
    if err != nil { return err }
    if (!isChild) {
        dest, err := replacePathAndExtention(srcDir, buildDir, path, ".css")
        if err != nil { return err }

        dir := filepath.Dir(dest)
        err = MkdirAll(dir)
        if err != nil { return err }

        cmd := exec.Command("lessc", path)
        err = pipeCommandToFile(cmd, dest)
        if err != nil { return err }

        err = gZipFile(dest)
        if err != nil { return err }

        err = setFileTimestamp(dest, info.ModTime())
        if err != nil { return err }

        err = os.Chmod(dest, 0755)
        if err != nil { return err }
    }
    return nil
}

func writeJson(content map[string]interface{}, buildDir string, path string) error {
    dest := filepath.Join(buildDir, path)

    file, err := os.Create(dest)
    if err != nil { return err }
    defer file.Close()

    encoder := json.NewEncoder(file)
    err = encoder.Encode(content)
    if err != nil { return err }
    file.Close()

    err = gZipFile(dest)
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func compileCoffeeScript(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err := replacePathAndExtention(srcDir, buildDir, path, ".js")
    if err != nil { return err }

    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return err }

    cmd := exec.Command("coffee", "-p", path)
    err = pipeCommandToFile(cmd, dest)
    if err != nil { return err }

    err = gZipFile(dest)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func compileCoffeeJson(srcDir string, buildDir string, path string, info os.FileInfo) error {
    srcDir = filepath.Join(srcDir, "coffee")
    buildDir = filepath.Join(buildDir, "js")

    dest, err := replacePathAndExtention(srcDir, buildDir, path, ".json")
    if err != nil { return err }

    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return err }

    cmd := exec.Command("coffee", "-p", "-b", path)
    err = pipeCommandToFile(cmd, dest)
    if err != nil { return err }

    cmd = exec.Command("perl", "-pi", "-e", "s/;|\\(|\\)//g", dest)
    err = cmd.Run()
    if err != nil { return err }

    cmd = exec.Command("perl", "-pi", "-e", "s/(\\w+):/\"$1\":/g", dest)
    err = cmd.Run()
    if err != nil { return err }

    err = gZipFile(dest)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func copyToBuild(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err := copy(srcDir, buildDir, path, info)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func copyAndZip(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err:= copy(srcDir, buildDir, path, info)
    if err != nil { return err }
    err = gZipFile(dest)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func pipeCommandToFile(cmd *exec.Cmd, path string) error {
    file, err := os.Create(path)
    if err != nil { return err }
    defer file.Close()
    stdout, err := cmd.StdoutPipe()
    if err != nil { return err }
	stderr, err := cmd.StderrPipe()
    if err != nil { return err }

    err = cmd.Start()
    if err != nil { return err }

    go io.Copy(os.Stdout, stderr)

    io.Copy(file, stdout)
    return cmd.Wait()
}

func replaceBasePath(srcDir string, buildDir string, path string) (string, error) {
    relativePath, err := filepath.Rel(srcDir, path)
    if err != nil { return "", err }
    return filepath.Join(buildDir, relativePath), nil
}

func replaceExtention(path string, ext string) string {
    dir := filepath.Dir(path)
    name := nameWithoutExt(path)
    return filepath.Join(dir, name + ext)
}

func replacePathAndExtention(srcDir string, buildDir string, path string, ext string) (string, error) {
    path, err := replaceBasePath(srcDir, buildDir, path)
    if err != nil { return "", err }

    return replaceExtention(path, ext), nil
}

func copy(srcDir string, buildDir string, path string, info os.FileInfo) (string, error) {
    relativePath, err := filepath.Rel(srcDir, path)
    if err != nil { return "", err }

    dest := filepath.Join(buildDir, relativePath)
    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return "", err }

    cmd := exec.Command("cp", path, dest)
    return dest, cmd.Run()
}

func MkdirAll(path string) error {
    return os.MkdirAll(path, 0755)
}

func nameWithoutExt(path string) string {
    filename := filepath.Base(path)
    ext := filepath.Ext(path)
    return filename[0:len(filename)-len(ext)]
}

func minifyJs(jarDir, src, dest string) error {
    jarPath := filepath.Join(jarDir, "compiler.jar")
    cmd := exec.Command("java", "-jar", jarPath, "--js", src, "--js_output_file", dest, "--compilation_level", "SIMPLE_OPTIMIZATIONS")
    return cmd.Run()
}

func gZipFile(path string) error {
    err := exec.Command("gzip", "-n", path).Run()
    if err != nil { return err }
    return os.Rename(path + ".gz", path)
}

func setFileTimestamp(path string, time time.Time) error {
    return os.Chtimes(path, time, time)
}

func panicOnError (err error) {
    if err != nil { panic(err) }
}
<commit_msg>fixed typo<commit_after>package main

import (
   "fmt"
   "io"
   "os"
   "time"
   "flag"
   "path"
   "os/exec"
   "math/rand"
   "path/filepath"
   "encoding/json"
   "github.com/GlenKelley/dev/git"
)

func main() {
   flags()

   groot, err := git.GitRoot()
   panicOnError(err)

   buildDir, err := mkdirRandom()
   panicOnError(err)
   defer os.RemoveAll(buildDir)

   deployDir := path.Join(os.Getenv("HOME"), "Sites")
   c, err := walkDir(groot, buildDir)
   panicOnError(err)
   err = <- c
   panicOnError(err)

   err = os.RemoveAll(deployDir)
   panicOnError(err)
   err = exec.Command("mv", buildDir, deployDir).Run()
   panicOnError(err)
}

func flags() string {
   envPtr := flag.String("env", "local", "environment")
   flag.Parse()
   fmt.Printf("building for environment [%s]\n", *envPtr)
   return *envPtr
}

func mkdirRandom() (string, error) {
    randomName := fmt.Sprintf("%v", rand.Int())
    dirPath := path.Join(os.TempDir(), randomName)
    err := os.Mkdir(dirPath, os.ModeDir | os.ModeTemporary)
    err = os.Chmod(dirPath, 0755)
    return dirPath, err
}

func walkDir(srcDir string, buildDir string) (chan error, error) {
    onFinish := make(chan error)
    run := func (f ProcessFile, path string, info os.FileInfo) {
        err := f(srcDir, buildDir, path, info)
        if err != nil { fmt.Printf("%s : %s\n", path, err) }
        onFinish <- err
    }

    handlers := map[string]ProcessFile {
        ".less": compileLess,
        ".html": copyAndZip,
        ".css": copyAndZip,
        ".js": copyAndZip,
        ".jpg": copyToBuild,
        ".png": copyToBuild,
        ".gif": copyToBuild,
        ".coffee": compileCoffeeScript,
        ".json": copyAndZip,
        ".svg": copyAndZip,
        ".go": compileGo,
        ".coffeejson": compileCoffeeJson,
        ".fs": copyToBuild,
        ".vs": copyToBuild,
        ".dae": copyToBuild,
        ".DS_Store": ignore,
    }
    ignore := map[string]bool {
       ".git":true,
    }

    visited := 0
    err := filepath.Walk(srcDir, func (path string, info os.FileInfo, err error) error {
        if err != nil {
            fmt.Println(err)
            return nil
        }
        if ignore[info.Name()] { return filepath.SkipDir }
        if info.IsDir() { return nil }
        ext := filepath.Ext(info.Name())
        handler := handlers[ext]
        if handler != nil {
            visited++;
            go run(handler, path, info)
        } else {
            fmt.Printf("unknown ext %s\n", ext)
        }
        return nil
    })
    if err != nil {
        return nil, err
    }
    finished := make(chan error)
    go func(v int) {
        fail := error(nil)
        for ; v > 0; v-- {
            e := <- onFinish
            if e != nil {
               fail = e
            }
        }
        finished <- fail
    }(visited)
    return finished, nil
}

type ProcessFile func(srcDir string, buildDir string, path string, info os.FileInfo) error

func compileGo(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err := replacePathAndExtention(srcDir, buildDir, path, "")
    if err != nil { return err }

    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return err }

    cmd := exec.Command("go", "build", "-o", dest, path)
	stdout, err := cmd.StdoutPipe()
    if err != nil { return err }
	stderr, err := cmd.StderrPipe()
    if err != nil { return err }

    err = cmd.Start()
    if err != nil { return err }

    go io.Copy(os.Stdout, stdout)
    go io.Copy(os.Stdout, stderr)

	err = cmd.Wait()
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func ignore(srcDir string, buildDir string, path string, info os.FileInfo) error {
    return nil
}

func compileLess(srcDir string, buildDir string, path string, info os.FileInfo) error {
    base := filepath.Base(path)
    isChild, err := filepath.Match("_*", base)
    if err != nil { return err }
    if (!isChild) {
        dest, err := replacePathAndExtention(srcDir, buildDir, path, ".css")
        if err != nil { return err }

        dir := filepath.Dir(dest)
        err = MkdirAll(dir)
        if err != nil { return err }

        cmd := exec.Command("lessc", path)
        err = pipeCommandToFile(cmd, dest)
        if err != nil { return err }

        err = gZipFile(dest)
        if err != nil { return err }

        err = setFileTimestamp(dest, info.ModTime())
        if err != nil { return err }

        err = os.Chmod(dest, 0755)
        if err != nil { return err }
    }
    return nil
}

func writeJson(content map[string]interface{}, buildDir string, path string) error {
    dest := filepath.Join(buildDir, path)

    file, err := os.Create(dest)
    if err != nil { return err }
    defer file.Close()

    encoder := json.NewEncoder(file)
    err = encoder.Encode(content)
    if err != nil { return err }
    file.Close()

    err = gZipFile(dest)
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func compileCoffeeScript(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err := replacePathAndExtention(srcDir, buildDir, path, ".js")
    if err != nil { return err }

    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return err }

    cmd := exec.Command("coffee", "-p", path)
    err = pipeCommandToFile(cmd, dest)
    if err != nil { return err }

    err = gZipFile(dest)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func compileCoffeeJson(srcDir string, buildDir string, path string, info os.FileInfo) error {
    srcDir = filepath.Join(srcDir, "coffee")
    buildDir = filepath.Join(buildDir, "js")

    dest, err := replacePathAndExtention(srcDir, buildDir, path, ".json")
    if err != nil { return err }

    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return err }

    cmd := exec.Command("coffee", "-p", "-b", path)
    err = pipeCommandToFile(cmd, dest)
    if err != nil { return err }

    cmd = exec.Command("perl", "-pi", "-e", "s/;|\\(|\\)//g", dest)
    err = cmd.Run()
    if err != nil { return err }

    cmd = exec.Command("perl", "-pi", "-e", "s/(\\w+):/\"$1\":/g", dest)
    err = cmd.Run()
    if err != nil { return err }

    err = gZipFile(dest)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func copyToBuild(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err := copy(srcDir, buildDir, path, info)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func copyAndZip(srcDir string, buildDir string, path string, info os.FileInfo) error {
    dest, err:= copy(srcDir, buildDir, path, info)
    if err != nil { return err }
    err = gZipFile(dest)
    if err != nil { return err }

    err = setFileTimestamp(dest, info.ModTime())
    if err != nil { return err }

    err = os.Chmod(dest, 0755)
    if err != nil { return err }

    return nil
}

func pipeCommandToFile(cmd *exec.Cmd, path string) error {
    file, err := os.Create(path)
    if err != nil { return err }
    defer file.Close()
    stdout, err := cmd.StdoutPipe()
    if err != nil { return err }
	stderr, err := cmd.StderrPipe()
    if err != nil { return err }

    err = cmd.Start()
    if err != nil { return err }

    go io.Copy(os.Stdout, stderr)

    io.Copy(file, stdout)
    return cmd.Wait()
}

func replaceBasePath(srcDir string, buildDir string, path string) (string, error) {
    relativePath, err := filepath.Rel(srcDir, path)
    if err != nil { return "", err }
    return filepath.Join(buildDir, relativePath), nil
}

func replaceExtention(path string, ext string) string {
    dir := filepath.Dir(path)
    name := nameWithoutExt(path)
    return filepath.Join(dir, name + ext)
}

func replacePathAndExtention(srcDir string, buildDir string, path string, ext string) (string, error) {
    path, err := replaceBasePath(srcDir, buildDir, path)
    if err != nil { return "", err }

    return replaceExtention(path, ext), nil
}

func copy(srcDir string, buildDir string, path string, info os.FileInfo) (string, error) {
    relativePath, err := filepath.Rel(srcDir, path)
    if err != nil { return "", err }

    dest := filepath.Join(buildDir, relativePath)
    dir := filepath.Dir(dest)
    err = MkdirAll(dir)
    if err != nil { return "", err }

    cmd := exec.Command("cp", path, dest)
    return dest, cmd.Run()
}

func MkdirAll(path string) error {
    return os.MkdirAll(path, 0755)
}

func nameWithoutExt(path string) string {
    filename := filepath.Base(path)
    ext := filepath.Ext(path)
    return filename[0:len(filename)-len(ext)]
}

func minifyJs(jarDir, src, dest string) error {
    jarPath := filepath.Join(jarDir, "compiler.jar")
    cmd := exec.Command("java", "-jar", jarPath, "--js", src, "--js_output_file", dest, "--compilation_level", "SIMPLE_OPTIMIZATIONS")
    return cmd.Run()
}

func gZipFile(path string) error {
    err := exec.Command("gzip", "-n", path).Run()
    if err != nil { return err }
    return os.Rename(path + ".gz", path)
}

func setFileTimestamp(path string, time time.Time) error {
    return os.Chtimes(path, time, time)
}

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


<commit_before>// Copyright 2015-2016 Sevki <s@sevki.org>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package build defines build target and build context structures
package build

import (
	"bytes"
	"context"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"bldy.build/build/url"
)

var (
	printenv = flag.Bool("e", false, "prints envinroment variables into the log")
)

// Target defines the interface that rules must implement for becoming build targets.
type Target interface {
	GetName() string
	GetDependencies() []string

	Hash() []byte
	Build(*Runner) error
	Installs() map[string]string
}

// Runner defines the envinroment in which a target will be built, it
// provide helper functions for shelling out without having to worry
// about stdout or stderr outputs.
type Runner struct {
	ctx context.Context
	wd  string
	run []*Run
	log []fmt.Stringer
}

func (r *Runner) Context() context.Context {
	return r.ctx
}

type Run struct {
	At     time.Time
	Cmd    string
	Args   []string
	Output []byte
	Env    []string
	Err    error
}

func (r *Run) String() string {
	buf := bytes.Buffer{}
	if *printenv {
		buf.WriteString("envinroment variables: " + strings.Join(r.Env, "\n"))
	}
	buf.WriteString(strings.Join(append([]string{r.Cmd}, r.Args...), "\n"))

	buf.WriteString("\n")
	buf.Write(r.Output)
	return string(buf.String())
}

type Message string

func (m Message) String() string {
	return string(m)
}
func (r *Runner) RunCmds() []*Run {
	return r.run
}

func (r *Runner) Log() []fmt.Stringer {
	return r.log
}

// NewContext initializes and returns a new build.Context
func NewRunner(ctx context.Context, dir string) *Runner {
	return &Runner{
		wd:  dir,
		ctx: ctx,
	}
}

func (r *Runner) Printf(format string, v ...interface{}) {
	r.log = append(r.log, Message(fmt.Sprintf(format, v...)))
}

func (r *Runner) Println(v ...interface{}) {
	r.log = append(r.log, Message(fmt.Sprintln(v...)))
}

// Exec executes a command writing it's outputs to the context
func (r *Runner) Exec(cmd string, env, args []string) error {
	run := Run{
		At:   time.Now(),
		Cmd:  cmd,
		Args: args,
		Env:  env,
	}
	x := exec.CommandContext(r.ctx, cmd, args...)
	x.Dir = r.wd
	x.Env = env
	run.Output, run.Err = x.CombinedOutput()
	r.run = append(r.run, &run)
	r.log = append(r.log, &run)
	return run.Err
}

// Run executes a command writing it's outputs to the context
func (r *Runner) Run(ctx context.Context, cmd string, env, params []string) *exec.Cmd {
	x := exec.CommandContext(ctx, cmd, params...)

	x.Dir = r.wd
	x.Env = env
	return x
}

// Create creates and returns a new file with the given name in the context
func (r *Runner) Create(name string) (*os.File, error) {
	return os.Create(filepath.Join(r.wd, name))
}

// Open creates and returns a new file with the given name in the context
func (r *Runner) Open(name string) (*os.File, error) {
	if filepath.IsAbs(name) {
		return os.Open(name)
	}
	return os.Open(filepath.Join(r.wd, name))
}

func (r *Runner) Mkdir(name string) error {
	return os.MkdirAll(filepath.Join(r.wd, name), os.ModeDir|os.ModePerm)
}

// VM seperate the parsing and evauluating targets logic from rest of bldy
// so we can implement and use new grammars like jsonnet or go it self.
type VM interface {
	GetTarget(url.URL) (Target, error)
}

// Status represents a nodes status.
type Status int

const (
	Success Status = iota
	Fail
	Pending
	Started
	Fatal
	Warning
	Building
)
<commit_msg>build: rename runner to executor<commit_after>// Copyright 2015-2016 Sevki <s@sevki.org>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package build defines build target and build context structures
package build

import (
	"bytes"
	"context"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"bldy.build/build/url"
)

var (
	printenv = flag.Bool("e", false, "prints envinroment variables into the log")
)

// Target defines the interface that rules must implement for becoming build targets.
type Target interface {
	GetName() string
	GetDependencies() []string

	Hash() []byte
	Build(*Executor) error
	Installs() map[string]string
}

// Runner defines the envinroment in which a target will be built, it
// provide helper functions for shelling out without having to worry
// about stdout or stderr outputs.
type Executor struct {
	ctx context.Context
	wd  string
	run []*Run
	log []fmt.Stringer
}

func (e *Executor) Context() context.Context {
	return e.ctx
}

type Run struct {
	At     time.Time
	Cmd    string
	Args   []string
	Output []byte
	Env    []string
	Err    error
}

func (r *Run) String() string {
	buf := bytes.Buffer{}
	if *printenv {
		buf.WriteString("envinroment variables: " + strings.Join(r.Env, "\n"))
	}
	buf.WriteString(strings.Join(append([]string{r.Cmd}, r.Args...), "\n"))

	buf.WriteString("\n")
	buf.Write(r.Output)
	return string(buf.String())
}

type Message string

func (m Message) String() string {
	return string(m)
}
func (e *Executor) RunCmds() []*Run {
	return e.run
}

func (e *Executor) Log() []fmt.Stringer {
	return e.log
}

// NewContext initializes and returns a new build.Context
func NewRunner(ctx context.Context, dir string) *Executor {
	return &Executor{
		wd:  dir,
		ctx: ctx,
	}
}

func (e *Executor) Printf(format string, v ...interface{}) {
	e.log = append(e.log, Message(fmt.Sprintf(format, v...)))
}

func (e *Executor) Println(v ...interface{}) {
	e.log = append(e.log, Message(fmt.Sprintln(v...)))
}

// Exec executes a command writing it's outputs to the context
func (e *Executor) Exec(cmd string, env, args []string) error {
	run := Run{
		At:   time.Now(),
		Cmd:  cmd,
		Args: args,
		Env:  env,
	}
	x := exec.CommandContext(e.ctx, cmd, args...)
	x.Dir = e.wd
	x.Env = env
	run.Output, run.Err = x.CombinedOutput()
	e.run = append(e.run, &run)
	e.log = append(e.log, &run)
	return run.Err
}

// Run executes a command writing it's outputs to the context
func (e *Executor) Run(ctx context.Context, cmd string, env, params []string) *exec.Cmd {
	x := exec.CommandContext(ctx, cmd, params...)

	x.Dir = e.wd
	x.Env = env
	return x
}

// Create creates and returns a new file with the given name in the context
func (r *Executor) Create(name string) (*os.File, error) {
	return os.Create(filepath.Join(r.wd, name))
}

// Open creates and returns a new file with the given name in the context
func (r *Executor) Open(name string) (*os.File, error) {
	if filepath.IsAbs(name) {
		return os.Open(name)
	}
	return os.Open(filepath.Join(r.wd, name))
}

func (r *Executor) Mkdir(name string) error {
	return os.MkdirAll(filepath.Join(r.wd, name), os.ModeDir|os.ModePerm)
}

// VM seperate the parsing and evauluating targets logic from rest of bldy
// so we can implement and use new grammars like jsonnet or go it self.
type VM interface {
	GetTarget(url.URL) (Target, error)
}

// Status represents a nodes status.
type Status int

const (
	Success Status = iota
	Fail
	Pending
	Started
	Fatal
	Warning
	Building
)
<|endoftext|>


<commit_before>package teamcity

// Builds is a list of builds
type Builds struct {
	Builds []Build `json:"build"`
}

// Build is an instance of a stage in the build chain for a given project
type Build struct {
	Id          int         `json:"id"`
	Number      string      `json:"number"`
	BuildTypeId string      `json:"buildTypeId"`
	BuildType   BuildType   `json:"buildType"`
	Status      string      `json:"status"`
	State       string      `json:"state"`
	Href        string      `json:"href"`
	StatusText  string      `json:"statusText"`
	QueuedDate  string      `json:"queuedDate"`
	StartDate   string      `json:"startDate"`
	FinishDate  string      `json:"finishDate"`
	LastChanges LastChanges `json:"lastChanges"`
}

// BuildType is a type of Build
type BuildType struct {
	Name string `json:"name"`
}

// LastChanges are the list of changes that corresponds to a certain build
type LastChanges struct {
	Changes []Change `json:"change"`
}

// Change is an individual change in a group that corresponds to a certain build
type Change struct {
	Id       int    `json:"id"`
	Version  string `json:"version"`
	Username string `json:"username"`
}
<commit_msg>yext/teamcity: use TeamCityDate instead of string for date types R=robfig<commit_after>package teamcity

import (
	"strings"
	"time"
)

const (
	dateFormat = "20060102T150405-0700"
)

// Builds is a list of builds
type Builds struct {
	Builds []Build `json:"build"`
}

// Build is an instance of a stage in the build chain for a given project
type Build struct {
	Id          int         `json:"id"`
	Number      string      `json:"number"`
	BuildTypeId string      `json:"buildTypeId"`
	BuildType   BuildType   `json:"buildType"`
	Status      string      `json:"status"`
	State       string      `json:"state"`
	Href        string      `json:"href"`
	StatusText  string      `json:"statusText"`
	QueuedDate  Time        `json:"queuedDate"`
	StartDate   Time        `json:"startDate"`
	FinishDate  Time        `json:"finishDate"`
	LastChanges LastChanges `json:"lastChanges"`
}

// BuildType is a type of Build
type BuildType struct {
	Name string `json:"name"`
}

// LastChanges are the list of changes that corresponds to a certain build
type LastChanges struct {
	Changes []Change `json:"change"`
}

// Change is an individual change in a group that corresponds to a certain build
type Change struct {
	Id       int    `json:"id"`
	Version  string `json:"version"`
	Username string `json:"username"`
}

// Time is the date in the format TeamCity provides
type Time time.Time

// UnmarshalJSON unmarshals the time using the TeamCity format
func (t *Time) UnmarshalJSON(buf []byte) error {
	tt, err := time.Parse(dateFormat, strings.Trim(string(buf), `"`))
	if err != nil {
		return err
	}
	*t = Time(tt)
	return nil
}

// MarshalJSON marshals the time using the TeamCity format
func (t Time) MarshalJSON() ([]byte, error) {
	return []byte(`"` + time.Time(t).Format(dateFormat) + `"`), nil
}
<|endoftext|>