<commit_before>package keys

import (
	"fmt"

	"github.com/ambientsound/pms/console"
	"github.com/ambientsound/pms/keysequence"
	"github.com/gdamore/tcell/v2"
)

// Binding holds a parsed, user provided key sequence.
type Binding struct {
	Command  string
	Sequence keysequence.KeySequence
}

// Sequencer holds all the keyboard bindings and their action mappings.
type Sequencer struct {
	binds []Binding
	event *tcell.EventKey
	input keysequence.KeySequence
}

// NewSequencer returns Sequencer.
func NewSequencer() *Sequencer {
	return &Sequencer{
		binds: make([]Binding, 0),
		input: make(keysequence.KeySequence, 0),
	}
}

// AddBind creates a new key mapping.
func (s *Sequencer) AddBind(seq keysequence.KeySequence, command string) error {
	if s.dupes(seq) {
		return fmt.Errorf("Can't bind: conflicting with already bound key sequence")
	}
	s.binds = append(s.binds, Binding{Sequence: seq, Command: command})
	return nil
}

// RemoveBind removes a key mapping.
func (s *Sequencer) RemoveBind(seq keysequence.KeySequence) error {
	for i := range s.binds {
		if keysequence.Compare(s.binds[i].Sequence, seq) {
			// Overwrite this position with the last in the list
			s.binds[i] = s.binds[len(s.binds)-1]

			// Truncate to remove the (now duplicate) last entry
			s.binds = s.binds[:len(s.binds)-1]

			return nil
		}
	}

	return fmt.Errorf("Can't unbind: sequence not bound")
}

// KeyInput feeds a keypress to the sequencer. Returns true if there is one match or more, or false if there is no match.
func (s *Sequencer) KeyInput(ev *tcell.EventKey) bool {
	console.Log("Key event: %s", keysequence.FormatKey(ev))
	s.input = append(s.input, ev)
	if len(s.find(s.input)) == 0 {
		s.input = make(keysequence.KeySequence, 0)
		return false
	}
	return true
}

// String returns the current input sequence as a string.
func (s *Sequencer) String() string {
	return keysequence.Format(s.input)
}

// dupes returns true if binding the given key event sequence will conflict with any other bound sequences.
func (s *Sequencer) dupes(seq keysequence.KeySequence) bool {
	matches := s.find(seq)
	return len(matches) > 0
}

// find returns a list of potential matches to key bindings.
func (s *Sequencer) find(seq keysequence.KeySequence) []Binding {
	binds := make([]Binding, 0)
	for i := range s.binds {
		if keysequence.StartsWith(s.binds[i].Sequence, seq) {
			binds = append(binds, s.binds[i])
		}
	}
	return binds
}

// Match returns a key binding if the current input sequence is found.
func (s *Sequencer) Match() *Binding {
	binds := s.find(s.input)
	if len(binds) != 1 {
		return nil
	}
	b := binds[0]
	//console.Log("Possible match found: %+v ||| %+v", b.Sequence, s.input)
	if !keysequence.Compare(b.Sequence, s.input) {
		return nil
	}
	//console.Log("Match found: %+v", b)
	s.input = make(keysequence.KeySequence, 0)
	return &b
}
<commit_msg>Pet the static checker: Error messages should start lowercase<commit_after>package keys

import (
	"fmt"

	"github.com/ambientsound/pms/console"
	"github.com/ambientsound/pms/keysequence"
	"github.com/gdamore/tcell/v2"
)

// Binding holds a parsed, user provided key sequence.
type Binding struct {
	Command  string
	Sequence keysequence.KeySequence
}

// Sequencer holds all the keyboard bindings and their action mappings.
type Sequencer struct {
	binds []Binding
	event *tcell.EventKey
	input keysequence.KeySequence
}

// NewSequencer returns Sequencer.
func NewSequencer() *Sequencer {
	return &Sequencer{
		binds: make([]Binding, 0),
		input: make(keysequence.KeySequence, 0),
	}
}

// AddBind creates a new key mapping.
func (s *Sequencer) AddBind(seq keysequence.KeySequence, command string) error {
	if s.dupes(seq) {
		return fmt.Errorf("can't bind: conflicting with already bound key sequence")
	}
	s.binds = append(s.binds, Binding{Sequence: seq, Command: command})
	return nil
}

// RemoveBind removes a key mapping.
func (s *Sequencer) RemoveBind(seq keysequence.KeySequence) error {
	for i := range s.binds {
		if keysequence.Compare(s.binds[i].Sequence, seq) {
			// Overwrite this position with the last in the list
			s.binds[i] = s.binds[len(s.binds)-1]

			// Truncate to remove the (now duplicate) last entry
			s.binds = s.binds[:len(s.binds)-1]

			return nil
		}
	}

	return fmt.Errorf("can't unbind: sequence not bound")
}

// KeyInput feeds a keypress to the sequencer. Returns true if there is one match or more, or false if there is no match.
func (s *Sequencer) KeyInput(ev *tcell.EventKey) bool {
	console.Log("Key event: %s", keysequence.FormatKey(ev))
	s.input = append(s.input, ev)
	if len(s.find(s.input)) == 0 {
		s.input = make(keysequence.KeySequence, 0)
		return false
	}
	return true
}

// String returns the current input sequence as a string.
func (s *Sequencer) String() string {
	return keysequence.Format(s.input)
}

// dupes returns true if binding the given key event sequence will conflict with any other bound sequences.
func (s *Sequencer) dupes(seq keysequence.KeySequence) bool {
	matches := s.find(seq)
	return len(matches) > 0
}

// find returns a list of potential matches to key bindings.
func (s *Sequencer) find(seq keysequence.KeySequence) []Binding {
	binds := make([]Binding, 0)
	for i := range s.binds {
		if keysequence.StartsWith(s.binds[i].Sequence, seq) {
			binds = append(binds, s.binds[i])
		}
	}
	return binds
}

// Match returns a key binding if the current input sequence is found.
func (s *Sequencer) Match() *Binding {
	binds := s.find(s.input)
	if len(binds) != 1 {
		return nil
	}
	b := binds[0]
	//console.Log("Possible match found: %+v ||| %+v", b.Sequence, s.input)
	if !keysequence.Compare(b.Sequence, s.input) {
		return nil
	}
	//console.Log("Match found: %+v", b)
	s.input = make(keysequence.KeySequence, 0)
	return &b
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/raintank/raintank-metric/dur"
	"github.com/raintank/raintank-metric/metricdef"
	"github.com/raintank/raintank-metric/schema"
	"log"
	"os"
	"time"
)

type Hit struct {
	Index  string                  `json:"_index"`
	Type   string                  `json:"_type"`
	Id     string                  `json:"_id"`
	Score  float64                 `json:"_score"`
	Source schema.MetricDefinition `json:"_source"`
}

type EsResult struct {
	Took     int
	TimedOut bool
	_shards  struct {
		total      int
		successful int
		failed     int
	}
	Hits struct {
		Total    int
		MaxScore int
		Hits     []Hit
	}
}

func perror(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

var esAddr = flag.String("es-addr", "localhost:9200", "elasticsearch address")
var esIndex = flag.String("es-index", "metrictank", "elasticsearch index to query")
var format = flag.String("format", "list", "format: list|vegeta-graphite|vegeta-mt")
var maxAge = flag.Int("max-age", 23400, "max age (last update diff with now) of metricdefs. defaults to 6.5hr. use 0 to disable")
var from = flag.String("from", "30min", "from. eg '30min', '5h', '14d', etc")
var silent = flag.Bool("silent", false, "silent mode (don't print number of metrics loaded to stderr)")
var fromS uint32
var total int

func showList(ds []*schema.MetricDefinition) {
	for _, d := range ds {
		if *maxAge != 0 && d.LastUpdate > time.Now().Unix()-int64(*maxAge) {
			total += 1
			fmt.Println(d.OrgId, d.Name)
		}
	}
}
func showVegetaGraphite(ds []*schema.MetricDefinition) {
	for _, d := range ds {
		if *maxAge != 0 && d.LastUpdate > time.Now().Unix()-int64(*maxAge) {
			total += 1
			fmt.Printf("GET http://localhost:8888/render?target=%s&from=-%s\nX-Org-Id: %d\n", d.Name, *from, d.OrgId)
		}
	}
}
func showVegetaMT(ds []*schema.MetricDefinition) {
	from := time.Now().Add(-time.Duration(fromS) * time.Second)
	for _, d := range ds {
		if *maxAge != 0 && d.LastUpdate > time.Now().Unix()-int64(*maxAge) {
			total += 1
			fmt.Printf("GET http://localhost:18763/get?target=%s&from=%d\n", d.Id, from.Unix())
		}
	}
}

func main() {
	flag.Parse()
	var show func(ds []*schema.MetricDefinition)
	switch *format {
	case "list":
		show = showList
	case "vegeta-graphite":
		show = showVegetaGraphite
	case "vegeta-mt":
		show = showVegetaMT
	default:
		log.Fatal("invalid format")
	}
	var err error
	fromS, err = dur.ParseUNsec(*from)
	perror(err)
	defs, err := metricdef.NewDefsEs(*esAddr, "", "", *esIndex)
	perror(err)
	met, scroll_id, err := defs.GetMetrics("")
	perror(err)
	show(met)
	for scroll_id != "" {
		met, scroll_id, err = defs.GetMetrics(scroll_id)
		perror(err)
		show(met)
	}
	if !*silent {
		fmt.Fprintf(os.Stderr, "listed %d metrics\n", total)
	}
}
<commit_msg>old es stuff no longer needed<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/raintank/raintank-metric/dur"
	"github.com/raintank/raintank-metric/metricdef"
	"github.com/raintank/raintank-metric/schema"
	"log"
	"os"
	"time"
)

func perror(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

var esAddr = flag.String("es-addr", "localhost:9200", "elasticsearch address")
var esIndex = flag.String("es-index", "metrictank", "elasticsearch index to query")
var format = flag.String("format", "list", "format: list|vegeta-graphite|vegeta-mt")
var maxAge = flag.Int("max-age", 23400, "max age (last update diff with now) of metricdefs. defaults to 6.5hr. use 0 to disable")
var from = flag.String("from", "30min", "from. eg '30min', '5h', '14d', etc")
var silent = flag.Bool("silent", false, "silent mode (don't print number of metrics loaded to stderr)")
var fromS uint32
var total int

func showList(ds []*schema.MetricDefinition) {
	for _, d := range ds {
		if *maxAge != 0 && d.LastUpdate > time.Now().Unix()-int64(*maxAge) {
			total += 1
			fmt.Println(d.OrgId, d.Name)
		}
	}
}
func showVegetaGraphite(ds []*schema.MetricDefinition) {
	for _, d := range ds {
		if *maxAge != 0 && d.LastUpdate > time.Now().Unix()-int64(*maxAge) {
			total += 1
			fmt.Printf("GET http://localhost:8888/render?target=%s&from=-%s\nX-Org-Id: %d\n", d.Name, *from, d.OrgId)
		}
	}
}
func showVegetaMT(ds []*schema.MetricDefinition) {
	from := time.Now().Add(-time.Duration(fromS) * time.Second)
	for _, d := range ds {
		if *maxAge != 0 && d.LastUpdate > time.Now().Unix()-int64(*maxAge) {
			total += 1
			fmt.Printf("GET http://localhost:18763/get?target=%s&from=%d\n", d.Id, from.Unix())
		}
	}
}

func main() {
	flag.Parse()
	var show func(ds []*schema.MetricDefinition)
	switch *format {
	case "list":
		show = showList
	case "vegeta-graphite":
		show = showVegetaGraphite
	case "vegeta-mt":
		show = showVegetaMT
	default:
		log.Fatal("invalid format")
	}
	var err error
	fromS, err = dur.ParseUNsec(*from)
	perror(err)
	defs, err := metricdef.NewDefsEs(*esAddr, "", "", *esIndex)
	perror(err)
	met, scroll_id, err := defs.GetMetrics("")
	perror(err)
	show(met)
	for scroll_id != "" {
		met, scroll_id, err = defs.GetMetrics(scroll_id)
		perror(err)
		show(met)
	}
	if !*silent {
		fmt.Fprintf(os.Stderr, "listed %d metrics\n", total)
	}
}
<|endoftext|>


<commit_before>// package tsdb contains the data structures for emulating TSDB API.
// The tsdb package must not depend on any other scotty packages.
package tsdb

// TagFilter represents an arbitrary filter on a tag value. It returns true
// if a given tag value should be included or false otherwise.
type TagFilter func(s string) bool

// TsValue represents a single timestamped value
type TsValue struct {
	// In seconds since Jan 1, 1970
	Ts float64
	// The value
	Value float64
}

// TimeSeries represents a time series. TimeSeries are time stamped values
// sorted by time stamp in ascending order. TimeSeries contain values for
// all known time stamps even if the value hasn't changed. TimeSeries
// instances must be treated as immutable.
type TimeSeries []TsValue

// MarshalJSON satisfies the encoding/json.Marshaler interface and allows
// correct JSON encoding of TimeSeries values. In particular, time series
// must be encoded as
// {"secondsSinceEpoch1":value1, "secondsSinceEpoch2":value2, ...}
// in ascending order by time.
func (t TimeSeries) MarshalJSON() ([]byte, error) {
	return t.marshalJSON()
}

// TagSet represents a set of tsdb tags for time series in scotty.
// Since scotty organizes time series by host name and app name, those are the
// only tags
type TagSet struct {
	HostName string
	AppName  string
}

// TaggedTimeSeries represents a single tagged timeSeries.
type TaggedTimeSeries struct {
	Tags   TagSet
	Values TimeSeries
}

// TaggedTimeSeriesSet represets a set of tagged time series
type TaggedTimeSeriesSet struct {
	// The metric name
	MetricName string
	// the data
	Data []TaggedTimeSeries
	// If true, Data is grouped by the "HostName" tag
	GroupedByHostName bool
	// If true, Data is grouped by the "appname" tag
	GroupedByAppName bool
}

// Aggregator aggregates time series together.
type Aggregator interface {
	// Add adds a time series
	Add(values TimeSeries)
	// Aggregate returns the aggregated time series.
	Aggregate() TimeSeries
}

// AggregatorGenerator produces Aggregator values.
// start is the start time for the aggregator inclusive;
// end is the end time for the aggregator exclusive.
// start and end are seconds since Jan 1, 1970.
type AggregatorGenerator func(start, end float64) Aggregator
<commit_msg>TSDB API -> the TSDB api in tsdb package documentation.<commit_after>// package tsdb contains the data structures for emulating the TSDB API.
// The tsdb package must not depend on any other scotty packages.
package tsdb

// TagFilter represents an arbitrary filter on a tag value. It returns true
// if a given tag value should be included or false otherwise.
type TagFilter func(s string) bool

// TsValue represents a single timestamped value
type TsValue struct {
	// In seconds since Jan 1, 1970
	Ts float64
	// The value
	Value float64
}

// TimeSeries represents a time series. TimeSeries are time stamped values
// sorted by time stamp in ascending order. TimeSeries contain values for
// all known time stamps even if the value hasn't changed. TimeSeries
// instances must be treated as immutable.
type TimeSeries []TsValue

// MarshalJSON satisfies the encoding/json.Marshaler interface and allows
// correct JSON encoding of TimeSeries values. In particular, time series
// must be encoded as
// {"secondsSinceEpoch1":value1, "secondsSinceEpoch2":value2, ...}
// in ascending order by time.
func (t TimeSeries) MarshalJSON() ([]byte, error) {
	return t.marshalJSON()
}

// TagSet represents a set of tsdb tags for time series in scotty.
// Since scotty organizes time series by host name and app name, those are the
// only tags
type TagSet struct {
	HostName string
	AppName  string
}

// TaggedTimeSeries represents a single tagged timeSeries.
type TaggedTimeSeries struct {
	Tags   TagSet
	Values TimeSeries
}

// TaggedTimeSeriesSet represets a set of tagged time series
type TaggedTimeSeriesSet struct {
	// The metric name
	MetricName string
	// the data
	Data []TaggedTimeSeries
	// If true, Data is grouped by the "HostName" tag
	GroupedByHostName bool
	// If true, Data is grouped by the "appname" tag
	GroupedByAppName bool
}

// Aggregator aggregates time series together.
type Aggregator interface {
	// Add adds a time series
	Add(values TimeSeries)
	// Aggregate returns the aggregated time series.
	Aggregate() TimeSeries
}

// AggregatorGenerator produces Aggregator values.
// start is the start time for the aggregator inclusive;
// end is the end time for the aggregator exclusive.
// start and end are seconds since Jan 1, 1970.
type AggregatorGenerator func(start, end float64) Aggregator
<|endoftext|>


<commit_before>/* Copyright (c) 2014, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	indexTmpl = "index.html"
	formTmpl  = "form.html"
	idSize    = 8
	rawIdSize = idSize / 2
	randTries = 10

	// GET error messages
	invalidId     = "Invalid paste id."
	pasteNotFound = "Paste doesn't exist."
	unknownError  = "Something went terribly wrong."

	// Common error messages
	timedOut = "Request timed out."
)

var (
	siteUrl, listen, dataDir, maxSizeStr string
	lifeTime, timeout                    time.Duration
	maxSize                              ByteSize
	indexTemplate, formTemplate          *template.Template

	regexByteSize = regexp.MustCompile(`^([\d\.]+)\s*([KM]?B|[BKM])$`)
	startTime     = time.Now()
)

// Default is "text-plain; charset=utf-8"
var mimeTypes = map[string]string{
	".gif": "image/gif",
	".jpg": "image/jpeg",
	".png": "image/png",
	".pdf": "application/pdf",
}

var workers [256]Worker
var post = make(chan PostRequest) // Posting is shared to balance load

type Id [rawIdSize]byte

type PasteInfo struct {
	Etag, ContentType, Path string
	ModTime                 time.Time
}

type GetRequest struct {
	w    http.ResponseWriter
	r    *http.Request
	done chan struct{}
	id   Id
}

type PostRequest struct {
	w       http.ResponseWriter
	r       *http.Request
	done    chan struct{}
	content []byte
	ext     string
	modTime time.Time
}

type Worker struct {
	n   byte // Its number, aka the first two hex chars
	get chan GetRequest
	del chan Id
	m   map[Id]PasteInfo
}

func (w Worker) recoverPaste(filePath string, fileInfo os.FileInfo, err error) error {
	if err != nil {
		return err
	}
	if fileInfo.IsDir() {
		return nil
	}
	dirParts := strings.Split(filePath, string(filepath.Separator))
	if len(dirParts) != 2 {
		return errors.New("Found invalid number of directories at " + filePath)
	}
	hexId := dirParts[0] + dirParts[1]
	ext := filepath.Ext(hexId)
	id, err := IdFromString(hexId[:len(hexId)-len(ext)])
	if err != nil {
		return err
	}
	modTime := fileInfo.ModTime()
	deathTime := modTime.Add(lifeTime)
	if deathTime.Before(startTime) {
		err := os.Remove(filePath)
		if err != nil {
			return err
		}
	}
	if modTime.After(startTime) {
		modTime = startTime
	}
	w.m[id] = id.GenPasteInfo(modTime, ext)
	w.DeletePasteAfter(id, deathTime.Sub(startTime))
	return nil
}

func (w Worker) RandomId() (Id, error) {
	var id Id
	id[0] = w.n
	for try := 0; try < randTries; try++ {
		if _, err := rand.Read(id[1:]); err != nil {
			return id, err
		}
		if _, e := w.m[id]; !e {
			return id, nil
		}
	}
	return id, fmt.Errorf("Gave up trying to find an unused random id after %d tries", randTries)
}

func (w Worker) Work() {
	dir := hex.EncodeToString([]byte{w.n})
	if stat, err := os.Stat(dir); err == nil {
		if !stat.IsDir() {
			log.Fatalf("%s/%s exists but is not a directory!", dataDir, dir)
		}
	} else {
		if err := os.Mkdir(dir, 0700); err != nil {
			log.Fatalf("Could not create data directory %s/%s: %s", dataDir, dir, err)
		}
	}
	w.m = make(map[Id]PasteInfo)
	if err := filepath.Walk(dir, w.recoverPaste); err != nil {
		log.Fatalf("Could not recover data directory %s/%s: %s", dataDir, dir, err)
	}
	for {
		var done chan struct{}
		select {
		case request := <-w.get:
			done = request.done
			pasteInfo, e := w.m[request.id]
			if !e {
				http.Error(request.w, pasteNotFound, http.StatusNotFound)
				break
			}
			if inm := request.r.Header.Get("If-None-Match"); inm != "" {
				if pasteInfo.Etag == inm || inm == "*" {
					request.w.WriteHeader(http.StatusNotModified)
					break
				}
			}
			pasteFile, err := os.Open(pasteInfo.Path)
			if err != nil {
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			request.w.Header().Set("Etag", pasteInfo.Etag)
			request.w.Header().Set("Content-Type", pasteInfo.ContentType)
			http.ServeContent(request.w, request.r, "", pasteInfo.ModTime, pasteFile)
			pasteFile.Close()

		case request := <-post:
			done = request.done
			id, err := w.RandomId()
			if err != nil {
				log.Println(err)
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			pasteInfo := id.GenPasteInfo(request.modTime, request.ext)
			pasteFile, err := os.OpenFile(pasteInfo.Path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
			if err != nil {
				log.Printf("Could not create new paste file %s: %s", pasteInfo.Path, err)
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			_, err = pasteFile.Write(request.content)
			pasteFile.Close()
			if err != nil {
				log.Printf("Could not write data into %s: %s", pasteInfo.Path, err)
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			w.m[id] = pasteInfo
			w.DeletePasteAfter(id, lifeTime)
			fmt.Fprintf(request.w, "%s/%s\n", siteUrl, id)

		case id := <-w.del:
			pasteInfo, _ := w.m[id]
			if err := os.Remove(pasteInfo.Path); err == nil {
				delete(w.m, id)
			} else {
				log.Printf("Could not remove %s: %s", id, err)
				w.DeletePasteAfter(id, 2*time.Minute)
			}
		}
		if done != nil {
			done <- struct{}{}
		}
	}
}

func init() {
	flag.StringVar(&siteUrl, "u", "http://localhost:8080", "URL of the site")
	flag.StringVar(&listen, "l", "localhost:8080", "Host and port to listen to")
	flag.StringVar(&dataDir, "d", "data", "Directory to store all the pastes in")
	flag.DurationVar(&lifeTime, "t", 12*time.Hour, "Lifetime of the pastes")
	flag.DurationVar(&timeout, "T", 200*time.Millisecond, "Timeout of requests")
	flag.StringVar(&maxSizeStr, "s", "1M", "Maximum size of POSTs in bytes")
}

func IdFromString(hexId string) (Id, error) {
	var id Id
	if len(hexId) != idSize {
		return id, errors.New("Invalid id at " + hexId)
	}
	b, err := hex.DecodeString(hexId)
	if err != nil || len(b) != rawIdSize {
		return id, errors.New("Invalid id at " + hexId)
	}
	copy(id[:], b)
	return id, nil
}

func (id Id) String() string {
	return hex.EncodeToString(id[:])
}

func (id Id) GenPasteInfo(modTime time.Time, ext string) (pasteInfo PasteInfo) {
	pasteInfo.ModTime = modTime
	pasteInfo.Etag = fmt.Sprintf("%d-%s", pasteInfo.ModTime.Unix(), id)
	if pasteInfo.ContentType = mimeTypes[ext]; pasteInfo.ContentType == "" {
		pasteInfo.ContentType = "text-plain; charset=utf-8"
	}
	hexId := id.String()
	pasteInfo.Path = path.Join(hexId[0:2], hexId[2:]+ext)
	return
}

func (w Worker) DeletePasteAfter(id Id, duration time.Duration) {
	timer := time.NewTimer(duration)
	go func() {
		<-timer.C
		w.del <- id
	}()
}

type ByteSize int64

const (
	B ByteSize = 1 << (10 * iota)
	KB
	MB
)

func parseByteSize(str string) (ByteSize, error) {
	if !regexByteSize.MatchString(str) {
		return 0, errors.New("Could not parse size in bytes")
	}
	parts := regexByteSize.FindStringSubmatch(str)
	size, _ := strconv.ParseFloat(string(parts[1]), 64)
	switch string(parts[2]) {
	case "KB", "K":
		size *= float64(KB)
	case "MB", "M":
		size *= float64(MB)
	}
	return ByteSize(size), nil
}

func (b ByteSize) String() string {
	switch {
	case b >= MB:
		return fmt.Sprintf("%.2fMB", float64(b)/float64(MB))
	case b >= KB:
		return fmt.Sprintf("%.2fKB", float64(b)/float64(KB))
	}
	return fmt.Sprintf("%dB", b)
}

func handler(w http.ResponseWriter, r *http.Request) {
	done := make(chan struct{})
	timer := time.NewTimer(timeout)
	switch r.Method {
	case "GET":
		switch r.URL.Path {
		case "/":
			indexTemplate.Execute(w, struct{ SiteUrl, LifeTime string }{
				siteUrl, lifeTime.String()})
			return
		case "/form":
			formTemplate.Execute(w, struct{ SiteUrl string }{siteUrl})
			return
		}
		id, err := IdFromString(r.URL.Path[1:])
		if err != nil {
			http.Error(w, invalidId, http.StatusBadRequest)
			return
		}
		select {
		case <-timer.C:
			http.Error(w, timedOut, http.StatusRequestTimeout)
		case workers[id[0]].get <- GetRequest{id: id, w: w, r: r, done: done}:
			timer.Stop()
		}

	case "POST":
		r.Body = http.MaxBytesReader(w, r.Body, int64(maxSize))
		var content []byte
		ext := ""
		if value := r.FormValue("paste"); value != "" {
			content = []byte(value)
		} else if f, h, err := r.FormFile("paste"); err == nil {
			content, err = ioutil.ReadAll(f)
			ext = strings.ToLower(filepath.Ext(h.Filename))
			f.Close()
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
		} else {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		select {
		case <-timer.C:
			http.Error(w, timedOut, http.StatusRequestTimeout)
		case post <- PostRequest{content: content, ext: ext, modTime: time.Now(), w: w, r: r, done: done}:
			timer.Stop()
		}

	default:
		http.Error(w, "Unsupported action.", http.StatusBadRequest)
		return
	}
	<-done
}

func main() {
	var err error
	flag.Parse()
	if maxSize, err = parseByteSize(maxSizeStr); err != nil {
		log.Fatalf("Invalid max size '%s': %s", maxSizeStr, err)
	}
	if indexTemplate, err = template.ParseFiles(indexTmpl); err != nil {
		log.Fatalf("Could not load template %s: %s", indexTmpl, err)
	}
	if formTemplate, err = template.ParseFiles(formTmpl); err != nil {
		log.Fatalf("Could not load template %s: %s", formTmpl, err)
	}
	if err = os.MkdirAll(dataDir, 0700); err != nil {
		log.Fatalf("Could not create data directory %s: %s", dataDir, err)
	}
	if err = os.Chdir(dataDir); err != nil {
		log.Fatalf("Could not enter data directory %s: %s", dataDir, err)
	}
	log.Printf("maxSize  = %s", maxSize)
	log.Printf("siteUrl  = %s", siteUrl)
	log.Printf("listen   = %s", listen)
	log.Printf("dataDir  = %s", dataDir)
	log.Printf("lifeTime = %s", lifeTime)
	log.Printf("timeout  = %s", timeout)
	for n := range workers {
		w := &workers[n]
		w.n = byte(n)
		w.get = make(chan GetRequest)
		w.del = make(chan Id)
		go w.Work()
	}
	http.HandleFunc("/", handler)
	log.Printf("Up and running!")
	log.Fatal(http.ListenAndServe(listen, nil))
}
<commit_msg>Added ContentType support for .jpeg, .svg and .ico<commit_after>/* Copyright (c) 2014, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	indexTmpl = "index.html"
	formTmpl  = "form.html"
	idSize    = 8
	rawIdSize = idSize / 2
	randTries = 10

	// GET error messages
	invalidId     = "Invalid paste id."
	pasteNotFound = "Paste doesn't exist."
	unknownError  = "Something went terribly wrong."

	// Common error messages
	timedOut = "Request timed out."
)

var (
	siteUrl, listen, dataDir, maxSizeStr string
	lifeTime, timeout                    time.Duration
	maxSize                              ByteSize
	indexTemplate, formTemplate          *template.Template

	regexByteSize = regexp.MustCompile(`^([\d\.]+)\s*([KM]?B|[BKM])$`)
	startTime     = time.Now()
)

// Default is "text-plain; charset=utf-8"
var mimeTypes = map[string]string{
	".png":  "image/png",
	".gif":  "image/gif",
	".jpg":  "image/jpeg",
	".jpeg": "image/jpeg",
	".svg":  "image/svg+xml",
	".ico":  "image/x-icon",
	".pdf":  "application/pdf",
}

var workers [256]Worker
var post = make(chan PostRequest) // Posting is shared to balance load

type Id [rawIdSize]byte

type PasteInfo struct {
	Etag, ContentType, Path string
	ModTime                 time.Time
}

type GetRequest struct {
	w    http.ResponseWriter
	r    *http.Request
	done chan struct{}
	id   Id
}

type PostRequest struct {
	w       http.ResponseWriter
	r       *http.Request
	done    chan struct{}
	content []byte
	ext     string
	modTime time.Time
}

type Worker struct {
	n   byte // Its number, aka the first two hex chars
	get chan GetRequest
	del chan Id
	m   map[Id]PasteInfo
}

func (w Worker) recoverPaste(filePath string, fileInfo os.FileInfo, err error) error {
	if err != nil {
		return err
	}
	if fileInfo.IsDir() {
		return nil
	}
	dirParts := strings.Split(filePath, string(filepath.Separator))
	if len(dirParts) != 2 {
		return errors.New("Found invalid number of directories at " + filePath)
	}
	hexId := dirParts[0] + dirParts[1]
	ext := filepath.Ext(hexId)
	id, err := IdFromString(hexId[:len(hexId)-len(ext)])
	if err != nil {
		return err
	}
	modTime := fileInfo.ModTime()
	deathTime := modTime.Add(lifeTime)
	if deathTime.Before(startTime) {
		err := os.Remove(filePath)
		if err != nil {
			return err
		}
	}
	if modTime.After(startTime) {
		modTime = startTime
	}
	w.m[id] = id.GenPasteInfo(modTime, ext)
	w.DeletePasteAfter(id, deathTime.Sub(startTime))
	return nil
}

func (w Worker) RandomId() (Id, error) {
	var id Id
	id[0] = w.n
	for try := 0; try < randTries; try++ {
		if _, err := rand.Read(id[1:]); err != nil {
			return id, err
		}
		if _, e := w.m[id]; !e {
			return id, nil
		}
	}
	return id, fmt.Errorf("Gave up trying to find an unused random id after %d tries", randTries)
}

func (w Worker) Work() {
	dir := hex.EncodeToString([]byte{w.n})
	if stat, err := os.Stat(dir); err == nil {
		if !stat.IsDir() {
			log.Fatalf("%s/%s exists but is not a directory!", dataDir, dir)
		}
	} else {
		if err := os.Mkdir(dir, 0700); err != nil {
			log.Fatalf("Could not create data directory %s/%s: %s", dataDir, dir, err)
		}
	}
	w.m = make(map[Id]PasteInfo)
	if err := filepath.Walk(dir, w.recoverPaste); err != nil {
		log.Fatalf("Could not recover data directory %s/%s: %s", dataDir, dir, err)
	}
	for {
		var done chan struct{}
		select {
		case request := <-w.get:
			done = request.done
			pasteInfo, e := w.m[request.id]
			if !e {
				http.Error(request.w, pasteNotFound, http.StatusNotFound)
				break
			}
			if inm := request.r.Header.Get("If-None-Match"); inm != "" {
				if pasteInfo.Etag == inm || inm == "*" {
					request.w.WriteHeader(http.StatusNotModified)
					break
				}
			}
			pasteFile, err := os.Open(pasteInfo.Path)
			if err != nil {
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			request.w.Header().Set("Etag", pasteInfo.Etag)
			request.w.Header().Set("Content-Type", pasteInfo.ContentType)
			http.ServeContent(request.w, request.r, "", pasteInfo.ModTime, pasteFile)
			pasteFile.Close()

		case request := <-post:
			done = request.done
			id, err := w.RandomId()
			if err != nil {
				log.Println(err)
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			pasteInfo := id.GenPasteInfo(request.modTime, request.ext)
			pasteFile, err := os.OpenFile(pasteInfo.Path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
			if err != nil {
				log.Printf("Could not create new paste file %s: %s", pasteInfo.Path, err)
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			_, err = pasteFile.Write(request.content)
			pasteFile.Close()
			if err != nil {
				log.Printf("Could not write data into %s: %s", pasteInfo.Path, err)
				http.Error(request.w, unknownError, http.StatusInternalServerError)
				break
			}
			w.m[id] = pasteInfo
			w.DeletePasteAfter(id, lifeTime)
			fmt.Fprintf(request.w, "%s/%s\n", siteUrl, id)

		case id := <-w.del:
			pasteInfo, _ := w.m[id]
			if err := os.Remove(pasteInfo.Path); err == nil {
				delete(w.m, id)
			} else {
				log.Printf("Could not remove %s: %s", id, err)
				w.DeletePasteAfter(id, 2*time.Minute)
			}
		}
		if done != nil {
			done <- struct{}{}
		}
	}
}

func init() {
	flag.StringVar(&siteUrl, "u", "http://localhost:8080", "URL of the site")
	flag.StringVar(&listen, "l", "localhost:8080", "Host and port to listen to")
	flag.StringVar(&dataDir, "d", "data", "Directory to store all the pastes in")
	flag.DurationVar(&lifeTime, "t", 12*time.Hour, "Lifetime of the pastes")
	flag.DurationVar(&timeout, "T", 200*time.Millisecond, "Timeout of requests")
	flag.StringVar(&maxSizeStr, "s", "1M", "Maximum size of POSTs in bytes")
}

func IdFromString(hexId string) (Id, error) {
	var id Id
	if len(hexId) != idSize {
		return id, errors.New("Invalid id at " + hexId)
	}
	b, err := hex.DecodeString(hexId)
	if err != nil || len(b) != rawIdSize {
		return id, errors.New("Invalid id at " + hexId)
	}
	copy(id[:], b)
	return id, nil
}

func (id Id) String() string {
	return hex.EncodeToString(id[:])
}

func (id Id) GenPasteInfo(modTime time.Time, ext string) (pasteInfo PasteInfo) {
	pasteInfo.ModTime = modTime
	pasteInfo.Etag = fmt.Sprintf("%d-%s", pasteInfo.ModTime.Unix(), id)
	if pasteInfo.ContentType = mimeTypes[ext]; pasteInfo.ContentType == "" {
		pasteInfo.ContentType = "text-plain; charset=utf-8"
	}
	hexId := id.String()
	pasteInfo.Path = path.Join(hexId[0:2], hexId[2:]+ext)
	return
}

func (w Worker) DeletePasteAfter(id Id, duration time.Duration) {
	timer := time.NewTimer(duration)
	go func() {
		<-timer.C
		w.del <- id
	}()
}

type ByteSize int64

const (
	B ByteSize = 1 << (10 * iota)
	KB
	MB
)

func parseByteSize(str string) (ByteSize, error) {
	if !regexByteSize.MatchString(str) {
		return 0, errors.New("Could not parse size in bytes")
	}
	parts := regexByteSize.FindStringSubmatch(str)
	size, _ := strconv.ParseFloat(string(parts[1]), 64)
	switch string(parts[2]) {
	case "KB", "K":
		size *= float64(KB)
	case "MB", "M":
		size *= float64(MB)
	}
	return ByteSize(size), nil
}

func (b ByteSize) String() string {
	switch {
	case b >= MB:
		return fmt.Sprintf("%.2fMB", float64(b)/float64(MB))
	case b >= KB:
		return fmt.Sprintf("%.2fKB", float64(b)/float64(KB))
	}
	return fmt.Sprintf("%dB", b)
}

func handler(w http.ResponseWriter, r *http.Request) {
	done := make(chan struct{})
	timer := time.NewTimer(timeout)
	switch r.Method {
	case "GET":
		switch r.URL.Path {
		case "/":
			indexTemplate.Execute(w, struct{ SiteUrl, LifeTime string }{
				siteUrl, lifeTime.String()})
			return
		case "/form":
			formTemplate.Execute(w, struct{ SiteUrl string }{siteUrl})
			return
		}
		id, err := IdFromString(r.URL.Path[1:])
		if err != nil {
			http.Error(w, invalidId, http.StatusBadRequest)
			return
		}
		select {
		case <-timer.C:
			http.Error(w, timedOut, http.StatusRequestTimeout)
		case workers[id[0]].get <- GetRequest{id: id, w: w, r: r, done: done}:
			timer.Stop()
		}

	case "POST":
		r.Body = http.MaxBytesReader(w, r.Body, int64(maxSize))
		var content []byte
		ext := ""
		if value := r.FormValue("paste"); value != "" {
			content = []byte(value)
		} else if f, h, err := r.FormFile("paste"); err == nil {
			content, err = ioutil.ReadAll(f)
			ext = strings.ToLower(filepath.Ext(h.Filename))
			f.Close()
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
		} else {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		select {
		case <-timer.C:
			http.Error(w, timedOut, http.StatusRequestTimeout)
		case post <- PostRequest{content: content, ext: ext, modTime: time.Now(), w: w, r: r, done: done}:
			timer.Stop()
		}

	default:
		http.Error(w, "Unsupported action.", http.StatusBadRequest)
		return
	}
	<-done
}

func main() {
	var err error
	flag.Parse()
	if maxSize, err = parseByteSize(maxSizeStr); err != nil {
		log.Fatalf("Invalid max size '%s': %s", maxSizeStr, err)
	}
	if indexTemplate, err = template.ParseFiles(indexTmpl); err != nil {
		log.Fatalf("Could not load template %s: %s", indexTmpl, err)
	}
	if formTemplate, err = template.ParseFiles(formTmpl); err != nil {
		log.Fatalf("Could not load template %s: %s", formTmpl, err)
	}
	if err = os.MkdirAll(dataDir, 0700); err != nil {
		log.Fatalf("Could not create data directory %s: %s", dataDir, err)
	}
	if err = os.Chdir(dataDir); err != nil {
		log.Fatalf("Could not enter data directory %s: %s", dataDir, err)
	}
	log.Printf("maxSize  = %s", maxSize)
	log.Printf("siteUrl  = %s", siteUrl)
	log.Printf("listen   = %s", listen)
	log.Printf("dataDir  = %s", dataDir)
	log.Printf("lifeTime = %s", lifeTime)
	log.Printf("timeout  = %s", timeout)
	for n := range workers {
		w := &workers[n]
		w.n = byte(n)
		w.get = make(chan GetRequest)
		w.del = make(chan Id)
		go w.Work()
	}
	http.HandleFunc("/", handler)
	log.Printf("Up and running!")
	log.Fatal(http.ListenAndServe(listen, nil))
}
<|endoftext|>


<commit_before>package vision

import "regexp"

// Basic rules and regular expressions for validating strings
const (
	Email         string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
	CreditCard    string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
	UUID3         string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
	UUID4         string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
	UUID5         string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
	UUID          string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
	Alpha         string = "^[a-zA-Z]+$"
	Alphanumeric  string = "^[a-zA-Z0-9]+$"
	Numeric       string = "^[-+]?[0-9]+$"
	IntNumber     string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
	FloatNumber   string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
	USPhoneNumber string = "^(?:(?:\\+?1\\s*(?:[.-]\\s*)?)?(?:\\(\\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\\s*\\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\\s*(?:[.-]\\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\\s*(?:[.-]\\s*)?([0-9]{4})(?:\\s*(?:#|x\\.?|ext\\.?|extension)\\s*(\\d+))?$"
	HexColor      string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
	RGBColor      string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
	Base64        string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
	DataURI       string = "^data:.+\\/(.+);base64$"
	Latitude      string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
	Longitude     string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
	URL           string = `^((ftp|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`
	Semver        string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
	Trim          string = "[]-+;:{}!@#$%^&*()<>?,./|~`'\"\\ "
	tagName       string = "valid"
)

// Regex checks for validating strings
var (
	regexEmail         = regexp.MustCompile(Email)
	regexCreditCard    = regexp.MustCompile(CreditCard)
	regexUUID3         = regexp.MustCompile(UUID3)
	regexUUID4         = regexp.MustCompile(UUID4)
	regexUUID5         = regexp.MustCompile(UUID5)
	regexUUID          = regexp.MustCompile(UUID)
	regexAlpha         = regexp.MustCompile(Alpha)
	regexAlphanumeric  = regexp.MustCompile(Alphanumeric)
	regexNumeric       = regexp.MustCompile(Numeric)
	regexInt           = regexp.MustCompile(IntNumber)
	regexFloat         = regexp.MustCompile(FloatNumber)
	regexUSPhoneNumber = regexp.MustCompile(USPhoneNumber)
	regexHexColor      = regexp.MustCompile(HexColor)
	regexRGBColor      = regexp.MustCompile(RGBColor)
	regexBase64        = regexp.MustCompile(Base64)
	regexDataURI       = regexp.MustCompile(DataURI)
	regexLatitude      = regexp.MustCompile(Latitude)
	regexLongitude     = regexp.MustCompile(Longitude)
	regexURL           = regexp.MustCompile(URL)
	regexSemver        = regexp.MustCompile(Semver)
)
<commit_msg>update URL patterns<commit_after>package vision

import "regexp"

// Basic rules and regular expressions for validating strings
const (
	Email         string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
	CreditCard    string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
	UUID3         string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
	UUID4         string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
	UUID5         string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
	UUID          string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
	Alpha         string = "^[a-zA-Z]+$"
	Alphanumeric  string = "^[a-zA-Z0-9]+$"
	Numeric       string = "^[-+]?[0-9]+$"
	IntNumber     string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
	FloatNumber   string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
	USPhoneNumber string = "^(?:(?:\\+?1\\s*(?:[.-]\\s*)?)?(?:\\(\\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\\s*\\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\\s*(?:[.-]\\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\\s*(?:[.-]\\s*)?([0-9]{4})(?:\\s*(?:#|x\\.?|ext\\.?|extension)\\s*(\\d+))?$"
	HexColor      string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
	RGBColor      string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
	Base64        string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
	DataURI       string = "^data:.+\\/(.+);base64$"
	Latitude      string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
	Longitude     string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
	URL           string = `^((http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`
	Semver        string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
	Trim          string = "[]-+;:{}!@#$%^&*()<>?,./|~`'\"\\ "
	tagName       string = "valid"
)

// Regex checks for validating strings
var (
	regexEmail         = regexp.MustCompile(Email)
	regexCreditCard    = regexp.MustCompile(CreditCard)
	regexUUID3         = regexp.MustCompile(UUID3)
	regexUUID4         = regexp.MustCompile(UUID4)
	regexUUID5         = regexp.MustCompile(UUID5)
	regexUUID          = regexp.MustCompile(UUID)
	regexAlpha         = regexp.MustCompile(Alpha)
	regexAlphanumeric  = regexp.MustCompile(Alphanumeric)
	regexNumeric       = regexp.MustCompile(Numeric)
	regexInt           = regexp.MustCompile(IntNumber)
	regexFloat         = regexp.MustCompile(FloatNumber)
	regexUSPhoneNumber = regexp.MustCompile(USPhoneNumber)
	regexHexColor      = regexp.MustCompile(HexColor)
	regexRGBColor      = regexp.MustCompile(RGBColor)
	regexBase64        = regexp.MustCompile(Base64)
	regexDataURI       = regexp.MustCompile(DataURI)
	regexLatitude      = regexp.MustCompile(Latitude)
	regexLongitude     = regexp.MustCompile(Longitude)
	regexURL           = regexp.MustCompile(URL)
	regexSemver        = regexp.MustCompile(Semver)
)
<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"bytes"
	"flag"
	"go/build"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

const testdata = "testdata"

var (
	name  = flag.String("name", "", "name of the test to run")
	write = flag.Bool("write", false, "write output files")
)

func basePath(p string) string {
	if strings.HasSuffix(p, "/...") {
		p = p[:len(p)-4]
	}
	return p
}

func want(t *testing.T, p string) string {
	outPath := basePath(p) + ".out"
	outBytes, err := ioutil.ReadFile(outPath)
	if os.IsNotExist(err) {
		t.Fatalf("Output file not found: %s", outPath)
	}
	if err != nil {
		t.Fatal(err)
	}
	return string(outBytes)
}

func doTest(t *testing.T, p string) {
	if *write {
		doTestWrite(t, p)
		return
	}
	exp := want(t, p)
	doTestWant(t, p, exp, false, p)
}

func doTestWrite(t *testing.T, p string) {
	var b bytes.Buffer
	err := CheckArgsOutput([]string{p}, &b, true)
	outPath := basePath(p) + ".out"
	if err != nil {
		t.Fatalf("No error was expected in %s, but got: %v", p, err)
	}
	got := endNewline(b.String())
	if err := ioutil.WriteFile(outPath, []byte(got), 0644); err != nil {
		t.Fatal(err)
	}
}

func endNewline(s string) string {
	if strings.HasSuffix(s, "\n") {
		return s
	}
	return s + "\n"
}

func doTestWant(t *testing.T, name, exp string, wantErr bool, args ...string) {
	if *write {
		return
	}
	var b bytes.Buffer
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, &b, true)
	exp = endNewline(exp)
	if wantErr {
		if err == nil {
			t.Fatalf("Wanted error in %s, but none found.", name)
		}
		got := endNewline(err.Error())
		if exp != got {
			t.Fatalf("Error mismatch in %s:\nExpected:\n%sGot:\n%s",
				name, exp, got)
		}
		return
	}
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	got := endNewline(b.String())
	if exp != got {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, exp, got)
	}
}

func inputPaths(t *testing.T, glob string) []string {
	all, err := filepath.Glob(glob)
	if err != nil {
		t.Fatal(err)
	}
	var paths []string
	for _, p := range all {
		if strings.HasSuffix(p, ".out") {
			continue
		}
		paths = append(paths, p)
	}
	return paths
}

func chdirUndo(t *testing.T, d string) func() {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(d); err != nil {
		t.Fatal(err)
	}
	return func() {
		if err := os.Chdir(wd); err != nil {
			t.Fatal(err)
		}
	}
}

func runFileTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "files")()
	if len(paths) == 0 {
		paths = inputPaths(t, "*")
	}
	for _, p := range paths {
		doTest(t, p)
	}
}

func runLocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "local")()
	if len(paths) == 0 {
		for _, p := range inputPaths(t, "*") {
			paths = append(paths, "./"+p+"/...")
		}
		// non-recursive
		paths = append(paths, "./single")
	}
	for _, p := range paths {
		doTest(t, p)
	}
	doTestWant(t, "no-args", ".", false, "")
}

func runNonlocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "src")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	paths = inputPaths(t, "*")
	for _, p := range paths {
		doTest(t, p+"/...")
	}
	// local recursive
	doTest(t, "./nested/...")
	// non-recursive
	doTest(t, "single")
	// make sure we don't miss a package's imports
	doTestWant(t, "grab-import", "grab-import\ngrab-import/use.go:27:15: s can be def2.Fooer", false)
	defer chdirUndo(t, "nested/pkg")()
	// relative paths
	doTestWant(t, "rel-path", "nested/pkg\nsimple.go:12:17: rc can be Closer", false, "./...")
}

func TestMain(m *testing.M) {
	flag.Parse()
	if err := os.Chdir(testdata); err != nil {
		panic(err)
	}
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	build.Default.GOPATH = wd
	os.Exit(m.Run())
}

func TestCheckWarnings(t *testing.T) {
	switch {
	case *name == "":
	case strings.HasSuffix(*name, ".go"):
		runFileTests(t, *name)
		return
	case strings.HasPrefix(*name, "./"):
		runLocalTests(t, *name)
		return
	default:
		runNonlocalTests(t, *name)
		return
	}
	runFileTests(t)
	runLocalTests(t)
	runNonlocalTests(t)
}

func TestErrors(t *testing.T) {
	// non-existent Go file
	doTestWant(t, "missing.go", "open missing.go: no such file or directory", true)
	// local non-existent non-recursive
	doTestWant(t, "./missing", "no initial packages were loaded", true)
	// non-local non-existent non-recursive
	doTestWant(t, "missing", "no initial packages were loaded", true)
	// local non-existent recursive
	doTestWant(t, "./missing-rec/...", "lstat ./missing-rec: no such file or directory", true)
	// Mixing Go files and dirs
	doTestWant(t, "wrong-args", "named files must be .go files: bar", true, "foo.go", "bar")
}

func TestExtraArg(t *testing.T) {
	err := CheckArgsOutput([]string{"single", "--", "foo", "bar"}, ioutil.Discard, false)
	got := err.Error()
	want := "unwanted extra args: [foo bar]"
	if got != want {
		t.Fatalf("Error mismatch:\nExpected:\n%sGot:\n%s", want, got)
	}
}
<commit_msg>Remove test -write option<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"bytes"
	"flag"
	"go/build"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

const testdata = "testdata"

var (
	name = flag.String("name", "", "name of the test to run")
)

func basePath(p string) string {
	if strings.HasSuffix(p, "/...") {
		p = p[:len(p)-4]
	}
	return p
}

func want(t *testing.T, p string) string {
	outPath := basePath(p) + ".out"
	outBytes, err := ioutil.ReadFile(outPath)
	if os.IsNotExist(err) {
		t.Fatalf("Output file not found: %s", outPath)
	}
	if err != nil {
		t.Fatal(err)
	}
	return string(outBytes)
}

func doTest(t *testing.T, p string) {
	exp := want(t, p)
	doTestWant(t, p, exp, false, p)
}

func endNewline(s string) string {
	if strings.HasSuffix(s, "\n") {
		return s
	}
	return s + "\n"
}

func doTestWant(t *testing.T, name, exp string, wantErr bool, args ...string) {
	var b bytes.Buffer
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, &b, true)
	exp = endNewline(exp)
	if wantErr {
		if err == nil {
			t.Fatalf("Wanted error in %s, but none found.", name)
		}
		got := endNewline(err.Error())
		if exp != got {
			t.Fatalf("Error mismatch in %s:\nExpected:\n%sGot:\n%s",
				name, exp, got)
		}
		return
	}
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	got := endNewline(b.String())
	if exp != got {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, exp, got)
	}
}

func inputPaths(t *testing.T, glob string) []string {
	all, err := filepath.Glob(glob)
	if err != nil {
		t.Fatal(err)
	}
	var paths []string
	for _, p := range all {
		if strings.HasSuffix(p, ".out") {
			continue
		}
		paths = append(paths, p)
	}
	return paths
}

func chdirUndo(t *testing.T, d string) func() {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(d); err != nil {
		t.Fatal(err)
	}
	return func() {
		if err := os.Chdir(wd); err != nil {
			t.Fatal(err)
		}
	}
}

func runFileTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "files")()
	if len(paths) == 0 {
		paths = inputPaths(t, "*")
	}
	for _, p := range paths {
		doTest(t, p)
	}
}

func runLocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "local")()
	if len(paths) == 0 {
		for _, p := range inputPaths(t, "*") {
			paths = append(paths, "./"+p+"/...")
		}
		// non-recursive
		paths = append(paths, "./single")
	}
	for _, p := range paths {
		doTest(t, p)
	}
	doTestWant(t, "no-args", ".", false, "")
}

func runNonlocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "src")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	paths = inputPaths(t, "*")
	for _, p := range paths {
		doTest(t, p+"/...")
	}
	// local recursive
	doTest(t, "./nested/...")
	// non-recursive
	doTest(t, "single")
	// make sure we don't miss a package's imports
	doTestWant(t, "grab-import", "grab-import\ngrab-import/use.go:27:15: s can be def2.Fooer", false)
	defer chdirUndo(t, "nested/pkg")()
	// relative paths
	doTestWant(t, "rel-path", "nested/pkg\nsimple.go:12:17: rc can be Closer", false, "./...")
}

func TestMain(m *testing.M) {
	flag.Parse()
	if err := os.Chdir(testdata); err != nil {
		panic(err)
	}
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	build.Default.GOPATH = wd
	os.Exit(m.Run())
}

func TestCheckWarnings(t *testing.T) {
	switch {
	case *name == "":
	case strings.HasSuffix(*name, ".go"):
		runFileTests(t, *name)
		return
	case strings.HasPrefix(*name, "./"):
		runLocalTests(t, *name)
		return
	default:
		runNonlocalTests(t, *name)
		return
	}
	runFileTests(t)
	runLocalTests(t)
	runNonlocalTests(t)
}

func TestErrors(t *testing.T) {
	// non-existent Go file
	doTestWant(t, "missing.go", "open missing.go: no such file or directory", true)
	// local non-existent non-recursive
	doTestWant(t, "./missing", "no initial packages were loaded", true)
	// non-local non-existent non-recursive
	doTestWant(t, "missing", "no initial packages were loaded", true)
	// local non-existent recursive
	doTestWant(t, "./missing-rec/...", "lstat ./missing-rec: no such file or directory", true)
	// Mixing Go files and dirs
	doTestWant(t, "wrong-args", "named files must be .go files: bar", true, "foo.go", "bar")
}

func TestExtraArg(t *testing.T) {
	err := CheckArgsOutput([]string{"single", "--", "foo", "bar"}, ioutil.Discard, false)
	got := err.Error()
	want := "unwanted extra args: [foo bar]"
	if got != want {
		t.Fatalf("Error mismatch:\nExpected:\n%sGot:\n%s", want, got)
	}
}
<|endoftext|>


<commit_before>package interfaces

import (
	"errors"
	"fmt"
	"time"

	sq "github.com/gbl08ma/squirrel"
	"github.com/heetch/sqalx"
)

// Line is a Network line
type Line struct {
	ID      string
	Name    string
	Color   string
	Network *Network
}

// GetLines returns a slice with all registered lines
func GetLines(node sqalx.Node) ([]*Line, error) {
	return getLinesWithSelect(node, sdb.Select())
}

func getLinesWithSelect(node sqalx.Node, sbuilder sq.SelectBuilder) ([]*Line, error) {
	lines := []*Line{}

	tx, err := node.Beginx()
	if err != nil {
		return lines, err
	}
	defer tx.Commit() // read-only tx

	rows, err := sbuilder.Columns("id", "name", "color", "network").
		From("mline").
		RunWith(tx).Query()
	if err != nil {
		return lines, fmt.Errorf("getLinesWithSelect: %s", err)
	}
	defer rows.Close()

	var networkIDs []string
	for rows.Next() {
		var line Line
		var networkID string
		err := rows.Scan(
			&line.ID,
			&line.Name,
			&line.Color,
			&networkID)
		if err != nil {
			return lines, fmt.Errorf("getLinesWithSelect: %s", err)
		}
		if err != nil {
			return lines, fmt.Errorf("getLinesWithSelect: %s", err)
		}
		lines = append(lines, &line)
		networkIDs = append(networkIDs, networkID)
	}
	if err := rows.Err(); err != nil {
		return lines, fmt.Errorf("getLinesWithSelect: %s", err)
	}
	for i := range networkIDs {
		lines[i].Network, err = GetNetwork(tx, networkIDs[i])
		if err != nil {
			return lines, fmt.Errorf("getLinesWithSelect: %s", err)
		}
	}
	return lines, nil
}

// GetLine returns the Line with the given ID
func GetLine(node sqalx.Node, id string) (*Line, error) {
	var line Line
	tx, err := node.Beginx()
	if err != nil {
		return &line, err
	}
	defer tx.Commit() // read-only tx

	var networkID string
	err = sdb.Select("id", "name", "color", "network").
		From("mline").
		Where(sq.Eq{"id": id}).
		RunWith(tx).QueryRow().
		Scan(&line.ID, &line.Name, &line.Color, &networkID)
	if err != nil {
		return &line, errors.New("GetLine: " + err.Error())
	}
	line.Network, err = GetNetwork(tx, networkID)
	if err != nil {
		return &line, errors.New("GetLine: " + err.Error())
	}
	return &line, nil
}

// OngoingDisturbances returns a slice with all ongoing disturbances on this line
func (line *Line) OngoingDisturbances(node sqalx.Node) ([]*Disturbance, error) {
	s := sdb.Select().
		Where(sq.Eq{"mline": line.ID}).
		Where("time_end IS NULL").
		OrderBy("time_start ASC")
	return getDisturbancesWithSelect(node, s)
}

// DisturbancesBetween returns a slice with all disturbances that start or end between the specified times
func (line *Line) DisturbancesBetween(node sqalx.Node, startTime time.Time, endTime time.Time) ([]*Disturbance, error) {
	s := sdb.Select().
		Where(sq.Eq{"mline": line.ID}).
		Where(sq.Or{
			sq.Expr("time_start BETWEEN ? AND ?", startTime, endTime),
			sq.And{
				sq.Expr("time_end IS NOT NULL"),
				sq.Expr("time_end BETWEEN ? AND ?", startTime, endTime),
			},
		}).OrderBy("time_start ASC")
	return getDisturbancesWithSelect(node, s)
}

// CountDisturbancesByDay counts disturbances by day between the specified dates
func (line *Line) CountDisturbancesByDay(node sqalx.Node, start time.Time, end time.Time) ([]int, error) {
	tx, err := node.Beginx()
	if err != nil {
		return []int{}, err
	}
	defer tx.Commit() // read-only tx

	rows, err := tx.Query("SELECT curd, COUNT(id) "+
		"FROM generate_series(($2 at time zone $1)::date, ($3 at time zone $1)::date, '1 day') AS curd "+
		"LEFT OUTER JOIN line_disturbance ON "+
		"(curd BETWEEN (time_start at time zone $1)::date AND (time_end at time zone $1)::date) "+
		"AND mline = $4 "+
		"GROUP BY curd ORDER BY curd;",
		start.Location().String(), start, end, line.ID)
	if err != nil {
		return []int{}, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	defer rows.Close()

	var counts []int
	for rows.Next() {
		var date time.Time
		var count int
		err := rows.Scan(&date, &count)
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		counts = append(counts, count)
	}
	if err := rows.Err(); err != nil {
		return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	return counts, nil
}

// CountDisturbancesByHourOfDay counts disturbances by hour of day between the specified dates
func (line *Line) CountDisturbancesByHourOfDay(node sqalx.Node, start time.Time, end time.Time) ([]int, error) {
	tx, err := node.Beginx()
	if err != nil {
		return []int{}, err
	}
	defer tx.Commit() // read-only tx

	rows, err := tx.Query("SELECT date_part('hour', curd) AS hour, COUNT(id) "+
		"FROM generate_series(($2 at time zone $1)::date, ($3 at time zone $1)::date, '1 hour') AS curd "+
		"LEFT OUTER JOIN line_disturbance ON "+
		"(curd BETWEEN date_trunc('hour', time_start at time zone $1) AND date_trunc('hour', time_end at time zone $1)) "+
		"AND mline = $4 "+
		"GROUP BY hour ORDER BY hour;",
		start.Location().String(), start, end, line.ID)
	if err != nil {
		return []int{}, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	defer rows.Close()

	var counts []int
	for rows.Next() {
		var hour int
		var count int
		err := rows.Scan(&hour, &count)
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		counts = append(counts, count)
	}
	if err := rows.Err(); err != nil {
		return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	return counts, nil
}

// LastOngoingDisturbance returns the latest ongoing disturbance affecting this line
func (line *Line) LastOngoingDisturbance(node sqalx.Node) (*Disturbance, error) {
	tx, err := node.Beginx()
	if err != nil {
		return nil, err
	}
	defer tx.Commit() // read-only tx

	// are there any ongoing disturbances? get the one with most recent START time
	s := sdb.Select().
		Where(sq.Eq{"mline": line.ID}).
		Where(sq.Expr("time_start = (SELECT MAX(time_start) FROM line_disturbance WHERE mline = ? AND time_end IS NULL)", line.ID)).
		OrderBy("time_start DESC")
	disturbances, err := getDisturbancesWithSelect(tx, s)
	if err != nil {
		return nil, err
	}
	if len(disturbances) == 0 {
		return nil, errors.New("No ongoing disturbances for this line")
	}
	return disturbances[0], nil
}

// LastDisturbance returns the latest disturbance affecting this line
func (line *Line) LastDisturbance(node sqalx.Node) (*Disturbance, error) {
	tx, err := node.Beginx()
	if err != nil {
		return nil, err
	}
	defer tx.Commit() // read-only tx

	// are there any ongoing disturbances? get the one with most recent START time
	disturbance, err := line.LastOngoingDisturbance(tx)
	if err != nil {
		// no ongoing disturbances. look at past ones and get the one with the most recent END time
		s := sdb.Select().
			Where(sq.Eq{"mline": line.ID}).
			Where(sq.Expr("time_end = (SELECT MAX(time_end) FROM line_disturbance WHERE mline = ?)", line.ID)).
			OrderBy("time_end DESC")
		disturbances, err := getDisturbancesWithSelect(tx, s)
		if err != nil {
			return nil, errors.New("LastDisturbance: " + err.Error())
		}
		if len(disturbances) == 0 {
			return nil, errors.New("No disturbances for this line")
		}
		return disturbances[0], err
	}
	return disturbance, err
}

// Update adds or updates the line
func (line *Line) Update(node sqalx.Node) error {
	tx, err := node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	err = line.Network.Update(tx)
	if err != nil {
		return errors.New("AddLine: " + err.Error())
	}

	_, err = sdb.Insert("mline").
		Columns("id", "name", "color", "network").
		Values(line.ID, line.Name, line.Color, line.Network.ID).
		Suffix("ON CONFLICT (id) DO UPDATE SET name = ?, color = ?, network = ?",
			line.Name, line.Color, line.Network.ID).
		RunWith(tx).Exec()

	if err != nil {
		return errors.New("AddLine: " + err.Error())
	}
	return tx.Commit()
}

// Delete deletes the line
func (line *Line) Delete(node sqalx.Node, lineID string) error {
	tx, err := node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	_, err = sdb.Delete("mline").
		Where(sq.Eq{"id": line.ID}).RunWith(tx).Exec()
	if err != nil {
		return fmt.Errorf("RemoveLine: %s", err)
	}
	return tx.Commit()
}
<commit_msg>Fix bug related to ongoing disturbances<commit_after>package interfaces

import (
	"errors"
	"fmt"
	"time"

	sq "github.com/gbl08ma/squirrel"
	"github.com/heetch/sqalx"
)

// Line is a Network line
type Line struct {
	ID      string
	Name    string
	Color   string
	Network *Network
}

// GetLines returns a slice with all registered lines
func GetLines(node sqalx.Node) ([]*Line, error) {
	return getLinesWithSelect(node, sdb.Select())
}

func getLinesWithSelect(node sqalx.Node, sbuilder sq.SelectBuilder) ([]*Line, error) {
	lines := []*Line{}

	tx, err := node.Beginx()
	if err != nil {
		return lines, err
	}
	defer tx.Commit() // read-only tx

	rows, err := sbuilder.Columns("id", "name", "color", "network").
		From("mline").
		RunWith(tx).Query()
	if err != nil {
		return lines, fmt.Errorf("getLinesWithSelect: %s", err)
	}
	defer rows.Close()

	var networkIDs []string
	for rows.Next() {
		var line Line
		var networkID string
		err := rows.Scan(
			&line.ID,
			&line.Name,
			&line.Color,
			&networkID)
		if err != nil {
			return lines, fmt.Errorf("getLinesWithSelect: %s", err)
		}
		if err != nil {
			return lines, fmt.Errorf("getLinesWithSelect: %s", err)
		}
		lines = append(lines, &line)
		networkIDs = append(networkIDs, networkID)
	}
	if err := rows.Err(); err != nil {
		return lines, fmt.Errorf("getLinesWithSelect: %s", err)
	}
	for i := range networkIDs {
		lines[i].Network, err = GetNetwork(tx, networkIDs[i])
		if err != nil {
			return lines, fmt.Errorf("getLinesWithSelect: %s", err)
		}
	}
	return lines, nil
}

// GetLine returns the Line with the given ID
func GetLine(node sqalx.Node, id string) (*Line, error) {
	var line Line
	tx, err := node.Beginx()
	if err != nil {
		return &line, err
	}
	defer tx.Commit() // read-only tx

	var networkID string
	err = sdb.Select("id", "name", "color", "network").
		From("mline").
		Where(sq.Eq{"id": id}).
		RunWith(tx).QueryRow().
		Scan(&line.ID, &line.Name, &line.Color, &networkID)
	if err != nil {
		return &line, errors.New("GetLine: " + err.Error())
	}
	line.Network, err = GetNetwork(tx, networkID)
	if err != nil {
		return &line, errors.New("GetLine: " + err.Error())
	}
	return &line, nil
}

// OngoingDisturbances returns a slice with all ongoing disturbances on this line
func (line *Line) OngoingDisturbances(node sqalx.Node) ([]*Disturbance, error) {
	s := sdb.Select().
		Where(sq.Eq{"mline": line.ID}).
		Where("time_end IS NULL").
		OrderBy("time_start ASC")
	return getDisturbancesWithSelect(node, s)
}

// DisturbancesBetween returns a slice with all disturbances that start or end between the specified times
func (line *Line) DisturbancesBetween(node sqalx.Node, startTime time.Time, endTime time.Time) ([]*Disturbance, error) {
	s := sdb.Select().
		Where(sq.Eq{"mline": line.ID}).
		Where(sq.Or{
		sq.Expr("time_start BETWEEN ? AND ?", startTime, endTime),
		sq.And{
			sq.Expr("time_end IS NOT NULL"),
			sq.Expr("time_end BETWEEN ? AND ?", startTime, endTime),
		},
	}).OrderBy("time_start ASC")
	return getDisturbancesWithSelect(node, s)
}

// CountDisturbancesByDay counts disturbances by day between the specified dates
func (line *Line) CountDisturbancesByDay(node sqalx.Node, start time.Time, end time.Time) ([]int, error) {
	tx, err := node.Beginx()
	if err != nil {
		return []int{}, err
	}
	defer tx.Commit() // read-only tx

	rows, err := tx.Query("SELECT curd, COUNT(id) "+
		"FROM generate_series(($2 at time zone $1)::date, ($3 at time zone $1)::date, '1 day') AS curd "+
		"LEFT OUTER JOIN line_disturbance ON "+
		"(curd BETWEEN (time_start at time zone $1)::date AND (coalesce(time_end, now()) at time zone $1)::date) "+
		"AND mline = $4 "+
		"GROUP BY curd ORDER BY curd;",
		start.Location().String(), start, end, line.ID)
	if err != nil {
		return []int{}, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	defer rows.Close()

	var counts []int
	for rows.Next() {
		var date time.Time
		var count int
		err := rows.Scan(&date, &count)
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		counts = append(counts, count)
	}
	if err := rows.Err(); err != nil {
		return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	return counts, nil
}

// CountDisturbancesByHourOfDay counts disturbances by hour of day between the specified dates
func (line *Line) CountDisturbancesByHourOfDay(node sqalx.Node, start time.Time, end time.Time) ([]int, error) {
	tx, err := node.Beginx()
	if err != nil {
		return []int{}, err
	}
	defer tx.Commit() // read-only tx

	rows, err := tx.Query("SELECT date_part('hour', curd) AS hour, COUNT(id) "+
		"FROM generate_series(($2 at time zone $1)::date, ($3 at time zone $1)::date + interval '1 day' - interval '1 second', '1 hour') AS curd "+
		"LEFT OUTER JOIN line_disturbance ON "+
		"(curd BETWEEN date_trunc('hour', time_start at time zone $1) AND date_trunc('hour', coalesce(time_end, now()) at time zone $1)) "+
		"AND mline = $4 "+
		"GROUP BY hour ORDER BY hour;",
		start.Location().String(), start, end, line.ID)
	if err != nil {
		return []int{}, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	defer rows.Close()

	var counts []int
	for rows.Next() {
		var hour int
		var count int
		err := rows.Scan(&hour, &count)
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		if err != nil {
			return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
		}
		counts = append(counts, count)
	}
	if err := rows.Err(); err != nil {
		return counts, fmt.Errorf("CountDisturbancesByDay: %s", err)
	}
	return counts, nil
}

// LastOngoingDisturbance returns the latest ongoing disturbance affecting this line
func (line *Line) LastOngoingDisturbance(node sqalx.Node) (*Disturbance, error) {
	tx, err := node.Beginx()
	if err != nil {
		return nil, err
	}
	defer tx.Commit() // read-only tx

	// are there any ongoing disturbances? get the one with most recent START time
	s := sdb.Select().
		Where(sq.Eq{"mline": line.ID}).
		Where(sq.Expr("time_start = (SELECT MAX(time_start) FROM line_disturbance WHERE mline = ? AND time_end IS NULL)", line.ID)).
		OrderBy("time_start DESC")
	disturbances, err := getDisturbancesWithSelect(tx, s)
	if err != nil {
		return nil, err
	}
	if len(disturbances) == 0 {
		return nil, errors.New("No ongoing disturbances for this line")
	}
	return disturbances[0], nil
}

// LastDisturbance returns the latest disturbance affecting this line
func (line *Line) LastDisturbance(node sqalx.Node) (*Disturbance, error) {
	tx, err := node.Beginx()
	if err != nil {
		return nil, err
	}
	defer tx.Commit() // read-only tx

	// are there any ongoing disturbances? get the one with most recent START time
	disturbance, err := line.LastOngoingDisturbance(tx)
	if err != nil {
		// no ongoing disturbances. look at past ones and get the one with the most recent END time
		s := sdb.Select().
			Where(sq.Eq{"mline": line.ID}).
			Where(sq.Expr("time_end = (SELECT MAX(time_end) FROM line_disturbance WHERE mline = ?)", line.ID)).
			OrderBy("time_end DESC")
		disturbances, err := getDisturbancesWithSelect(tx, s)
		if err != nil {
			return nil, errors.New("LastDisturbance: " + err.Error())
		}
		if len(disturbances) == 0 {
			return nil, errors.New("No disturbances for this line")
		}
		return disturbances[0], err
	}
	return disturbance, err
}

// Update adds or updates the line
func (line *Line) Update(node sqalx.Node) error {
	tx, err := node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	err = line.Network.Update(tx)
	if err != nil {
		return errors.New("AddLine: " + err.Error())
	}

	_, err = sdb.Insert("mline").
		Columns("id", "name", "color", "network").
		Values(line.ID, line.Name, line.Color, line.Network.ID).
		Suffix("ON CONFLICT (id) DO UPDATE SET name = ?, color = ?, network = ?",
		line.Name, line.Color, line.Network.ID).
		RunWith(tx).Exec()

	if err != nil {
		return errors.New("AddLine: " + err.Error())
	}
	return tx.Commit()
}

// Delete deletes the line
func (line *Line) Delete(node sqalx.Node, lineID string) error {
	tx, err := node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	_, err = sdb.Delete("mline").
		Where(sq.Eq{"id": line.ID}).RunWith(tx).Exec()
	if err != nil {
		return fmt.Errorf("RemoveLine: %s", err)
	}
	return tx.Commit()
}
<|endoftext|>


<commit_before>package cmd

import (
	"context"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"time"
)

// RC represents the .envrc or .env file
type RC struct {
	path      string
	allowPath string
	times     FileTimes
	config    *Config
}

// FindRC looks for ".envrc" and ".env" files up in the file hierarchy.
func FindRC(wd string, config *Config) (*RC, error) {
	rcPath := findEnvUp(wd, config.LoadDotenv)
	if rcPath == "" {
		return nil, nil
	}

	return RCFromPath(rcPath, config)
}

// RCFromPath inits the RC from a given path
func RCFromPath(path string, config *Config) (*RC, error) {
	hash, err := fileHash(path)
	if err != nil {
		return nil, err
	}

	allowPath := filepath.Join(config.AllowDir(), hash)

	times := NewFileTimes()

	err = times.Update(path)
	if err != nil {
		return nil, err
	}

	err = times.Update(allowPath)
	if err != nil {
		return nil, err
	}

	return &RC{path, allowPath, times, config}, nil
}

// RCFromEnv inits the RC from the environment
func RCFromEnv(path, marshalledTimes string, config *Config) *RC {
	times := NewFileTimes()
	err := times.Unmarshal(marshalledTimes)
	if err != nil {
		return nil
	}
	return &RC{path, "", times, config}
}

// Allow grants the RC as allowed to load
func (rc *RC) Allow() (err error) {
	if rc.allowPath == "" {
		return fmt.Errorf("cannot allow empty path")
	}
	if err = os.MkdirAll(filepath.Dir(rc.allowPath), 0755); err != nil {
		return
	}
	if err = allow(rc.path, rc.allowPath); err != nil {
		return
	}
	err = rc.times.Update(rc.allowPath)
	return
}

// Deny revokes the permission of the RC file to load
func (rc *RC) Deny() error {
	return os.Remove(rc.allowPath)
}

// Allowed checks if the RC file has been granted loading
func (rc *RC) Allowed() bool {
	// happy path is if this envrc has been explicitly allowed, O(1)ish common case
	_, err := os.Stat(rc.allowPath)

	if err == nil {
		return true
	}

	// when whitelisting we want to be (path) absolutely sure we've not been duped with a symlink
	path, err := filepath.Abs(rc.path)
	// seems unlikely that we'd hit this, but have to handle it
	if err != nil {
		return false
	}

	// exact whitelists are O(1)ish to check, so look there first
	if rc.config.WhitelistExact[path] {
		return true
	}

	// finally we check if any of our whitelist prefixes match
	for _, prefix := range rc.config.WhitelistPrefix {
		if strings.HasPrefix(path, prefix) {
			return true
		}
	}

	return false
}

// Path returns the path to the RC file
func (rc *RC) Path() string {
	return rc.path
}

// Touch updates the mtime of the RC file. This is mainly used to trigger a
// reload in direnv.
func (rc *RC) Touch() error {
	return touch(rc.path)
}

const notAllowed = "%s is blocked. Run `direnv allow` to approve its content"

// Load evaluates the RC file and returns the new Env or error.
//
// This functions is key to the implementation of direnv.
func (rc *RC) Load(previousEnv Env) (newEnv Env, err error) {
	config := rc.config
	wd := config.WorkDir
	direnv := config.SelfPath
	newEnv = previousEnv.Copy()
	newEnv[DIRENV_WATCHES] = rc.times.Marshal()
	defer func() {
		// Record directory changes even if load is disallowed or fails
		newEnv[DIRENV_DIR] = "-" + filepath.Dir(rc.path)
		newEnv[DIRENV_FILE] = rc.path
		newEnv[DIRENV_DIFF] = previousEnv.Diff(newEnv).Serialize()
	}()

	if !rc.Allowed() {
		err = fmt.Errorf(notAllowed, rc.Path())
		return
	}

	// check what type of RC we're processing
	// use different exec method for each
	fn := "source_env"
	if filepath.Base(rc.path) == ".env" {
		fn = "dotenv"
	}

	prelude := ""
	if config.StrictEnv {
		prelude = "set -euo pipefail && "
	}

	arg := fmt.Sprintf(
		`%seval "$("%s" stdlib)" && __main__ %s "%s"`,
		prelude,
		direnv,
		fn,
		rc.Path(),
	)

	// Allow RC loads to be canceled with SIGINT
	ctx, cancel := context.WithCancel(context.Background())
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		<-c
		cancel()
	}()

	// G204: Subprocess launched with function call as argument or cmd arguments
	// #nosec
	cmd := exec.CommandContext(ctx, config.BashPath, "--noprofile", "--norc", "-c", arg)
	cmd.Dir = wd
	cmd.Env = newEnv.ToGoEnv()
	cmd.Stderr = os.Stderr

	if config.DisableStdin {
		cmd.Stdin, err = os.Open(os.DevNull)
		if err != nil {
			return
		}
	} else {
		cmd.Stdin = os.Stdin
	}

	if out, err := cmd.Output(); err == nil && len(out) > 0 {
		var newEnv2 Env
		newEnv2, err = LoadEnvJSON(out)
		if err == nil {
			newEnv = newEnv2
		}
	}

	return
}

/// Utils

func eachDir(path string) (paths []string) {
	path, err := filepath.Abs(path)
	if err != nil {
		return
	}

	paths = []string{path}

	if path == "/" {
		return
	}

	for i := len(path) - 1; i >= 0; i-- {
		if path[i] == os.PathSeparator {
			path = path[:i]
			if path == "" {
				path = "/"
			}
			paths = append(paths, path)
		}
	}

	return
}

func fileExists(path string) bool {
	// Some broken filesystems like SSHFS return file information on stat() but
	// then cannot open the file. So we use os.Open.
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	f.Close()

	// Next, check that the file is a regular file.
	fi, err := os.Stat(path)
	if err != nil {
		return false
	}

	return fi.Mode().IsRegular()
}

func fileHash(path string) (hash string, err error) {
	if path, err = filepath.Abs(path); err != nil {
		return
	}

	fd, err := os.Open(path)
	if err != nil {
		return
	}

	hasher := sha256.New()
	_, err = hasher.Write([]byte(path + "\n"))
	if err != nil {
		return
	}
	if _, err = io.Copy(hasher, fd); err != nil {
		return
	}

	return fmt.Sprintf("%x", hasher.Sum(nil)), nil
}

// Creates a file

func touch(path string) (err error) {
	t := time.Now()
	return os.Chtimes(path, t, t)
}

func allow(path string, allowPath string) (err error) {
	// G306: Expect WriteFile permissions to be 0600 or less
	// #nosec
	return ioutil.WriteFile(allowPath, []byte(path+"\n"), 0644)
}

func findEnvUp(searchDir string, loadDotenv bool) (path string) {
	if loadDotenv {
		return findUp(searchDir, ".envrc", ".env")
	}
	return findUp(searchDir, ".envrc")
}

func findUp(searchDir string, fileNames ...string) (path string) {
	for _, dir := range eachDir(searchDir) {
		for _, fileName := range fileNames {
			path := filepath.Join(dir, fileName)
			if fileExists(path) {
				return path
			}
		}
	}
	return ""
}
<commit_msg>rc: install interrupt handler earlier<commit_after>package cmd

import (
	"context"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"time"
)

// RC represents the .envrc or .env file
type RC struct {
	path      string
	allowPath string
	times     FileTimes
	config    *Config
}

// FindRC looks for ".envrc" and ".env" files up in the file hierarchy.
func FindRC(wd string, config *Config) (*RC, error) {
	rcPath := findEnvUp(wd, config.LoadDotenv)
	if rcPath == "" {
		return nil, nil
	}

	return RCFromPath(rcPath, config)
}

// RCFromPath inits the RC from a given path
func RCFromPath(path string, config *Config) (*RC, error) {
	hash, err := fileHash(path)
	if err != nil {
		return nil, err
	}

	allowPath := filepath.Join(config.AllowDir(), hash)

	times := NewFileTimes()

	err = times.Update(path)
	if err != nil {
		return nil, err
	}

	err = times.Update(allowPath)
	if err != nil {
		return nil, err
	}

	return &RC{path, allowPath, times, config}, nil
}

// RCFromEnv inits the RC from the environment
func RCFromEnv(path, marshalledTimes string, config *Config) *RC {
	times := NewFileTimes()
	err := times.Unmarshal(marshalledTimes)
	if err != nil {
		return nil
	}
	return &RC{path, "", times, config}
}

// Allow grants the RC as allowed to load
func (rc *RC) Allow() (err error) {
	if rc.allowPath == "" {
		return fmt.Errorf("cannot allow empty path")
	}
	if err = os.MkdirAll(filepath.Dir(rc.allowPath), 0755); err != nil {
		return
	}
	if err = allow(rc.path, rc.allowPath); err != nil {
		return
	}
	err = rc.times.Update(rc.allowPath)
	return
}

// Deny revokes the permission of the RC file to load
func (rc *RC) Deny() error {
	return os.Remove(rc.allowPath)
}

// Allowed checks if the RC file has been granted loading
func (rc *RC) Allowed() bool {
	// happy path is if this envrc has been explicitly allowed, O(1)ish common case
	_, err := os.Stat(rc.allowPath)

	if err == nil {
		return true
	}

	// when whitelisting we want to be (path) absolutely sure we've not been duped with a symlink
	path, err := filepath.Abs(rc.path)
	// seems unlikely that we'd hit this, but have to handle it
	if err != nil {
		return false
	}

	// exact whitelists are O(1)ish to check, so look there first
	if rc.config.WhitelistExact[path] {
		return true
	}

	// finally we check if any of our whitelist prefixes match
	for _, prefix := range rc.config.WhitelistPrefix {
		if strings.HasPrefix(path, prefix) {
			return true
		}
	}

	return false
}

// Path returns the path to the RC file
func (rc *RC) Path() string {
	return rc.path
}

// Touch updates the mtime of the RC file. This is mainly used to trigger a
// reload in direnv.
func (rc *RC) Touch() error {
	return touch(rc.path)
}

const notAllowed = "%s is blocked. Run `direnv allow` to approve its content"

// Load evaluates the RC file and returns the new Env or error.
//
// This functions is key to the implementation of direnv.
func (rc *RC) Load(previousEnv Env) (newEnv Env, err error) {
	config := rc.config
	wd := config.WorkDir
	direnv := config.SelfPath
	newEnv = previousEnv.Copy()
	newEnv[DIRENV_WATCHES] = rc.times.Marshal()
	defer func() {
		// Record directory changes even if load is disallowed or fails
		newEnv[DIRENV_DIR] = "-" + filepath.Dir(rc.path)
		newEnv[DIRENV_FILE] = rc.path
		newEnv[DIRENV_DIFF] = previousEnv.Diff(newEnv).Serialize()
	}()

	if !rc.Allowed() {
		err = fmt.Errorf(notAllowed, rc.Path())
		return
	}

	// Allow RC loads to be canceled with SIGINT
	ctx, cancel := context.WithCancel(context.Background())
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		<-c
		cancel()
	}()

	// check what type of RC we're processing
	// use different exec method for each
	fn := "source_env"
	if filepath.Base(rc.path) == ".env" {
		fn = "dotenv"
	}

	prelude := ""
	if config.StrictEnv {
		prelude = "set -euo pipefail && "
	}

	arg := fmt.Sprintf(
		`%seval "$("%s" stdlib)" && __main__ %s "%s"`,
		prelude,
		direnv,
		fn,
		rc.Path(),
	)

	// G204: Subprocess launched with function call as argument or cmd arguments
	// #nosec
	cmd := exec.CommandContext(ctx, config.BashPath, "--noprofile", "--norc", "-c", arg)
	cmd.Dir = wd
	cmd.Env = newEnv.ToGoEnv()
	cmd.Stderr = os.Stderr

	if config.DisableStdin {
		cmd.Stdin, err = os.Open(os.DevNull)
		if err != nil {
			return
		}
	} else {
		cmd.Stdin = os.Stdin
	}

	if out, err := cmd.Output(); err == nil && len(out) > 0 {
		var newEnv2 Env
		newEnv2, err = LoadEnvJSON(out)
		if err == nil {
			newEnv = newEnv2
		}
	}

	return
}

/// Utils

func eachDir(path string) (paths []string) {
	path, err := filepath.Abs(path)
	if err != nil {
		return
	}

	paths = []string{path}

	if path == "/" {
		return
	}

	for i := len(path) - 1; i >= 0; i-- {
		if path[i] == os.PathSeparator {
			path = path[:i]
			if path == "" {
				path = "/"
			}
			paths = append(paths, path)
		}
	}

	return
}

func fileExists(path string) bool {
	// Some broken filesystems like SSHFS return file information on stat() but
	// then cannot open the file. So we use os.Open.
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	f.Close()

	// Next, check that the file is a regular file.
	fi, err := os.Stat(path)
	if err != nil {
		return false
	}

	return fi.Mode().IsRegular()
}

func fileHash(path string) (hash string, err error) {
	if path, err = filepath.Abs(path); err != nil {
		return
	}

	fd, err := os.Open(path)
	if err != nil {
		return
	}

	hasher := sha256.New()
	_, err = hasher.Write([]byte(path + "\n"))
	if err != nil {
		return
	}
	if _, err = io.Copy(hasher, fd); err != nil {
		return
	}

	return fmt.Sprintf("%x", hasher.Sum(nil)), nil
}

// Creates a file

func touch(path string) (err error) {
	t := time.Now()
	return os.Chtimes(path, t, t)
}

func allow(path string, allowPath string) (err error) {
	// G306: Expect WriteFile permissions to be 0600 or less
	// #nosec
	return ioutil.WriteFile(allowPath, []byte(path+"\n"), 0644)
}

func findEnvUp(searchDir string, loadDotenv bool) (path string) {
	if loadDotenv {
		return findUp(searchDir, ".envrc", ".env")
	}
	return findUp(searchDir, ".envrc")
}

func findUp(searchDir string, fileNames ...string) (path string) {
	for _, dir := range eachDir(searchDir) {
		for _, fileName := range fileNames {
			path := filepath.Join(dir, fileName)
			if fileExists(path) {
				return path
			}
		}
	}
	return ""
}
<|endoftext|>


<commit_before>package internal

import (
	"errors"
	"strings"
)

// FkMode represents the different foreign key naming modes.
type FkMode int

const (
	// FkModeSmart is the default FkMode.
	//
	// When there are no naming conflicts, FkModeSmart behaves the same
	// FkModeParent, otherwise it behaves the same as FkModeField.
	FkModeSmart FkMode = iota

	// FkModeParent causes a foreign key field to be named in the form of
	// "<type>.<ParentName>".
	//
	// For example, if you have an `authors` and `books` tables, then the
	// foreign key func will be Book.Author.
	FkModeParent

	// FkModeField causes a foreign key field to be named in the form of
	// "<type>.<ParentName>By<Field>".
	//
	// For example, if you have an `authors` and `books` tables, then the
	// foreign key func will be Book.AuthorByAuthorID.
	FkModeField

	// FkModeKey causes a foreign key field to be named in the form of
	// "<type>.<ParentName>By<ForeignKeyName>".
	//
	// For example, if you have an `authors` and `books` tables with a foreign
	// key name of 'fk_123', then the foreign key func will be
	// Book.AuthorByFk123.
	FkModeKey
)

// UnmarshalText unmarshals FkMode from text.
func (f *FkMode) UnmarshalText(text []byte) error {
	switch strings.ToLower(string(text)) {
	case "smart", "default":
		*f = FkModeSmart
	case "parent":
		*f = FkModeParent
	case "field":
		*f = FkModeField
	case "key":
		*f = FkModeKey

	default:
		return errors.New("invalid FkMode")
	}

	return nil
}

// String satisfies the Stringer interface.
func (f FkMode) String() string {
	switch f {
	case FkModeSmart:
		return "smart"
	case FkModeParent:
		return "parent"
	case FkModeField:
		return "field"
	case FkModeKey:
		return "key"
	}

	return "unknown"
}

// fkName returns the name for the foreign key.
func fkName(mode FkMode, fkMap map[string]*ForeignKey, fk *ForeignKey) string {
	switch mode {
	case FkModeParent:
		return fk.RefType.Name
	case FkModeField:
		return fk.RefType.Name + "By" + fk.Field.Name
	case FkModeKey:
		return fk.RefType.Name + "By" + SnakeToCamel(fk.ForeignKey.ForeignKeyName)
	}

	// mode is FkModeSmart
	// look for other foreign keys with same parent name
	for _, f := range fkMap {
		if f != fk && fk.RefType.Name == f.RefType.Name {
			return fkName(FkModeField, fkMap, fk)
		}
	}

	return fkName(FkModeParent, fkMap, fk)
}

// ForeignKeyName returns the foreign key name for the passed type.
func (a *ArgType) ForeignKeyName(fkMap map[string]*ForeignKey, fk *ForeignKey) string {
	return fkName(*a.ForeignKeyMode, fkMap, fk)
}
<commit_msg>Fixing logic issue with recently introduced --fk-mode option<commit_after>package internal

import (
	"errors"
	"strings"
)

// FkMode represents the different foreign key naming modes.
type FkMode int

const (
	// FkModeSmart is the default FkMode.
	//
	// When there are no naming conflicts, FkModeSmart behaves the same
	// FkModeParent, otherwise it behaves the same as FkModeField.
	FkModeSmart FkMode = iota

	// FkModeParent causes a foreign key field to be named in the form of
	// "<type>.<ParentName>".
	//
	// For example, if you have an `authors` and `books` tables, then the
	// foreign key func will be Book.Author.
	FkModeParent

	// FkModeField causes a foreign key field to be named in the form of
	// "<type>.<ParentName>By<Field>".
	//
	// For example, if you have an `authors` and `books` tables, then the
	// foreign key func will be Book.AuthorByAuthorID.
	FkModeField

	// FkModeKey causes a foreign key field to be named in the form of
	// "<type>.<ParentName>By<ForeignKeyName>".
	//
	// For example, if you have an `authors` and `books` tables with a foreign
	// key name of 'fk_123', then the foreign key func will be
	// Book.AuthorByFk123.
	FkModeKey
)

// UnmarshalText unmarshals FkMode from text.
func (f *FkMode) UnmarshalText(text []byte) error {
	switch strings.ToLower(string(text)) {
	case "smart", "default":
		*f = FkModeSmart
	case "parent":
		*f = FkModeParent
	case "field":
		*f = FkModeField
	case "key":
		*f = FkModeKey

	default:
		return errors.New("invalid FkMode")
	}

	return nil
}

// String satisfies the Stringer interface.
func (f FkMode) String() string {
	switch f {
	case FkModeSmart:
		return "smart"
	case FkModeParent:
		return "parent"
	case FkModeField:
		return "field"
	case FkModeKey:
		return "key"
	}

	return "unknown"
}

// fkName returns the name for the foreign key.
func fkName(mode FkMode, fkMap map[string]*ForeignKey, fk *ForeignKey) string {
	switch mode {
	case FkModeParent:
		return fk.RefType.Name
	case FkModeField:
		return fk.RefType.Name + "By" + fk.Field.Name
	case FkModeKey:
		return fk.RefType.Name + "By" + SnakeToCamel(fk.ForeignKey.ForeignKeyName)
	}

	// mode is FkModeSmart
	// inspect all foreign keys and use FkModeField if conflict found
	for _, f := range fkMap {
		if fk != f && fk.Type.Name == f.Type.Name && fk.RefType.Name == f.RefType.Name {
			return fkName(FkModeField, fkMap, fk)
		}
	}

	// no conflict, so use FkModeParent
	return fkName(FkModeParent, fkMap, fk)
}

// ForeignKeyName returns the foreign key name for the passed type.
func (a *ArgType) ForeignKeyName(fkMap map[string]*ForeignKey, fk *ForeignKey) string {
	return fkName(*a.ForeignKeyMode, fkMap, fk)
}
<|endoftext|>


<commit_before>/*
 * gomacro - A Go intepreter with Lisp-like macros
 *
 * Copyright (C) 2017 Massimiliano Ghilardi
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * main.go
 *
 *  Created on: Feb 13, 2017
 *      Author: Massimiliano Ghilardi
 */

package interpreter

import (
	"errors"
	"fmt"
	"io/ioutil"
	"sort"
	"strings"

	mp "github.com/cosmos72/gomacro/parser"
)

type Cmd struct {
	*Env
}

func (cmd *Cmd) Init() {
	cmd.Env = New()

	cmd.ParserMode = mp.Trace & 0 // | mp.TraceMacro
	cmd.Options = OptTrapPanic    // | OptShowAfterMacroExpansion // | OptDebugMacroExpand // |  OptDebugQuasiquote  // | OptShowEvalDuration // | OptShowAfterParse
}

func (cmd *Cmd) Main(args []string) (err error) {
	if cmd.Env == nil {
		cmd.Init()
	}
	env := cmd.Env

	if len(args) == 0 {
		env.ReplStdin()
		return nil
	}

	switch args[0] {
	case "-h":
		return cmd.Usage()
	case "-f":
		return cmd.EvalFiles(args[1:]...)
	case "-d":
		return cmd.EvalDirs(args[1:]...)
	case ".":
		return cmd.EvalDirs(".")
	default:
		return cmd.EvalString(strings.Join(args, " "))
	}
	return nil
}

func (cmd *Cmd) Usage() error {
	fmt.Print(`usage: gomacro [expression]
       gomacro -f [filenames...]
       gomacro -d [dirnames...]
       gomacro .
`)
	return nil
}

func (cmd *Cmd) EvalString(str string) (err error) {
	defer func() {
		if rec := recover(); rec != nil {
			switch rec := rec.(type) {
			case error:
				err = rec
			default:
				err = errors.New(fmt.Sprint(rec))
			}
		}
	}()

	cmd.Env.ParseEvalPrint(str, nil)
	return nil
}

func (cmd *Cmd) EvalFiles(filenames ...string) (err error) {
	for _, filename := range filenames {
		bytes, err := ioutil.ReadFile(filename)
		if err != nil {
			return err
		}
		str := string(bytes)
		err = cmd.EvalString(str)
		if err != nil {
			return err
		}
	}
	return nil
}

func (cmd *Cmd) EvalDirs(dirnames ...string) error {
	for _, dirname := range dirnames {
		files, err := ioutil.ReadDir(dirname)
		if err != nil {
			return err
		}
		filenames := make([]string, 0, len(files))
		for _, file := range files {
			filename := file.Name()
			l := len(filename)
			if l > 3 && filename[l-3:] == ".go" {
				filenames = append(filenames, filename)
			}
		}
		sort.Strings(filenames)

		for _, filename := range filenames {
			err := cmd.EvalFiles(filename)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<commit_msg>interpreter/cmd.go: modified EvalDirs() to only pick files named "*.go" or "*.gomacro"<commit_after>/*
 * gomacro - A Go intepreter with Lisp-like macros
 *
 * Copyright (C) 2017 Massimiliano Ghilardi
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * main.go
 *
 *  Created on: Feb 13, 2017
 *      Author: Massimiliano Ghilardi
 */

package interpreter

import (
	"errors"
	"fmt"
	"io/ioutil"
	"sort"
	"strings"

	mp "github.com/cosmos72/gomacro/parser"
)

type Cmd struct {
	*Env
}

func (cmd *Cmd) Init() {
	cmd.Env = New()

	cmd.ParserMode = mp.Trace & 0 // | mp.TraceMacro
	cmd.Options = OptTrapPanic    // | OptShowAfterMacroExpansion // | OptDebugMacroExpand // |  OptDebugQuasiquote  // | OptShowEvalDuration // | OptShowAfterParse
}

func (cmd *Cmd) Main(args []string) (err error) {
	if cmd.Env == nil {
		cmd.Init()
	}
	env := cmd.Env

	if len(args) == 0 {
		env.ReplStdin()
		return nil
	}

	switch args[0] {
	case "-h":
		return cmd.Usage()
	case "-f":
		return cmd.EvalFiles(args[1:]...)
	case "-d":
		return cmd.EvalDirs(args[1:]...)
	case ".":
		return cmd.EvalDirs(".")
	default:
		return cmd.EvalString(strings.Join(args, " "))
	}
	return nil
}

func (cmd *Cmd) Usage() error {
	fmt.Print(`usage: gomacro [expression]
       gomacro -f [filenames...]
       gomacro -d [dirnames...]
       gomacro .
`)
	return nil
}

func (cmd *Cmd) EvalString(str string) (err error) {
	defer func() {
		if rec := recover(); rec != nil {
			switch rec := rec.(type) {
			case error:
				err = rec
			default:
				err = errors.New(fmt.Sprint(rec))
			}
		}
	}()

	cmd.Env.ParseEvalPrint(str, nil)
	return nil
}

func (cmd *Cmd) EvalFiles(filenames ...string) (err error) {
	for _, filename := range filenames {
		bytes, err := ioutil.ReadFile(filename)
		if err != nil {
			return err
		}
		str := string(bytes)
		err = cmd.EvalString(str)
		if err != nil {
			return err
		}
	}
	return nil
}

func (cmd *Cmd) EvalDirs(dirnames ...string) error {
	for _, dirname := range dirnames {
		files, err := ioutil.ReadDir(dirname)
		if err != nil {
			return err
		}
		filenames := make([]string, 0, len(files))
		for _, file := range files {
			filename := file.Name()
			l := len(filename)
			if l > 3 && filename[l-3:] == ".go" || l > 8 && filename[l-8] == ".gomacro" {
				filenames = append(filenames, filename)
			}
		}
		sort.Strings(filenames)

		for _, filename := range filenames {
			err := cmd.EvalFiles(filename)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>