<commit_before>package rmq

import (
	"bytes"
	"fmt"
	"sort"
)

type ConnectionStat struct {
	active       bool
	unackedCount int
	consumers    []string
}

func (stat ConnectionStat) String() string {
	return fmt.Sprintf("[unacked:%d consumers:%d]",
		stat.unackedCount,
		len(stat.consumers),
	)
}

type ConnectionStats map[string]ConnectionStat

type QueueStat struct {
	ReadyCount      int `json:"ready"`
	RejectedCount   int `json:"rejected"`
	connectionStats ConnectionStats
}

func NewQueueStat(readyCount, rejectedCount int) QueueStat {
	return QueueStat{
		ReadyCount:      readyCount,
		RejectedCount:   rejectedCount,
		connectionStats: ConnectionStats{},
	}
}

func (stat QueueStat) String() string {
	return fmt.Sprintf("[ready:%d rejected:%d conn:%s",
		stat.ReadyCount,
		stat.RejectedCount,
		stat.connectionStats,
	)
}

func (stat QueueStat) unackedCount() int {
	unacked := 0
	for _, connectionStat := range stat.connectionStats {
		unacked += connectionStat.unackedCount
	}
	return unacked
}

func (stat QueueStat) ConsumerCount() int {
	consumer := 0
	for _, connectionStat := range stat.connectionStats {
		consumer += len(connectionStat.consumers)
	}
	return consumer
}

type QueueStats map[string]QueueStat

type Stats struct {
	QueueStats       QueueStats      `json:"queues"`
	otherConnections map[string]bool // non consuming connections, active or not
}

func NewStats() Stats {
	return Stats{
		QueueStats:       QueueStats{},
		otherConnections: map[string]bool{},
	}
}

func CollectStats(queueList []string, mainConnection *redisConnection) Stats {
	stats := NewStats()
	for _, queueName := range queueList {
		queue := mainConnection.openQueue(queueName)
		stats.QueueStats[queueName] = NewQueueStat(queue.ReadyCount(), queue.RejectedCount())
	}

	connectionNames := mainConnection.GetConnections()
	for _, connectionName := range connectionNames {
		connection := mainConnection.hijackConnection(connectionName)
		connectionActive := connection.Check()

		queueNames := connection.GetConsumingQueues()
		if len(queueNames) == 0 {
			stats.otherConnections[connectionName] = connectionActive
			continue
		}

		for _, queueName := range queueNames {
			queue := connection.openQueue(queueName)
			consumers := queue.GetConsumers()
			openQueueStat, ok := stats.QueueStats[queueName]
			if !ok {
				continue
			}
			openQueueStat.connectionStats[connectionName] = ConnectionStat{
				active:       connectionActive,
				unackedCount: queue.UnackedCount(),
				consumers:    consumers,
			}
		}
	}

	return stats
}

func (stats Stats) String() string {
	var buffer bytes.Buffer

	for queueName, queueStat := range stats.QueueStats {
		buffer.WriteString(fmt.Sprintf("    queue:%s ready:%d rejected:%d unacked:%d consumers:%d\n",
			queueName, queueStat.ReadyCount, queueStat.RejectedCount, queueStat.unackedCount(), queueStat.ConsumerCount(),
		))

		for connectionName, connectionStat := range queueStat.connectionStats {
			buffer.WriteString(fmt.Sprintf("        connection:%s unacked:%d consumers:%d active:%t\n",
				connectionName, connectionStat.unackedCount, len(connectionStat.consumers), connectionStat.active,
			))
		}
	}

	for connectionName, active := range stats.otherConnections {
		buffer.WriteString(fmt.Sprintf("    connection:%s active:%t\n",
			connectionName, active,
		))
	}

	return buffer.String()
}

func (stats Stats) GetHtml(layout, refresh string) string {
	buffer := bytes.NewBufferString("<html>")

	if refresh != "" {
		buffer.WriteString(fmt.Sprintf(`<head><meta http-equiv="refresh" content="%s">`, refresh))
	}

	buffer.WriteString(`<body><table style="font-family:monospace">`)
	buffer.WriteString(`<tr><td>` +
		`queue</td><td></td><td>` +
		`ready</td><td></td><td>` +
		`rejected</td><td></td><td>` +
		`</td><td></td><td>` +
		`connections</td><td></td><td>` +
		`unacked</td><td></td><td>` +
		`consumers</td><td></td></tr>`,
	)

	for _, queueName := range stats.sortedQueueNames() {
		queueStat := stats.QueueStats[queueName]
		connectionNames := queueStat.connectionStats.sortedNames()
		buffer.WriteString(fmt.Sprintf(`<tr><td>`+
			`%s</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%s</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%d</td><td></td></tr>`,
			queueName, queueStat.ReadyCount, queueStat.RejectedCount, "", len(connectionNames), queueStat.unackedCount(), queueStat.ConsumerCount(),
		))

		if layout != "condensed" {
			for _, connectionName := range connectionNames {
				connectionStat := queueStat.connectionStats[connectionName]
				buffer.WriteString(fmt.Sprintf(`<tr style="color:lightgrey"><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%d</td><td></td><td>`+
					`%d</td><td></td></tr>`,
					"", "", "", ActiveSign(connectionStat.active), connectionName, connectionStat.unackedCount, len(connectionStat.consumers),
				))
			}
		}
	}

	if layout != "condensed" {
		buffer.WriteString(`<tr><td>-----</td></tr>`)
		for _, connectionName := range stats.sortedConnectionNames() {
			active := stats.otherConnections[connectionName]
			buffer.WriteString(fmt.Sprintf(`<tr style="color:lightgrey"><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td></tr>`,
				"", "", "", ActiveSign(active), connectionName, "", "",
			))
		}
	}

	buffer.WriteString(`</table></body></html>`)
	return buffer.String()
}

func (stats ConnectionStats) sortedNames() []string {
	var keys []string
	for key := range stats {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func (stats Stats) sortedQueueNames() []string {
	var keys []string
	for key := range stats.QueueStats {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func (stats Stats) sortedConnectionNames() []string {
	var keys []string
	for key := range stats.otherConnections {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func ActiveSign(active bool) string {
	if active {
		return "✓"
	}
	return "✗"
}
<commit_msg>Expose QueueStat methods for tracking queue size<commit_after>package rmq

import (
	"bytes"
	"fmt"
	"sort"
)

type ConnectionStat struct {
	active       bool
	unackedCount int
	consumers    []string
}

func (stat ConnectionStat) String() string {
	return fmt.Sprintf("[unacked:%d consumers:%d]",
		stat.unackedCount,
		len(stat.consumers),
	)
}

type ConnectionStats map[string]ConnectionStat

type QueueStat struct {
	ReadyCount      int `json:"ready"`
	RejectedCount   int `json:"rejected"`
	connectionStats ConnectionStats
}

func NewQueueStat(readyCount, rejectedCount int) QueueStat {
	return QueueStat{
		ReadyCount:      readyCount,
		RejectedCount:   rejectedCount,
		connectionStats: ConnectionStats{},
	}
}

func (stat QueueStat) String() string {
	return fmt.Sprintf("[ready:%d rejected:%d conn:%s",
		stat.ReadyCount,
		stat.RejectedCount,
		stat.connectionStats,
	)
}

func (stat QueueStat) UnackedCount() int {
	unacked := 0
	for _, connectionStat := range stat.connectionStats {
		unacked += connectionStat.unackedCount
	}
	return unacked
}

func (stat QueueStat) ConsumerCount() int {
	consumer := 0
	for _, connectionStat := range stat.connectionStats {
		consumer += len(connectionStat.consumers)
	}
	return consumer
}

func (stat QueueStat) ConnectionCount() int {
	return len(stat.connectionStats)
}

type QueueStats map[string]QueueStat

type Stats struct {
	QueueStats       QueueStats      `json:"queues"`
	otherConnections map[string]bool // non consuming connections, active or not
}

func NewStats() Stats {
	return Stats{
		QueueStats:       QueueStats{},
		otherConnections: map[string]bool{},
	}
}

func CollectStats(queueList []string, mainConnection *redisConnection) Stats {
	stats := NewStats()
	for _, queueName := range queueList {
		queue := mainConnection.openQueue(queueName)
		stats.QueueStats[queueName] = NewQueueStat(queue.ReadyCount(), queue.RejectedCount())
	}

	connectionNames := mainConnection.GetConnections()
	for _, connectionName := range connectionNames {
		connection := mainConnection.hijackConnection(connectionName)
		connectionActive := connection.Check()

		queueNames := connection.GetConsumingQueues()
		if len(queueNames) == 0 {
			stats.otherConnections[connectionName] = connectionActive
			continue
		}

		for _, queueName := range queueNames {
			queue := connection.openQueue(queueName)
			consumers := queue.GetConsumers()
			openQueueStat, ok := stats.QueueStats[queueName]
			if !ok {
				continue
			}
			openQueueStat.connectionStats[connectionName] = ConnectionStat{
				active:       connectionActive,
				unackedCount: queue.UnackedCount(),
				consumers:    consumers,
			}
		}
	}

	return stats
}

func (stats Stats) String() string {
	var buffer bytes.Buffer

	for queueName, queueStat := range stats.QueueStats {
		buffer.WriteString(fmt.Sprintf("    queue:%s ready:%d rejected:%d unacked:%d consumers:%d\n",
			queueName, queueStat.ReadyCount, queueStat.RejectedCount, queueStat.UnackedCount(), queueStat.ConsumerCount(),
		))

		for connectionName, connectionStat := range queueStat.connectionStats {
			buffer.WriteString(fmt.Sprintf("        connection:%s unacked:%d consumers:%d active:%t\n",
				connectionName, connectionStat.unackedCount, len(connectionStat.consumers), connectionStat.active,
			))
		}
	}

	for connectionName, active := range stats.otherConnections {
		buffer.WriteString(fmt.Sprintf("    connection:%s active:%t\n",
			connectionName, active,
		))
	}

	return buffer.String()
}

func (stats Stats) GetHtml(layout, refresh string) string {
	buffer := bytes.NewBufferString("<html>")

	if refresh != "" {
		buffer.WriteString(fmt.Sprintf(`<head><meta http-equiv="refresh" content="%s">`, refresh))
	}

	buffer.WriteString(`<body><table style="font-family:monospace">`)
	buffer.WriteString(`<tr><td>` +
		`queue</td><td></td><td>` +
		`ready</td><td></td><td>` +
		`rejected</td><td></td><td>` +
		`</td><td></td><td>` +
		`connections</td><td></td><td>` +
		`unacked</td><td></td><td>` +
		`consumers</td><td></td></tr>`,
	)

	for _, queueName := range stats.sortedQueueNames() {
		queueStat := stats.QueueStats[queueName]
		connectionNames := queueStat.connectionStats.sortedNames()
		buffer.WriteString(fmt.Sprintf(`<tr><td>`+
			`%s</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%s</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%d</td><td></td><td>`+
			`%d</td><td></td></tr>`,
			queueName, queueStat.ReadyCount, queueStat.RejectedCount, "", len(connectionNames), queueStat.UnackedCount(), queueStat.ConsumerCount(),
		))

		if layout != "condensed" {
			for _, connectionName := range connectionNames {
				connectionStat := queueStat.connectionStats[connectionName]
				buffer.WriteString(fmt.Sprintf(`<tr style="color:lightgrey"><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%s</td><td></td><td>`+
					`%d</td><td></td><td>`+
					`%d</td><td></td></tr>`,
					"", "", "", ActiveSign(connectionStat.active), connectionName, connectionStat.unackedCount, len(connectionStat.consumers),
				))
			}
		}
	}

	if layout != "condensed" {
		buffer.WriteString(`<tr><td>-----</td></tr>`)
		for _, connectionName := range stats.sortedConnectionNames() {
			active := stats.otherConnections[connectionName]
			buffer.WriteString(fmt.Sprintf(`<tr style="color:lightgrey"><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td><td>`+
				`%s</td><td></td></tr>`,
				"", "", "", ActiveSign(active), connectionName, "", "",
			))
		}
	}

	buffer.WriteString(`</table></body></html>`)
	return buffer.String()
}

func (stats ConnectionStats) sortedNames() []string {
	var keys []string
	for key := range stats {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func (stats Stats) sortedQueueNames() []string {
	var keys []string
	for key := range stats.QueueStats {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func (stats Stats) sortedConnectionNames() []string {
	var keys []string
	for key := range stats.otherConnections {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func ActiveSign(active bool) string {
	if active {
		return "✓"
	}
	return "✗"
}
<|endoftext|>


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

package sessions

import (
	"encoding/base32"
	"io"
	"net/http"
	"os"
	"strings"
	"sync"

	"github.com/gorilla/securecookie"
)

// Store is an interface for custom session stores.
//
// See CookieStore and FilesystemStore for examples.
type Store interface {
	// Get should return a cached session.
	Get(r *http.Request, name string) (*Session, error)

	// New should create and return a new session.
	//
	// Note that New should never return a nil session, even in the case of
	// an error if using the Registry infrastructure to cache the session.
	New(r *http.Request, name string) (*Session, error)

	// Save should persist session to the underlying store implementation.
	Save(r *http.Request, w http.ResponseWriter, s *Session) error
}

// CookieStore ----------------------------------------------------------------

// NewCookieStore returns a new CookieStore.
//
// Keys are defined in pairs to allow key rotation, but the common case is
// to set a single authentication key and optionally an encryption key.
//
// The first key in a pair is used for authentication and the second for
// encryption. The encryption key can be set to nil or omitted in the last
// pair, but the authentication key is required in all pairs.
//
// It is recommended to use an authentication key with 32 or 64 bytes.
// The encryption key, if set, must be either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256 modes.
//
// Use the convenience function securecookie.GenerateRandomKey() to create
// strong keys.
func NewCookieStore(keyPairs ...[]byte) *CookieStore {
	return &CookieStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
	}
}

// CookieStore stores sessions using secure cookies.
type CookieStore struct {
	Codecs  []securecookie.Codec
	Options *Options // default configuration
}

// Get returns a session for the given name after adding it to the registry.
//
// It returns a new session if the sessions doesn't exist. Access IsNew on
// the session to check if it is an existing session or a new one.
//
// It returns a new session and an error if the session exists but could
// not be decoded.
func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
	return GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// The difference between New() and Get() is that calling New() twice will
// decode the session data twice, while Get() registers and reuses the same
// decoded session after the first call.
func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
	session := NewSession(s, name)
	opts := *s.Options
	session.Options = &opts
	session.IsNew = true
	var err error
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.Values,
			s.Codecs...)
		if err == nil {
			session.IsNew = false
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
	session *Session) error {
	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
	return nil
}

// FilesystemStore ------------------------------------------------------------

var fileMutex sync.RWMutex

// NewFilesystemStore returns a new FilesystemStore.
//
// The path argument is the directory where sessions will be saved. If empty
// it will use os.TempDir().
//
// See NewCookieStore() for a description of the other parameters.
func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
	if path == "" {
		path = os.TempDir()
	}
	if path[len(path)-1] != '/' {
		path += "/"
	}
	return &FilesystemStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		path: path,
	}
}

// FilesystemStore stores sessions in the filesystem.
//
// It also serves as a referece for custom stores.
//
// This store is still experimental and not well tested. Feedback is welcome.
type FilesystemStore struct {
	Codecs  []securecookie.Codec
	Options *Options // default configuration
	path    string
}

// MaxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new FilesystemStore is 4096.
func (s *FilesystemStore) MaxLength(l int) {
	for _, c := range s.Codecs {
		if codec, ok := c.(*securecookie.SecureCookie); ok {
			codec.MaxLength(l)
		}
	}
}

// Get returns a session for the given name after adding it to the registry.
//
// See CookieStore.Get().
func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
	return GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// See CookieStore.New().
func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
	session := NewSession(s, name)
	opts := *s.Options
	session.Options = &opts
	session.IsNew = true
	var err error
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
		if err == nil {
			err = s.load(session)
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
	session *Session) error {
	if session.ID == "" {
		// Because the ID is used in the filename, encode it to
		// use alphanumeric characters only.
		session.ID = strings.TrimRight(
			base32.StdEncoding.EncodeToString(
				securecookie.GenerateRandomKey(32)), "=")
	}
	if err := s.save(session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
	return nil
}

// save writes encoded session.Values to a file.
func (s *FilesystemStore) save(session *Session) error {
	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
		s.Codecs...)
	if err != nil {
		return err
	}
	filename := s.path + "session_" + session.ID
	fileMutex.Lock()
	defer fileMutex.Unlock()
	fp, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	if _, err = fp.Write([]byte(encoded)); err != nil {
		return err
	}
	fp.Close()
	return nil
}

// load reads a file and decodes its content into session.Values.
func (s *FilesystemStore) load(session *Session) error {
	filename := s.path + "session_" + session.ID
	fp, err := os.OpenFile(filename, os.O_RDONLY, 0400)
	if err != nil {
		return err
	}
	defer fp.Close()
	var fdata []byte
	buf := make([]byte, 128)
	for {
		var n int
		n, err = fp.Read(buf[0:])
		fdata = append(fdata, buf[0:n]...)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
	}
	if err = securecookie.DecodeMulti(session.Name(), string(fdata),
		&session.Values, s.Codecs...); err != nil {
		return err
	}
	return nil
}
<commit_msg>ensure FilesystemStore closes the file even on error.<commit_after>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sessions

import (
	"encoding/base32"
	"io"
	"net/http"
	"os"
	"strings"
	"sync"

	"github.com/gorilla/securecookie"
)

// Store is an interface for custom session stores.
//
// See CookieStore and FilesystemStore for examples.
type Store interface {
	// Get should return a cached session.
	Get(r *http.Request, name string) (*Session, error)

	// New should create and return a new session.
	//
	// Note that New should never return a nil session, even in the case of
	// an error if using the Registry infrastructure to cache the session.
	New(r *http.Request, name string) (*Session, error)

	// Save should persist session to the underlying store implementation.
	Save(r *http.Request, w http.ResponseWriter, s *Session) error
}

// CookieStore ----------------------------------------------------------------

// NewCookieStore returns a new CookieStore.
//
// Keys are defined in pairs to allow key rotation, but the common case is
// to set a single authentication key and optionally an encryption key.
//
// The first key in a pair is used for authentication and the second for
// encryption. The encryption key can be set to nil or omitted in the last
// pair, but the authentication key is required in all pairs.
//
// It is recommended to use an authentication key with 32 or 64 bytes.
// The encryption key, if set, must be either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256 modes.
//
// Use the convenience function securecookie.GenerateRandomKey() to create
// strong keys.
func NewCookieStore(keyPairs ...[]byte) *CookieStore {
	return &CookieStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
	}
}

// CookieStore stores sessions using secure cookies.
type CookieStore struct {
	Codecs  []securecookie.Codec
	Options *Options // default configuration
}

// Get returns a session for the given name after adding it to the registry.
//
// It returns a new session if the sessions doesn't exist. Access IsNew on
// the session to check if it is an existing session or a new one.
//
// It returns a new session and an error if the session exists but could
// not be decoded.
func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
	return GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// The difference between New() and Get() is that calling New() twice will
// decode the session data twice, while Get() registers and reuses the same
// decoded session after the first call.
func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
	session := NewSession(s, name)
	opts := *s.Options
	session.Options = &opts
	session.IsNew = true
	var err error
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.Values,
			s.Codecs...)
		if err == nil {
			session.IsNew = false
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
	session *Session) error {
	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
	return nil
}

// FilesystemStore ------------------------------------------------------------

var fileMutex sync.RWMutex

// NewFilesystemStore returns a new FilesystemStore.
//
// The path argument is the directory where sessions will be saved. If empty
// it will use os.TempDir().
//
// See NewCookieStore() for a description of the other parameters.
func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
	if path == "" {
		path = os.TempDir()
	}
	if path[len(path)-1] != '/' {
		path += "/"
	}
	return &FilesystemStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		path: path,
	}
}

// FilesystemStore stores sessions in the filesystem.
//
// It also serves as a referece for custom stores.
//
// This store is still experimental and not well tested. Feedback is welcome.
type FilesystemStore struct {
	Codecs  []securecookie.Codec
	Options *Options // default configuration
	path    string
}

// MaxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new FilesystemStore is 4096.
func (s *FilesystemStore) MaxLength(l int) {
	for _, c := range s.Codecs {
		if codec, ok := c.(*securecookie.SecureCookie); ok {
			codec.MaxLength(l)
		}
	}
}

// Get returns a session for the given name after adding it to the registry.
//
// See CookieStore.Get().
func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
	return GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// See CookieStore.New().
func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
	session := NewSession(s, name)
	opts := *s.Options
	session.Options = &opts
	session.IsNew = true
	var err error
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
		if err == nil {
			err = s.load(session)
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
	session *Session) error {
	if session.ID == "" {
		// Because the ID is used in the filename, encode it to
		// use alphanumeric characters only.
		session.ID = strings.TrimRight(
			base32.StdEncoding.EncodeToString(
				securecookie.GenerateRandomKey(32)), "=")
	}
	if err := s.save(session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
	return nil
}

// save writes encoded session.Values to a file.
func (s *FilesystemStore) save(session *Session) error {
	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
		s.Codecs...)
	if err != nil {
		return err
	}
	filename := s.path + "session_" + session.ID
	fileMutex.Lock()
	defer fileMutex.Unlock()
	fp, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer fp.Close()
	if _, err = fp.Write([]byte(encoded)); err != nil {
		return err
	}
	return nil
}

// load reads a file and decodes its content into session.Values.
func (s *FilesystemStore) load(session *Session) error {
	filename := s.path + "session_" + session.ID
	fp, err := os.OpenFile(filename, os.O_RDONLY, 0400)
	if err != nil {
		return err
	}
	defer fp.Close()
	var fdata []byte
	buf := make([]byte, 128)
	for {
		var n int
		n, err = fp.Read(buf[0:])
		fdata = append(fdata, buf[0:n]...)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
	}
	if err = securecookie.DecodeMulti(session.Name(), string(fdata),
		&session.Values, s.Codecs...); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Oleku Konko All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// This module is a Table Writer  API for the Go Programming Language.
// The protocols were written in pure Go and works on windows and unix systems

// Create & Generate text based table
package tablewriter

import (
	"fmt"
	"io"
	"regexp"
	"strings"
)

const (
	MAX_ROW_WIDTH = 30
)

const (
	CENTRE = "+"
	ROW    = "-"
	COLUMN = "|"
	SPACE  = " "
)

const (
	ALIGN_DEFAULT = iota
	ALIGN_CENTRE
	ALIGN_RIGHT
	ALIGN_LEFT
)

var (
	decimal = regexp.MustCompile(`^\d*\.?\d*$`)
	percent = regexp.MustCompile(`^\d*\.?\d*$%$`)
)

type Table struct {
	out      io.Writer
	rows     [][]string
	lines    [][][]string
	cs       map[int]int
	rs       map[int]int
	headers  []string
	footers  []string
	autoFmt  bool
	autoWrap bool
	mW       int
	pCenter  string
	pRow     string
	pColumn  string
	tColumn  int
	tRow     int
	align    int
	rowLine  bool
	hdrLine  bool
	border   bool
	colSize  int
}

// Start New Table
// Take io.Writer Directly
func NewWriter(writer io.Writer) *Table {
	t := &Table{
		out:      writer,
		rows:     [][]string{},
		lines:    [][][]string{},
		cs:       make(map[int]int),
		rs:       make(map[int]int),
		headers:  []string{},
		footers:  []string{},
		autoFmt:  true,
		autoWrap: true,
		mW:       MAX_ROW_WIDTH,
		pCenter:  CENTRE,
		pRow:     ROW,
		pColumn:  COLUMN,
		tColumn:  -1,
		tRow:     -1,
		align:    ALIGN_DEFAULT,
		rowLine:  false,
		hdrLine:  true,
		border:   true,
		colSize:  -1}
	return t
}

// Render table output
func (t Table) Render() {
	if t.border {
		t.printLine(true)
	}
	t.printHeading()
	t.printRows()

	if !t.rowLine && t.border {
		t.printLine(true)
	}
	t.printFooter()

}

// Set table header
func (t *Table) SetHeader(keys []string) {
	t.colSize = len(keys)
	for i, v := range keys {
		t.parseDimension(v, i, -1)
		t.headers = append(t.headers, v)
	}
}

// Set table Footer
func (t *Table) SetFooter(keys []string) {
	//t.colSize = len(keys)
	for i, v := range keys {
		t.parseDimension(v, i, -1)
		t.footers = append(t.footers, v)
	}
}

// Turn header autoformatting on/off. Default is on (true).
func (t *Table) SetAutoFormatHeaders(auto bool) {
	t.autoFmt = auto
}

// Turn automatic multiline text adjustment on/off. Default is on (true).
func (t *Table) SetAutoWrapText(auto bool) {
	t.autoWrap = auto
}

// Set the Default column width
func (t *Table) SetColWidth(width int) {
	t.mW = width
}

// Set the Column Separator
func (t *Table) SetColumnSeparator(sep string) {
	t.pColumn = sep
}

// Set the Row Separator
func (t *Table) SetRowSeparator(sep string) {
	t.pRow = sep
}

// Set the center Separator
func (t *Table) SetCenterSeparator(sep string) {
	t.pCenter = sep
}

// Set Table Alignment
func (t *Table) SetAlignment(align int) {
	t.align = align
}

// Set Header Line
// This would enable / disable a line after the header
func (t *Table) SetHeaderLine(line bool) {
	t.hdrLine = line
}

// Set Row Line
// This would enable / disable a line on each row of the table
func (t *Table) SetRowLine(line bool) {
	t.rowLine = line
}

// Set Table Border
// This would enable / disable line around the table
func (t *Table) SetBorder(border bool) {
	t.border = border
}

// Append row to table
func (t *Table) Append(row []string) {
	rowSize := len(t.headers)
	if rowSize > t.colSize {
		t.colSize = rowSize
	}

	n := len(t.lines)
	line := [][]string{}
	for i, v := range row {

		// Detect string  width
		// Detect String height
		// Break strings into words
		out := t.parseDimension(v, i, n)

		// Append broken words
		line = append(line, out)
	}
	t.lines = append(t.lines, line)
}

// Allow Support for Bulk Append
// Eliminates repeated for loops
func (t *Table) AppendBulk(rows [][]string) {
	for _, row := range rows {
		t.Append(row)
	}
}

// Print line based on row width
func (t Table) printLine(nl bool) {
	fmt.Fprint(t.out, t.pCenter)
	for i := 0; i < len(t.cs); i++ {
		v := t.cs[i]
		fmt.Fprintf(t.out, "%s%s%s%s",
			t.pRow,
			strings.Repeat(string(t.pRow), v),
			t.pRow,
			t.pCenter)
	}
	if nl {
		fmt.Fprintln(t.out)
	}
}

// Print heading information
func (t Table) printHeading() {
	// Check if headers is available
	if len(t.headers) < 1 {
		return
	}

	// Check if border is set
	// Replace with space if not set
	fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))

	// Identify last column
	end := len(t.cs) - 1

	// Print Heading column
	for i := 0; i <= end; i++ {
		v := t.cs[i]
		h := t.headers[i]
		if t.autoFmt {
			h = Title(h)
		}
		pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)
		fmt.Fprintf(t.out, " %s %s",
			Pad(h, SPACE, v),
			pad)
	}
	// Next line
	fmt.Fprintln(t.out)
	if t.hdrLine {
		t.printLine(true)
	}
}

// Print heading information
func (t Table) printFooter() {
	// Check if headers is available
	if len(t.footers) < 1 {
		return
	}

	// Only print line if border is not set
	if !t.border {
		t.printLine(true)
	}
	// Check if border is set
	// Replace with space if not set
	fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))

	// Identify last column
	end := len(t.cs) - 1

	// Print Heading column
	for i := 0; i <= end; i++ {
		v := t.cs[i]
		f := t.footers[i]
		if t.autoFmt {
			f = Title(f)
		}
		pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)

		if len(t.footers[i]) == 0 {
			pad = SPACE
		}
		fmt.Fprintf(t.out, " %s %s",
			Pad(f, SPACE, v),
			pad)
	}
	// Next line
	fmt.Fprintln(t.out)
	//t.printLine(true)

	hasPrinted := false

	for i := 0; i <= end; i++ {
		v := t.cs[i]
		pad := t.pRow
		center := t.pCenter
		length := len(t.footers[i])

		if length > 0 {
			hasPrinted = true
		}

		// Set center to be space if length is 0
		if length == 0 && !t.border {
			center = SPACE
		}

		// Print first junction
		if i == 0 {
			fmt.Fprint(t.out, center)
		}

		// Pad With space of length is 0
		if length == 0 {
			pad = SPACE
		}
		// Ignore left space of it has printed before
		if hasPrinted || t.border {
			pad = t.pRow
			center = t.pCenter
		}

		// Change Center start position
		if center == SPACE {
			if i < end && len(t.footers[i+1]) != 0 {
				center = t.pCenter
			}
		}

		// Print the footer
		fmt.Fprintf(t.out, "%s%s%s%s",
			pad,
			strings.Repeat(string(pad), v),
			pad,
			center)

	}

	fmt.Fprintln(t.out)

}

func (t Table) printRows() {
	for i, lines := range t.lines {
		t.printRow(lines, i)
	}

}

// Print Row Information
// Adjust column alignment based on type

func (t Table) printRow(columns [][]string, colKey int) {
	// Get Maximum Height
	max := t.rs[colKey]
	total := len(columns)

	// TODO Fix uneven col size
	// if total < t.colSize {
	//	for n := t.colSize - total; n < t.colSize ; n++ {
	//		columns = append(columns, []string{SPACE})
	//		t.cs[n] = t.mW
	//	}
	//}

	// Pad Each Height
	// pads := []int{}
	pads := []int{}

	for i, line := range columns {
		length := len(line)
		pad := max - length
		pads = append(pads, pad)
		for n := 0; n < pad; n++ {
			columns[i] = append(columns[i], "  ")
		}
	}
	//fmt.Println(max, "\n")
	for x := 0; x < max; x++ {
		for y := 0; y < total; y++ {

			// Check if border is set
			fmt.Fprint(t.out, ConditionString((!t.border && y == 0), SPACE, t.pColumn))

			fmt.Fprintf(t.out, SPACE)
			str := columns[y][x]

			// This would print alignment
			// Default alignment  would use multiple configuration
			switch t.align {
			case ALIGN_CENTRE: //
				fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
			case ALIGN_RIGHT:
				fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
			case ALIGN_LEFT:
				fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
			default:
				if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
					fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
				} else {
					fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))

					// TODO Custom alignment per column
					//if max == 1 || pads[y] > 0 {
					//	fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
					//} else {
					//	fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
					//}

				}
			}
			fmt.Fprintf(t.out, SPACE)
		}
		// Check if border is set
		// Replace with space if not set
		fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
		fmt.Fprintln(t.out)
	}

	if t.rowLine {
		t.printLine(true)
	}

}

func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
	var (
		raw []string
		max int
	)
	w := DisplayWidth(str)
	// Calculate Width
	// Check if with is grater than maximum width
	if w > t.mW {
		w = t.mW
	}

	// Check if width exists
	v, ok := t.cs[colKey]
	if !ok || v < w || v == 0 {
		t.cs[colKey] = w
	}

	if rowKey == -1 {
		return raw
	}
	// Calculate Height
	if t.autoWrap {
		raw, _ = WrapString(str, t.cs[colKey])
	} else {
		raw = getLines(str)
	}

	for _, line := range raw {
		if w := DisplayWidth(line); w > max {
			max = w
		}
	}

	// Make sure the with is the same length as maximum word
	// Important for cases where the width is smaller than maxu word
	if max > t.cs[colKey] {
		t.cs[colKey] = max
	}

	h := len(raw)
	v, ok = t.rs[rowKey]

	if !ok || v < h || v == 0 {
		t.rs[rowKey] = h
	}
	//fmt.Printf("Raw %+v %d\n", raw, len(raw))
	return raw
}
<commit_msg>Allow to choose the alignment of the header and footer<commit_after>// Copyright 2014 Oleku Konko All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// This module is a Table Writer  API for the Go Programming Language.
// The protocols were written in pure Go and works on windows and unix systems

// Create & Generate text based table
package tablewriter

import (
	"fmt"
	"io"
	"regexp"
	"strings"
)

const (
	MAX_ROW_WIDTH = 30
)

const (
	CENTRE = "+"
	ROW    = "-"
	COLUMN = "|"
	SPACE  = " "
)

const (
	ALIGN_DEFAULT = iota
	ALIGN_CENTRE
	ALIGN_RIGHT
	ALIGN_LEFT
)

var (
	decimal = regexp.MustCompile(`^\d*\.?\d*$`)
	percent = regexp.MustCompile(`^\d*\.?\d*$%$`)
)

type Table struct {
	out      io.Writer
	rows     [][]string
	lines    [][][]string
	cs       map[int]int
	rs       map[int]int
	headers  []string
	footers  []string
	autoFmt  bool
	autoWrap bool
	mW       int
	pCenter  string
	pRow     string
	pColumn  string
	tColumn  int
	tRow     int
	hAlign   int
	fAlign   int
	align    int
	rowLine  bool
	hdrLine  bool
	border   bool
	colSize  int
}

// Start New Table
// Take io.Writer Directly
func NewWriter(writer io.Writer) *Table {
	t := &Table{
		out:      writer,
		rows:     [][]string{},
		lines:    [][][]string{},
		cs:       make(map[int]int),
		rs:       make(map[int]int),
		headers:  []string{},
		footers:  []string{},
		autoFmt:  true,
		autoWrap: true,
		mW:       MAX_ROW_WIDTH,
		pCenter:  CENTRE,
		pRow:     ROW,
		pColumn:  COLUMN,
		tColumn:  -1,
		tRow:     -1,
		hAlign:   ALIGN_DEFAULT,
		fAlign:   ALIGN_DEFAULT,
		align:    ALIGN_DEFAULT,
		rowLine:  false,
		hdrLine:  true,
		border:   true,
		colSize:  -1}
	return t
}

// Render table output
func (t Table) Render() {
	if t.border {
		t.printLine(true)
	}
	t.printHeading()
	t.printRows()

	if !t.rowLine && t.border {
		t.printLine(true)
	}
	t.printFooter()

}

// Set table header
func (t *Table) SetHeader(keys []string) {
	t.colSize = len(keys)
	for i, v := range keys {
		t.parseDimension(v, i, -1)
		t.headers = append(t.headers, v)
	}
}

// Set table Footer
func (t *Table) SetFooter(keys []string) {
	//t.colSize = len(keys)
	for i, v := range keys {
		t.parseDimension(v, i, -1)
		t.footers = append(t.footers, v)
	}
}

// Turn header autoformatting on/off. Default is on (true).
func (t *Table) SetAutoFormatHeaders(auto bool) {
	t.autoFmt = auto
}

// Turn automatic multiline text adjustment on/off. Default is on (true).
func (t *Table) SetAutoWrapText(auto bool) {
	t.autoWrap = auto
}

// Set the Default column width
func (t *Table) SetColWidth(width int) {
	t.mW = width
}

// Set the Column Separator
func (t *Table) SetColumnSeparator(sep string) {
	t.pColumn = sep
}

// Set the Row Separator
func (t *Table) SetRowSeparator(sep string) {
	t.pRow = sep
}

// Set the center Separator
func (t *Table) SetCenterSeparator(sep string) {
	t.pCenter = sep
}

// Set Header Alignment
func (t *Table) SetHeaderAlignment(hAlign int) {
	t.hAlign = hAlign
}

// Set Footer Alignment
func (t *Table) SetFooterAlignment(fAlign int) {
	t.fAlign = fAlign
}

// Set Table Alignment
func (t *Table) SetAlignment(align int) {
	t.align = align
}

// Set Header Line
// This would enable / disable a line after the header
func (t *Table) SetHeaderLine(line bool) {
	t.hdrLine = line
}

// Set Row Line
// This would enable / disable a line on each row of the table
func (t *Table) SetRowLine(line bool) {
	t.rowLine = line
}

// Set Table Border
// This would enable / disable line around the table
func (t *Table) SetBorder(border bool) {
	t.border = border
}

// Append row to table
func (t *Table) Append(row []string) {
	rowSize := len(t.headers)
	if rowSize > t.colSize {
		t.colSize = rowSize
	}

	n := len(t.lines)
	line := [][]string{}
	for i, v := range row {

		// Detect string  width
		// Detect String height
		// Break strings into words
		out := t.parseDimension(v, i, n)

		// Append broken words
		line = append(line, out)
	}
	t.lines = append(t.lines, line)
}

// Allow Support for Bulk Append
// Eliminates repeated for loops
func (t *Table) AppendBulk(rows [][]string) {
	for _, row := range rows {
		t.Append(row)
	}
}

// Print line based on row width
func (t Table) printLine(nl bool) {
	fmt.Fprint(t.out, t.pCenter)
	for i := 0; i < len(t.cs); i++ {
		v := t.cs[i]
		fmt.Fprintf(t.out, "%s%s%s%s",
			t.pRow,
			strings.Repeat(string(t.pRow), v),
			t.pRow,
			t.pCenter)
	}
	if nl {
		fmt.Fprintln(t.out)
	}
}

// Return the PadRight function if align is left, PadLeft if align is right,
// and Pad by default
func pad(align int) func(string, string, int) string {
	padFunc := Pad
	switch align {
	case ALIGN_LEFT:
		padFunc = PadRight
	case ALIGN_RIGHT:
		padFunc = PadLeft
	}
	return padFunc
}

// Print heading information
func (t Table) printHeading() {
	// Check if headers is available
	if len(t.headers) < 1 {
		return
	}

	// Check if border is set
	// Replace with space if not set
	fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))

	// Identify last column
	end := len(t.cs) - 1

	// Get pad function
	padFunc := pad(t.hAlign)

	// Print Heading column
	for i := 0; i <= end; i++ {
		v := t.cs[i]
		h := t.headers[i]
		if t.autoFmt {
			h = Title(h)
		}
		pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)
		fmt.Fprintf(t.out, " %s %s",
			padFunc(h, SPACE, v),
			pad)
	}
	// Next line
	fmt.Fprintln(t.out)
	if t.hdrLine {
		t.printLine(true)
	}
}

// Print heading information
func (t Table) printFooter() {
	// Check if headers is available
	if len(t.footers) < 1 {
		return
	}

	// Only print line if border is not set
	if !t.border {
		t.printLine(true)
	}
	// Check if border is set
	// Replace with space if not set
	fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))

	// Identify last column
	end := len(t.cs) - 1

	// Get pad function
	padFunc := pad(t.fAlign)

	// Print Heading column
	for i := 0; i <= end; i++ {
		v := t.cs[i]
		f := t.footers[i]
		if t.autoFmt {
			f = Title(f)
		}
		pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)

		if len(t.footers[i]) == 0 {
			pad = SPACE
		}
		fmt.Fprintf(t.out, " %s %s",
			padFunc(f, SPACE, v),
			pad)
	}
	// Next line
	fmt.Fprintln(t.out)
	//t.printLine(true)

	hasPrinted := false

	for i := 0; i <= end; i++ {
		v := t.cs[i]
		pad := t.pRow
		center := t.pCenter
		length := len(t.footers[i])

		if length > 0 {
			hasPrinted = true
		}

		// Set center to be space if length is 0
		if length == 0 && !t.border {
			center = SPACE
		}

		// Print first junction
		if i == 0 {
			fmt.Fprint(t.out, center)
		}

		// Pad With space of length is 0
		if length == 0 {
			pad = SPACE
		}
		// Ignore left space of it has printed before
		if hasPrinted || t.border {
			pad = t.pRow
			center = t.pCenter
		}

		// Change Center start position
		if center == SPACE {
			if i < end && len(t.footers[i+1]) != 0 {
				center = t.pCenter
			}
		}

		// Print the footer
		fmt.Fprintf(t.out, "%s%s%s%s",
			pad,
			strings.Repeat(string(pad), v),
			pad,
			center)

	}

	fmt.Fprintln(t.out)

}

func (t Table) printRows() {
	for i, lines := range t.lines {
		t.printRow(lines, i)
	}

}

// Print Row Information
// Adjust column alignment based on type

func (t Table) printRow(columns [][]string, colKey int) {
	// Get Maximum Height
	max := t.rs[colKey]
	total := len(columns)

	// TODO Fix uneven col size
	// if total < t.colSize {
	//	for n := t.colSize - total; n < t.colSize ; n++ {
	//		columns = append(columns, []string{SPACE})
	//		t.cs[n] = t.mW
	//	}
	//}

	// Pad Each Height
	// pads := []int{}
	pads := []int{}

	for i, line := range columns {
		length := len(line)
		pad := max - length
		pads = append(pads, pad)
		for n := 0; n < pad; n++ {
			columns[i] = append(columns[i], "  ")
		}
	}
	//fmt.Println(max, "\n")
	for x := 0; x < max; x++ {
		for y := 0; y < total; y++ {

			// Check if border is set
			fmt.Fprint(t.out, ConditionString((!t.border && y == 0), SPACE, t.pColumn))

			fmt.Fprintf(t.out, SPACE)
			str := columns[y][x]

			// This would print alignment
			// Default alignment  would use multiple configuration
			switch t.align {
			case ALIGN_CENTRE: //
				fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
			case ALIGN_RIGHT:
				fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
			case ALIGN_LEFT:
				fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
			default:
				if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
					fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
				} else {
					fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))

					// TODO Custom alignment per column
					//if max == 1 || pads[y] > 0 {
					//	fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
					//} else {
					//	fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
					//}

				}
			}
			fmt.Fprintf(t.out, SPACE)
		}
		// Check if border is set
		// Replace with space if not set
		fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
		fmt.Fprintln(t.out)
	}

	if t.rowLine {
		t.printLine(true)
	}

}

func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
	var (
		raw []string
		max int
	)
	w := DisplayWidth(str)
	// Calculate Width
	// Check if with is grater than maximum width
	if w > t.mW {
		w = t.mW
	}

	// Check if width exists
	v, ok := t.cs[colKey]
	if !ok || v < w || v == 0 {
		t.cs[colKey] = w
	}

	if rowKey == -1 {
		return raw
	}
	// Calculate Height
	if t.autoWrap {
		raw, _ = WrapString(str, t.cs[colKey])
	} else {
		raw = getLines(str)
	}

	for _, line := range raw {
		if w := DisplayWidth(line); w > max {
			max = w
		}
	}

	// Make sure the with is the same length as maximum word
	// Important for cases where the width is smaller than maxu word
	if max > t.cs[colKey] {
		t.cs[colKey] = max
	}

	h := len(raw)
	v, ok = t.rs[rowKey]

	if !ok || v < h || v == 0 {
		t.rs[rowKey] = h
	}
	//fmt.Printf("Raw %+v %d\n", raw, len(raw))
	return raw
}
<|endoftext|>


<commit_before>package crud

import "strconv"
import "fmt"
import "strings"
import "errors"

type Table struct {
	*CRUD
	tableName string
}

// 返回这张表所有数据
func (t *Table) All() []map[string]string {
	return t.Query("SELECT * FROM " + t.tableName).RawsMap()
}

// 返回表有多少条数据
func (t *Table) Count() (count int) {
	t.Query("SELECT COUNT(*) FROM " + t.tableName).Scan(&count)
	return
}

// 查找表的更新时间
func (t *Table) UpdateTime() (updateTime string) {
	t.Query("SELECT `UPDATE_TIME` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA =(select database()) AND TABLE_NAME = '" + t.tableName + "';").Scan(&updateTime)
	return
}

// 查找表的自增ID的值
func (t *Table) AutoIncrement() (id int) {
	t.Query("SELECT `AUTO_INCREMENT` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA =(select database()) AND TABLE_NAME = '" + t.tableName + "';").Scan(&id)
	return
}

// 设置自动增长ID
func (t *Table) SetAutoIncrement(id int) error {
	return t.Exec("ALTER TABLE `" + t.tableName + "` AUTO_INCREMENT = " + strconv.Itoa(id)).err
}

// 查找表的最大ID，如果为NULL的话则为0
func (t *Table) MaxID() (maxid int) {
	t.Query("SELECT IFNULL(MAX(id), 0) as id FROM `" + t.tableName + "`").Scan(&maxid)
	return
}

/*
	创建
	check 如果有，则会判断表里面以这几个字段为唯一的话，数据库是否存在此条数据，如果有就不插入了。
*/
func (t *Table) Create(m map[string]interface{}, checks ...string) error {
	//INSERT INTO `feedback` (`task_id`, `template_question_id`, `question_options_id`, `suggestion`, `member_id`) VALUES ('1', '1', '1', '1', '1')
	if len(checks) > 0 {
		names := []string{}
		values := []interface{}{}
		for _, check := range checks {
			names = append(names, "`"+check+"`"+" = ? ")
			values = append(values, m[check])
		}
		// SELECT COUNT(*) FROM `feedback` WHERE `task_id` = ? AND `member_id` = ?
		if t.Query(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", t.tableName, strings.Join(names, "AND")), values...).Int() > 0 {
			return errors.New("重复插入")
		}
	}
	ks, vs := ksvs(m)
	e, err := t.Exec(fmt.Sprintf("INSERT INTO `%s` (%s) VALUES (%s)", t.tableName, strings.Join(ks, ","), argslice(len(ks))), vs...).Effected()
	if err != nil {
		return errors.New("SQL语句异常")
	}
	if e <= 0 {
		return errors.New("插入数据库异常")
	}
	return nil
}
<commit_msg>增加Table Update<commit_after>package crud

import "strconv"
import "fmt"
import "strings"
import "errors"

type Table struct {
	*CRUD
	tableName string
}

// 返回这张表所有数据
func (t *Table) All() []map[string]string {
	return t.Query("SELECT * FROM " + t.tableName).RawsMap()
}

// 返回表有多少条数据
func (t *Table) Count() (count int) {
	t.Query("SELECT COUNT(*) FROM " + t.tableName).Scan(&count)
	return
}

// 查找表的更新时间
func (t *Table) UpdateTime() (updateTime string) {
	t.Query("SELECT `UPDATE_TIME` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA =(select database()) AND TABLE_NAME = '" + t.tableName + "';").Scan(&updateTime)
	return
}

// 查找表的自增ID的值
func (t *Table) AutoIncrement() (id int) {
	t.Query("SELECT `AUTO_INCREMENT` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA =(select database()) AND TABLE_NAME = '" + t.tableName + "';").Scan(&id)
	return
}

// 设置自动增长ID
func (t *Table) SetAutoIncrement(id int) error {
	return t.Exec("ALTER TABLE `" + t.tableName + "` AUTO_INCREMENT = " + strconv.Itoa(id)).err
}

// 查找表的最大ID，如果为NULL的话则为0
func (t *Table) MaxID() (maxid int) {
	t.Query("SELECT IFNULL(MAX(id), 0) as id FROM `" + t.tableName + "`").Scan(&maxid)
	return
}

/*
	创建
	check 如果有，则会判断表里面以这几个字段为唯一的话，数据库是否存在此条数据，如果有就不插入了。
*/
func (t *Table) Create(m map[string]interface{}, checks ...string) error {
	//INSERT INTO `feedback` (`task_id`, `template_question_id`, `question_options_id`, `suggestion`, `member_id`) VALUES ('1', '1', '1', '1', '1')
	if len(checks) > 0 {
		names := []string{}
		values := []interface{}{}
		for _, check := range checks {
			names = append(names, "`"+check+"`"+" = ? ")
			values = append(values, m[check])
		}
		// SELECT COUNT(*) FROM `feedback` WHERE `task_id` = ? AND `member_id` = ?
		if t.Query(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", t.tableName, strings.Join(names, "AND")), values...).Int() > 0 {
			return errors.New("重复插入")
		}
	}
	ks, vs := ksvs(m)
	e, err := t.Exec(fmt.Sprintf("INSERT INTO `%s` (%s) VALUES (%s)", t.tableName, strings.Join(ks, ","), argslice(len(ks))), vs...).Effected()
	if err != nil {
		return errors.New("SQL语句异常")
	}
	if e <= 0 {
		return errors.New("插入数据库异常")
	}
	return nil
}

func (t *Table) Update(m map[string]interface{}, keys ...string) error {
	if len(keys) == 0 {
		keys = append(keys, "id")
	}
	keysValue := []interface{}{}
	whereks := []string{}
	for _, key := range keys {
		val, ok := m[key]
		if !ok {
			return errors.New("没有更新主键")
		}
		keysValue = append(keysValue, val)
		delete(m, key)
		whereks = append(whereks, "`"+key+"` = ? ")
	}
	//因为在更新的时候最好不要更新ID，而有时候又会将ID传入进来，所以id每次都会被删除，如果要更新id的话使用Exec()
	delete(m, "id")
	ks, vs := ksvs(m, " = ? ")
	for _, val := range keysValue {
		vs = append(vs, val)
	}
	_, err := t.Exec(fmt.Sprintf("UPDATE `%s` SET %s WHERE %s LIMIT 1", t.tableName, strings.Join(ks, ","), strings.Join(whereks, "AND")), vs...).Effected()
	if err != nil {
		return errors.New("SQL语句异常")
	}
	return nil
}

func (t *Table) Delete(m map[string]interface{}) error {
	return nil
}
<|endoftext|>


<commit_before>package yorm

import (
	"database/sql"
	"errors"
	"reflect"
	"strings"
	"sync"
	"time"
)

type tableSetter struct {
	table    string
	dests    []interface{}
	columns  []*column
	pkColumn *column
}

var (
	//TimeType time's reflect type.
	TimeType = reflect.TypeOf(time.Time{})

	// one struct reflect to a table query setter
	tableMap = map[reflect.Value]*tableSetter{}
	//table lock
	tableRWLock sync.RWMutex
)

func newTableSetter(ri reflect.Value) (*tableSetter, error) {
	if q, ok := tableMap[ri]; ok {
		//		if t, ok := ri.Interface().(YormTableStruct); ok {
		//			returnValue := *q
		//			returnValue.table = t.YormTableName()
		//			return &returnValue, nil
		//		}
		return q, nil
	}
	tableRWLock.Lock()
	defer tableRWLock.Unlock()
	if q, ok := tableMap[ri]; ok {
		return q, nil
	}
	if ri.Kind() != reflect.Ptr {
		return nil, ErrNonPtr
	}
	if ri.IsNil() {
		return nil, ErrNotSupported
	}
	q := new(tableSetter)
	table, cs := structToTable(reflect.Indirect(ri).Interface())
	var err error
	q.pkColumn, err = findPkColumn(cs)
	if q.pkColumn == nil {
		tableMap[ri] = nil
		return nil, err
	}
	q.table = table
	q.columns = cs
	q.dests = make([]interface{}, len(cs))
	for k, v := range cs {
		q.dests[k] = newPtrInterface(v.typ)
	}
	tableMap[ri] = q
	return q, nil
}

func findPkColumn(cs []*column) (*column, error) {
	var c *column
	var idColumn *column
	isPk := false

	for _, v := range cs {
		if strings.ToLower(v.name) == "id" {
			idColumn = v
		}
		if v.isPK {
			if isPk {
				return c, ErrDuplicatePkColumn
			}
			isPk = true
			c = v
		}
	}
	if c == nil && idColumn != nil {
		idColumn.isPK = true
		idColumn.isAuto = true
		c = idColumn
	}
	if c == nil {
		return nil, ErrNonePkColumn
	}
	return c, nil
}

func newPtrInterface(t reflect.Type) interface{} {
	k := t.Kind()
	var ti interface{}
	switch k {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		ti = new(sql.NullInt64)
	case reflect.String:
		ti = new(sql.NullString)
	case reflect.Float32, reflect.Float64:
		ti = new(sql.NullFloat64)
	case reflect.Struct:
		switch t {
		case TimeType:
			ti = new(sql.NullString)
		}
	}
	return ti
}

func scanValue(sc sqlScanner, q *tableSetter, st reflect.Value) error {
	err := sc.Scan(q.dests...)
	if err != nil {
		return err
	}
	for idx, c := range q.columns {
		// different assign func here
		fv := st.Field(c.fieldNum)
		fi := q.dests[idx]
		err := setValue(fv, fi)
		if err != nil {
			continue
		}
	}
	return nil
}

func setValue(fv reflect.Value, fi interface{}) error {
	switch fv.Type().Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		sqlValue := sql.NullInt64(*(fi.(*sql.NullInt64)))
		if !sqlValue.Valid {
			fv.SetInt(0)
			return errors.New("sqlValue is invalid")
		}
		fv.SetInt(sqlValue.Int64)
	case reflect.String:
		sqlValue := sql.NullString(*(fi.(*sql.NullString)))
		if !sqlValue.Valid {
			fv.SetString("")
			return errors.New("sqlValue is invalid")
		}
		fv.SetString(sqlValue.String)
	case reflect.Float32, reflect.Float64:
		sqlValue := sql.NullFloat64(*(fi.(*sql.NullFloat64)))
		if !sqlValue.Valid {
			fv.SetFloat(0.0)
			return errors.New("sqlValue is invalid")
		}
		fv.SetFloat(sqlValue.Float64)
	case reflect.Struct:
		switch fv.Type() {
		case TimeType:
			sqlValue := sql.NullString(*(fi.(*sql.NullString)))
			if !sqlValue.Valid {
				return errors.New("sqlValue is invalid")
			}
			timeStr := sqlValue.String
			var layout string
			if len(timeStr) == 10 {
				layout = shortSimpleTimeFormat
			}
			if len(timeStr) == 19 {
				layout = longSimpleTimeFormat
			}
			timeTime, err := time.ParseInLocation(layout, timeStr, time.Local)
			if timeTime.IsZero() {
				return err
			}
			fv.Set(reflect.ValueOf(timeTime))
		}
	}
	return nil
}
<commit_msg>bug fixed<commit_after>package yorm

import (
	"database/sql"
	"errors"
	"reflect"
	"strings"
	"sync"
	"time"
)

type tableSetter struct {
	table    string
	dests    []interface{}
	columns  []*column
	pkColumn *column
}

var (
	//TimeType time's reflect type.
	TimeType = reflect.TypeOf(time.Time{})

	// one struct reflect to a table query setter
	tableMap = map[reflect.Value]*tableSetter{}
	//table lock
	tableRWLock sync.RWMutex
)

func newTableSetter(ri reflect.Value) (*tableSetter, error) {
	if q, ok := tableMap[ri]; ok {
		//		if t, ok := ri.Interface().(YormTableStruct); ok {
		//			returnValue := *q
		//			returnValue.table = t.YormTableName()
		//			return &returnValue, nil
		//		}
		return q, nil
	}
	tableRWLock.Lock()
	defer tableRWLock.Unlock()
	if q, ok := tableMap[ri]; ok {
		return q, nil
	}
	if ri.Kind() != reflect.Ptr {
		return nil, ErrNonPtr
	}
	if ri.IsNil() {
		return nil, ErrNotSupported
	}
	q := new(tableSetter)
	table, cs := structToTable(reflect.Indirect(ri).Interface())
	var err error
	q.pkColumn, err = findPkColumn(cs)
	if q.pkColumn == nil {
		return nil, err
	}
	q.table = table
	q.columns = cs
	q.dests = make([]interface{}, len(cs))
	for k, v := range cs {
		q.dests[k] = newPtrInterface(v.typ)
	}
	tableMap[ri] = q
	return q, nil
}

func findPkColumn(cs []*column) (*column, error) {
	var c *column
	var idColumn *column
	isPk := false

	for _, v := range cs {
		if strings.ToLower(v.name) == "id" {
			idColumn = v
		}
		if v.isPK {
			if isPk {
				return c, ErrDuplicatePkColumn
			}
			isPk = true
			c = v
		}
	}
	if c == nil && idColumn != nil {
		idColumn.isPK = true
		idColumn.isAuto = true
		c = idColumn
	}
	if c == nil {
		return nil, ErrNonePkColumn
	}
	return c, nil
}

func newPtrInterface(t reflect.Type) interface{} {
	k := t.Kind()
	var ti interface{}
	switch k {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		ti = new(sql.NullInt64)
	case reflect.String:
		ti = new(sql.NullString)
	case reflect.Float32, reflect.Float64:
		ti = new(sql.NullFloat64)
	case reflect.Struct:
		switch t {
		case TimeType:
			ti = new(sql.NullString)
		}
	}
	return ti
}

func scanValue(sc sqlScanner, q *tableSetter, st reflect.Value) error {
	err := sc.Scan(q.dests...)
	if err != nil {
		return err
	}
	for idx, c := range q.columns {
		// different assign func here
		fv := st.Field(c.fieldNum)
		fi := q.dests[idx]
		err := setValue(fv, fi)
		if err != nil {
			continue
		}
	}
	return nil
}

func setValue(fv reflect.Value, fi interface{}) error {
	switch fv.Type().Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		fallthrough
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		sqlValue := sql.NullInt64(*(fi.(*sql.NullInt64)))
		if !sqlValue.Valid {
			fv.SetInt(0)
			return errors.New("sqlValue is invalid")
		}
		fv.SetInt(sqlValue.Int64)
	case reflect.String:
		sqlValue := sql.NullString(*(fi.(*sql.NullString)))
		if !sqlValue.Valid {
			fv.SetString("")
			return errors.New("sqlValue is invalid")
		}
		fv.SetString(sqlValue.String)
	case reflect.Float32, reflect.Float64:
		sqlValue := sql.NullFloat64(*(fi.(*sql.NullFloat64)))
		if !sqlValue.Valid {
			fv.SetFloat(0.0)
			return errors.New("sqlValue is invalid")
		}
		fv.SetFloat(sqlValue.Float64)
	case reflect.Struct:
		switch fv.Type() {
		case TimeType:
			sqlValue := sql.NullString(*(fi.(*sql.NullString)))
			if !sqlValue.Valid {
				return errors.New("sqlValue is invalid")
			}
			timeStr := sqlValue.String
			var layout string
			if len(timeStr) == 10 {
				layout = shortSimpleTimeFormat
			}
			if len(timeStr) == 19 {
				layout = longSimpleTimeFormat
			}
			timeTime, err := time.ParseInLocation(layout, timeStr, time.Local)
			if timeTime.IsZero() {
				return err
			}
			fv.Set(reflect.ValueOf(timeTime))
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package hooks

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/gorilla/mux"

	"github.com/ovh/cds/engine/api"
	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/sdk/"
	"github.com/ovh/cds/sdk/cdsclient"
	"github.com/ovh/cds/sdk/log"
)

// New returns a new service
func New() *Service {
	s := new(Service)
	s.Router = &api.Router{
		Mux: mux.NewRouter(),
	}
	return s
}

// ApplyConfiguration apply an object of type hooks.Configuration after checking it
func (s *Service) ApplyConfiguration(config interface{}) error {
	if err := s.CheckConfiguration(config); err != nil {
		return err
	}
	var ok bool
	s.Cfg, ok = config.(Configuration)
	if !ok {
		return fmt.Errorf("Invalid configuration")
	}
	return nil
}

// CheckConfiguration checks the validity of the configuration object
func (s *Service) CheckConfiguration(config interface{}) error {
	sConfig, ok := config.(Configuration)
	if !ok {
		return fmt.Errorf("Invalid configuration")
	}

	if sConfig.URL == "" {
		return fmt.Errorf("your CDS configuration seems to be empty. Please use environment variables, file or Consul to set your configuration")
	}
	if sConfig.Name == "" {
		return fmt.Errorf("please enter a name in your hooks configuration")
	}

	return nil
}

// Serve will start the http api server
func (s *Service) Serve(c context.Context) error {
	ctx, cancel := context.WithCancel(c)
	defer cancel()

	log.Info("Hooks> Starting service %s %s...", s.Cfg.Name, sdk.VERSION)

	//Instanciate a cds client
	s.cds = cdsclient.NewService(s.Cfg.API.HTTP.URL)

	//First register(heartbeat)
	if err := s.doHeartbeat(); err != nil {
		log.Error("Hooks> Unable to register: %v", err)
		return err
	}
	log.Info("Hooks> Service registered")

	//Init the cache
	var errCache error
	s.Cache, errCache = cache.New(s.Cfg.Cache.Redis.Host, s.Cfg.Cache.Redis.Password, s.Cfg.Cache.TTL)
	if errCache != nil {
		return errCache
	}

	//Init the DAO
	s.Dao = dao{s.Cache}

	//Start the heartbeat gorourine
	go func() {
		if err := s.heartbeat(ctx); err != nil {
			log.Error("%v", err)
			cancel()
		}
	}()

	//Start all the tasks
	go func() {
		if err := s.runTasks(ctx); err != nil {
			log.Error("%v", err)
			cancel()
		}
	}()

	//Start the scheduler to execute all the tasks
	go func() {
		if err := s.runScheduler(ctx); err != nil {
			log.Error("%v", err)
			cancel()
		}
	}()

	//Init the http server
	s.initRouter(ctx)
	server := &http.Server{
		Addr:           fmt.Sprintf("%s:%d", s.Cfg.HTTP.Addr, s.Cfg.HTTP.Port),
		Handler:        s.Router.Mux,
		ReadTimeout:    10 * time.Minute,
		WriteTimeout:   10 * time.Minute,
		MaxHeaderBytes: 1 << 20,
	}

	//Gracefully shutdown the http server
	go func() {
		select {
		case <-ctx.Done():
			log.Info("Hooks> Shutdown HTTP Server")
			server.Shutdown(ctx)
		}
	}()

	//Start the http server
	log.Info("Hooks> Starting HTTP Server on port %d", s.Cfg.HTTP.Port)
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("Hooks> Cannot start cds-hooks: %s", err)
	}

	return ctx.Err()
}
<commit_msg>fix (api): non-canonical (#1526)<commit_after>package hooks

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/gorilla/mux"

	"github.com/ovh/cds/engine/api"
	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/cdsclient"
	"github.com/ovh/cds/sdk/log"
)

// New returns a new service
func New() *Service {
	s := new(Service)
	s.Router = &api.Router{
		Mux: mux.NewRouter(),
	}
	return s
}

// ApplyConfiguration apply an object of type hooks.Configuration after checking it
func (s *Service) ApplyConfiguration(config interface{}) error {
	if err := s.CheckConfiguration(config); err != nil {
		return err
	}
	var ok bool
	s.Cfg, ok = config.(Configuration)
	if !ok {
		return fmt.Errorf("Invalid configuration")
	}
	return nil
}

// CheckConfiguration checks the validity of the configuration object
func (s *Service) CheckConfiguration(config interface{}) error {
	sConfig, ok := config.(Configuration)
	if !ok {
		return fmt.Errorf("Invalid configuration")
	}

	if sConfig.URL == "" {
		return fmt.Errorf("your CDS configuration seems to be empty. Please use environment variables, file or Consul to set your configuration")
	}
	if sConfig.Name == "" {
		return fmt.Errorf("please enter a name in your hooks configuration")
	}

	return nil
}

// Serve will start the http api server
func (s *Service) Serve(c context.Context) error {
	ctx, cancel := context.WithCancel(c)
	defer cancel()

	log.Info("Hooks> Starting service %s %s...", s.Cfg.Name, sdk.VERSION)

	//Instanciate a cds client
	s.cds = cdsclient.NewService(s.Cfg.API.HTTP.URL)

	//First register(heartbeat)
	if err := s.doHeartbeat(); err != nil {
		log.Error("Hooks> Unable to register: %v", err)
		return err
	}
	log.Info("Hooks> Service registered")

	//Init the cache
	var errCache error
	s.Cache, errCache = cache.New(s.Cfg.Cache.Redis.Host, s.Cfg.Cache.Redis.Password, s.Cfg.Cache.TTL)
	if errCache != nil {
		return errCache
	}

	//Init the DAO
	s.Dao = dao{s.Cache}

	//Start the heartbeat gorourine
	go func() {
		if err := s.heartbeat(ctx); err != nil {
			log.Error("%v", err)
			cancel()
		}
	}()

	//Start all the tasks
	go func() {
		if err := s.runTasks(ctx); err != nil {
			log.Error("%v", err)
			cancel()
		}
	}()

	//Start the scheduler to execute all the tasks
	go func() {
		if err := s.runScheduler(ctx); err != nil {
			log.Error("%v", err)
			cancel()
		}
	}()

	//Init the http server
	s.initRouter(ctx)
	server := &http.Server{
		Addr:           fmt.Sprintf("%s:%d", s.Cfg.HTTP.Addr, s.Cfg.HTTP.Port),
		Handler:        s.Router.Mux,
		ReadTimeout:    10 * time.Minute,
		WriteTimeout:   10 * time.Minute,
		MaxHeaderBytes: 1 << 20,
	}

	//Gracefully shutdown the http server
	go func() {
		select {
		case <-ctx.Done():
			log.Info("Hooks> Shutdown HTTP Server")
			server.Shutdown(ctx)
		}
	}()

	//Start the http server
	log.Info("Hooks> Starting HTTP Server on port %d", s.Cfg.HTTP.Port)
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("Hooks> Cannot start cds-hooks: %s", err)
	}

	return ctx.Err()
}
<|endoftext|>


<commit_before>package shoboi

import (
	"fmt"
	"testing"

	"github.com/fatih/color"
	"github.com/stretchr/testify/assert"
)

func TestEpisodes(t *testing.T) {
	anime, err := GetAnime("4515")

	assert.Nil(t, err)

	for _, episode := range anime.Episodes() {
		assert.NotEmpty(t, episode.TitleJapanese)

		fmt.Printf("Episode %d: %s\n", episode.Number, color.GreenString(episode.TitleJapanese))

		airingDate := episode.AiringDate()
		fmt.Println(airingDate)

		assert.NotEmpty(t, airingDate.Start)
		assert.NotEmpty(t, airingDate.End)
	}
}
<commit_msg>Added test<commit_after>package shoboi

import (
	"fmt"
	"testing"

	"github.com/fatih/color"
	"github.com/stretchr/testify/assert"
)

func TestEpisodes(t *testing.T) {
	anime, err := GetAnime("4515")

	assert.Nil(t, err)

	for _, episode := range anime.Episodes() {
		assert.NotEmpty(t, episode.TitleJapanese)

		fmt.Printf("Episode %d: %s\n", episode.Number, color.GreenString(episode.TitleJapanese))

		airingDate := episode.AiringDate()
		fmt.Println(airingDate)

		assert.NotEmpty(t, airingDate.Start)
		assert.NotEmpty(t, airingDate.End)
	}
}

func TestNonExistingAnime(t *testing.T) {
	anime, err := GetAnime("999999999999")

	assert.Nil(t, anime)
	assert.Error(t, err)
}
<|endoftext|>


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

import (
	"fmt"
	"log"
	"sync"
	"github.com/streadway/amqp"
	"github.com/tsuru/config"
)

type rabbitmqQ struct {
	name    string
	prefix  string
	factory *rabbitmqQFactory
	psc     *amqp.Channel
}

func (r *rabbitmqQ) exchname() string {
	return r.prefix + "_" + r.name + "_exchange"
}

func (r *rabbitmqQ) qname() string {
	return r.prefix + "_" + r.name + "_queue"
}

func (r *rabbitmqQ) tag() string {
	return r.prefix + "_" + r.name + "_tag"
}

func (r *rabbitmqQ) key() string {
	return r.prefix + "_" + r.name + "_key"
}

func (r *rabbitmqQ) Pub(msg []byte) error {
	chnl, err := r.factory.dial(r.exchname()) // return amqp.Channel
	if err != nil {
		return err
	}

	log.Printf(" [x] Publishing (%s, %s) message (%q).", r.exchname(), r.key() , msg)

	if err = chnl.Publish(
		r.exchname(), // publish to an exchange
		r.key(),      // routing to 0 or more queues
		false,        // mandatory
		false,        // immediate
		amqp.Publishing{
			Headers:         amqp.Table{},
			ContentType:     "text/plain",
			ContentEncoding: "",
			Body:            msg,
			DeliveryMode:    amqp.Transient, // 1=non-persistent, 2=persistent
			Priority:        0,              // 0-9
			// a bunch of application/implementation-specific fields
		},
	); err != nil {
		return fmt.Errorf("Exchange Publish: %s", err)
	}
	log.Printf(" [x] Publish message (%q).", err)
	return err
}

func (r *rabbitmqQ) UnSub() error {
	if r.psc == nil {
		return nil
	}
	err := r.psc.Cancel(r.tag(), false)
	if err != nil {
		return err
	}
	return nil
}

func (r *rabbitmqQ) Sub() (chan []byte, error) {
	chnl, err := r.factory.getChonn(r.key(), r.exchname(), r.qname())
	if err != nil {
		return nil, err
	}

	r.psc = chnl

	msgChan := make(chan []byte)

	log.Printf(" [x] Subscribing (%s,%s)", r.qname(), r.tag())

	deliveries, err := chnl.Consume(
		r.qname(), // name
		r.tag(),   // consumerTag,
		true,     // noAck
		false,     // exclusive
		false,     // noLocal
		false,     // noWait
		nil,       // arguments
	)
	if err != nil {
		return nil, err
	}
	log.Printf(" [x] Subscribed (%s,%s)", r.qname(), r.tag())

	//This is asynchronous, the callee will have to wait.
	go func() {
		//defer close(msgChan)
		for d := range deliveries {
			log.Printf(" [%s] : [%v] %q", r.qname(), d.DeliveryTag, d.Body)
			msgChan <- d.Body
		}

	}()
	return msgChan, nil
}

type rabbitmqQFactory struct {
	sync.Mutex
}

func (factory *rabbitmqQFactory) Get(name string) (PubSubQ, error) {
	return &rabbitmqQ{name: name, prefix: "megam", factory: factory}, nil
}

func (factory *rabbitmqQFactory) Dial() (*amqp.Connection, error) {
	addr, err := config.GetString("amqp:url")
	if err != nil {
		addr = "amqp://localhost:5672/"
	}
	conn, err := amqp.Dial(addr)

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

func (factory *rabbitmqQFactory) dial(exchname string) (*amqp.Channel, error) {
	addr, err := config.GetString("amqp:url")
	if err != nil {
		addr = "amqp://localhost:5672/"
	}
	conn, err := amqp.Dial(addr)

	if err != nil {
		return nil, err
	}

	//defer conn.Close()

	log.Printf(" [x] Dialed to (%s)", addr)

	chnl, err := conn.Channel()

	if err != nil {
		return nil, err
	}

	//defer chnl.Close()

	if err = chnl.ExchangeDeclare(
		exchname, // name of the exchange
		"fanout", // exchange Type
		true,     // durable
		false,    // delete when complete
		false,    // internal
		false,    // noWait
		nil,      // arguments
	); err != nil {
		return nil, err
	}

	log.Printf(" [x] Connection successful to  (%s,%s)", addr, exchname)
	return chnl, err
}

func (factory *rabbitmqQFactory) getChonn(key string, exchname string, qname string) (*amqp.Channel, error) {
	chnl, err := factory.dial(exchname)
	if err != nil {
		return nil, err
	}
	log.Printf(" [x] Dialed  (%s)", exchname)

	qu, err := chnl.QueueDeclare(
		qname, // name of the queue
		true,  // durable
		false, // delete when usused
		false, // exclusive
		false, // noWait
		nil,   // arguments
	)
	if err != nil {
		return nil, err
	}

	log.Printf(" [x] Declared queue (%s)", qname)

	if err = chnl.QueueBind(
		qu.Name, // name of the queue
		key,
		exchname,
		false, // noWait
		nil,   // arguments
	); err != nil {
		return nil, err
	}

	log.Printf(" [x] Bound to queue (%s,%s,%s)", qname, exchname, key)
	return chnl, nil
}
<commit_msg>Removecommit<commit_after>/*
** Copyright [2012-2014] [Megam Systems]
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
 */
package amqp

import (
	"fmt"
	"log"
	"sync"
	"github.com/streadway/amqp"
	"github.com/tsuru/config"
)

type rabbitmqQ struct {
	name    string
	prefix  string
	factory *rabbitmqQFactory
	psc     *amqp.Channel
}

func (r *rabbitmqQ) exchname() string {
	return r.prefix + "_" + r.name + "_exchange"
}

func (r *rabbitmqQ) qname() string {
	return r.prefix + "_" + r.name + "_queue"
}

func (r *rabbitmqQ) tag() string {
	return r.prefix + "_" + r.name + "_tag"
}

func (r *rabbitmqQ) key() string {
	return r.prefix + "_" + r.name + "_key"
}

func (r *rabbitmqQ) Pub(msg []byte) error {
	chnl, err := r.factory.dial(r.exchname()) // return amqp.Channel
	if err != nil {
		return err
	}

	log.Printf(" [x] Publishing (%s, %s) message (%q).", r.exchname(), r.key() , msg)

	if err = chnl.Publish(
		r.exchname(), // publish to an exchange
		r.key(),      // routing to 0 or more queues
		false,        // mandatory
		false,        // immediate
		amqp.Publishing{
			Headers:         amqp.Table{},
			ContentType:     "text/plain",
			ContentEncoding: "",
			Body:            msg,
			DeliveryMode:    amqp.Transient, // 1=non-persistent, 2=persistent
			Priority:        0,              // 0-9
			// a bunch of application/implementation-specific fields
		},
	); err != nil {
		return fmt.Errorf("Exchange Publish: %s", err)
	}
	log.Printf(" [x] Publish message (%q).", err)
	return err
}

func (r *rabbitmqQ) UnSub() error {
	if r.psc == nil {
		return nil
	}
	err := r.psc.Cancel(r.tag(), false)
	if err != nil {
		return err
	}
	return nil
}

func (r *rabbitmqQ) Sub() (chan []byte, error) {
        fmt.Println("Entered Subz")
	chnl, err := r.factory.getChonn(r.key(), r.exchname(), r.qname())
	if err != nil {
		return nil, err
	}

	r.psc = chnl

	msgChan := make(chan []byte)

	log.Printf(" [x] Subscribing (%s,%s)", r.qname(), r.tag())

	deliveries, err := chnl.Consume(
		r.qname(), // name
		r.tag(),   // consumerTag,
		true,     // noAck
		false,     // exclusive
		false,     // noLocal
		false,     // noWait
		nil,       // arguments
	)
	if err != nil {
		return nil, err
	}
	log.Printf(" [x] Subscribed (%s,%s)", r.qname(), r.tag())

	//This is asynchronous, the callee will have to wait.
	go func() {
		//defer close(msgChan)
		for d := range deliveries {
			log.Printf(" [%s] : [%v] %q", r.qname(), d.DeliveryTag, d.Body)
			msgChan <- d.Body
		}

	}()
	return msgChan, nil
}

type rabbitmqQFactory struct {
	sync.Mutex
}

func (factory *rabbitmqQFactory) Get(name string) (PubSubQ, error) {
        fmt.Println("Entered getz")

	return &rabbitmqQ{name: name, prefix: "megam", factory: factory}, nil
}

func (factory *rabbitmqQFactory) Dial() (*amqp.Connection, error) {
        fmt.Println("Entered dialz")

	addr, err := config.GetString("amqp:url")
	if err != nil {
		addr = "amqp://localhost:5672/"
	}
	conn, err := amqp.Dial(addr)

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

func (factory *rabbitmqQFactory) dial(exchname string) (*amqp.Channel, error) {
	addr, err := config.GetString("amqp:url")
	if err != nil {
		addr = "amqp://localhost:5672/"
	}
	conn, err := amqp.Dial(addr)

	if err != nil {
		return nil, err
	}

	//defer conn.Close()

	log.Printf(" [x] Dialed to (%s)", addr)

	chnl, err := conn.Channel()

	if err != nil {
		return nil, err
	}

	//defer chnl.Close()

	if err = chnl.ExchangeDeclare(
		exchname, // name of the exchange
		"fanout", // exchange Type
		true,     // durable
		false,    // delete when complete
		false,    // internal
		false,    // noWait
		nil,      // arguments
	); err != nil {
		return nil, err
	}

	log.Printf(" [x] Connection successful to  (%s,%s)", addr, exchname)
	return chnl, err
}

func (factory *rabbitmqQFactory) getChonn(key string, exchname string, qname string) (*amqp.Channel, error) {
	chnl, err := factory.dial(exchname)
	if err != nil {
		return nil, err
	}
	log.Printf(" [x] Dialed  (%s)", exchname)

	qu, err := chnl.QueueDeclare(
		qname, // name of the queue
		true,  // durable
		false, // delete when usused
		false, // exclusive
		false, // noWait
		nil,   // arguments
	)
	if err != nil {
		return nil, err
	}

	log.Printf(" [x] Declared queue (%s)", qname)

	if err = chnl.QueueBind(
		qu.Name, // name of the queue
		key,
		exchname,
		false, // noWait
		nil,   // arguments
	); err != nil {
		return nil, err
	}

	log.Printf(" [x] Bound to queue (%s,%s,%s)", qname, exchname, key)
	return chnl, nil
}
<|endoftext|>


<commit_before>package container

import (
	log "github.com/Sirupsen/logrus"
	"github.com/samalba/dockerclient"

	"fmt"
	"strings"
	"time"
)

// Docker container labels
const (
	TugbotService     = "tugbot-service"
	TugbotTest        = "tugbot-test"
	TugbotEventDocker = "tugbot-event-docker"
	TugbotEventTimer  = "tugbot-event-timer"
	TugbotCreatedFrom = "tugbot-created-from"
	SwarmTaskID       = "com.docker.swarm.task.id"
)

// Docker Event Filter
const (
	// type filter: tugbot-event-docker-filter-type=container|image|daemon|network|volume|plugin
	TypeFilter = "tugbot-event-docker-filter-type"
	// action filter (depends on type), for 'container' type:
	//  - attach, commit, copy, create, destroy, detach, die, exec_create, exec_detach, exec_start, export,
	//  - health_status, kill, oom, pause, rename, resize, restart, start, stop, top, unpause, update
	ActionFilter = "tugbot-event-docker-filter-action"
	// container filter: use name, comma separated name list or RE2 regexp
	ContainerFilter = "tugbot-event-docker-filter-container"
	// image filter: use name, comma separated name list or RE2 regexp
	ImageFilter = "tugbot-event-docker-filter-image"
	// label filter: use key=value comma separated pairs
	LabelFilter = "tugbot-event-docker-filter-label"
)

// NewContainer returns a new Container instance instantiated with the
// specified ContainerInfo and ImageInfo structs.
func NewContainer(containerInfo *dockerclient.ContainerInfo, imageInfo *dockerclient.ImageInfo) *Container {
	return &Container{
		containerInfo: containerInfo,
		imageInfo:     imageInfo,
	}
}

// Container represents a running Docker container.
type Container struct {
	containerInfo *dockerclient.ContainerInfo
	imageInfo     *dockerclient.ImageInfo
}

// ID returns the Docker container ID.
func (c Container) ID() string {
	return c.containerInfo.Id
}

// Name returns the Docker container name.
func (c Container) Name() string {
	return strings.TrimPrefix(c.containerInfo.Name, "/")
}

// ImageID returns the ID of the Docker image that was used to start the
// container.
func (c Container) ImageID() string {
	return c.imageInfo.Id
}

// ImageName returns the name of the Docker image that was used to start the
// container. If the original image was specified without a particular tag, the
// "latest" tag is assumed.
func (c Container) ImageName() string {
	imageName := c.containerInfo.Config.Image

	if !strings.Contains(imageName, ":") {
		imageName = fmt.Sprintf("%s:latest", imageName)
	}

	return imageName
}

// IsTugbot returns whether or not the current container is the tugbot container itself.
// The tugbot container is identified by the presence of the "tugbot.service"
// label in the container metadata.
func (c Container) IsTugbot() bool {
	val, ok := c.containerInfo.Config.Labels[TugbotService]

	return ok && val == "true"
}

// IsTugbotCandidate returns whether or not a container is a candidate to run by tugbot.
// A candidate container is identified by the presence of "tugbot-test",
// it doesn't contain "tugbot.created.from" in the container metadata and it state is "Exited".
func (c Container) IsTugbotCandidate() bool {
	ret := false
	log.Info("Checking container %s for being candidate",c.containerInfo.Name)
	val, ok := c.containerInfo.Config.Labels[TugbotTest]
	log.Info("Val is %s", val)
	if ok && val == "true" {
		if !c.IsCreatedByTugbot() {
			ret = c.containerInfo.State.StateString() == "exited"
		}
	}

	return ret
}

// IsCreatedByTugbot returns whether or not a container created by tugbot.
func (c Container) IsCreatedByTugbot() bool {
	val, ok := c.containerInfo.Config.Labels[TugbotCreatedFrom]

	return ok && val != ""
}

// IsEventListener returns whether or not a container should run when an event e is occurred.
func (c Container) IsEventListener(e *dockerclient.Event) bool {
	ret := false
	if e != nil {
		// check if container is subscribed to Docker events, i.e. 'tugbot-event-docker' label exists
		_, ret = c.containerInfo.Config.Labels[TugbotEventDocker]
		if ret {
			// filter by event type
			if typeFilter, ok := c.containerInfo.Config.Labels[TypeFilter]; ok {
				ret = sliceContains(e.Type, splitAndTrimSpaces(typeFilter, ","))
			}
			// filter by event action
			if actionFilter, ok := c.containerInfo.Config.Labels[ActionFilter]; ok {
				ret = ret && sliceContains(e.Action, splitAndTrimSpaces(actionFilter, ","))
			}
			// filter by container name or name regexp
			if containerFilter, ok := c.containerInfo.Config.Labels[ContainerFilter]; ok {
				ret = ret && inFilterOrList(e.Actor.Attributes["name"], containerFilter)
			}
		}
		// filter by event image
		if imageFilter, ok := c.containerInfo.Config.Labels[ImageFilter]; ok {
			// get image name from event.From field
			imageName := e.From
			// in case of "image" event.Type, event.ID contains image ID (name:tag) for 'pull' action and sha256:num for untag and delete
			if e.Type == "image" {
				imageName = e.ID
			}
			ret = ret && inFilterOrList(imageName, imageFilter)
		}
		// filter by event labels
		if labelFilter, ok := c.containerInfo.Config.Labels[LabelFilter]; ok {
			labels := splitAndTrimSpaces(labelFilter, ",")
			for _, label := range labels {
				ret = ret && mapContains(e.Actor.Attributes, splitAndTrimSpaces(label, "="))
			}
		}
	}

	return ret
}

// GetEventListenerTimer returns interval duration between a test container run and true
// if docker label exist and label value parsed into Duration, Otherwise false.
func (c Container) GetEventListenerInterval() (time.Duration, bool) {
	var ret time.Duration
	val, ok := c.containerInfo.Config.Labels[TugbotEventTimer]
	if ok {
		interval, err := time.ParseDuration(val)
		if err != nil {
			log.Errorf("Failed to parse %s docker label: %s into golang Duration (%v)", TugbotEventTimer, val, err)
			ok = false
		} else {
			ret = interval
		}
	}

	return ret, ok
}

// Any links in the HostConfig need to be re-written before they can be
// re-submitted to the Docker create API.
func (c Container) hostConfig() *dockerclient.HostConfig {
	hostConfig := c.containerInfo.HostConfig

	for i, link := range hostConfig.Links {
		name := link[0:strings.Index(link, ":")]
		alias := link[strings.LastIndex(link, "/"):]

		hostConfig.Links[i] = fmt.Sprintf("%s:%s", name, alias)
	}

	return hostConfig
}
<commit_msg>extra logging added temporarily<commit_after>package container

import (
	log "github.com/Sirupsen/logrus"
	"github.com/samalba/dockerclient"

	"fmt"
	"strings"
	"time"
)

// Docker container labels
const (
	TugbotService     = "tugbot-service"
	TugbotTest        = "tugbot-test"
	TugbotEventDocker = "tugbot-event-docker"
	TugbotEventTimer  = "tugbot-event-timer"
	TugbotCreatedFrom = "tugbot-created-from"
	SwarmTaskID       = "com.docker.swarm.task.id"
)

// Docker Event Filter
const (
	// type filter: tugbot-event-docker-filter-type=container|image|daemon|network|volume|plugin
	TypeFilter = "tugbot-event-docker-filter-type"
	// action filter (depends on type), for 'container' type:
	//  - attach, commit, copy, create, destroy, detach, die, exec_create, exec_detach, exec_start, export,
	//  - health_status, kill, oom, pause, rename, resize, restart, start, stop, top, unpause, update
	ActionFilter = "tugbot-event-docker-filter-action"
	// container filter: use name, comma separated name list or RE2 regexp
	ContainerFilter = "tugbot-event-docker-filter-container"
	// image filter: use name, comma separated name list or RE2 regexp
	ImageFilter = "tugbot-event-docker-filter-image"
	// label filter: use key=value comma separated pairs
	LabelFilter = "tugbot-event-docker-filter-label"
)

// NewContainer returns a new Container instance instantiated with the
// specified ContainerInfo and ImageInfo structs.
func NewContainer(containerInfo *dockerclient.ContainerInfo, imageInfo *dockerclient.ImageInfo) *Container {
	return &Container{
		containerInfo: containerInfo,
		imageInfo:     imageInfo,
	}
}

// Container represents a running Docker container.
type Container struct {
	containerInfo *dockerclient.ContainerInfo
	imageInfo     *dockerclient.ImageInfo
}

// ID returns the Docker container ID.
func (c Container) ID() string {
	return c.containerInfo.Id
}

// Name returns the Docker container name.
func (c Container) Name() string {
	return strings.TrimPrefix(c.containerInfo.Name, "/")
}

// ImageID returns the ID of the Docker image that was used to start the
// container.
func (c Container) ImageID() string {
	return c.imageInfo.Id
}

// ImageName returns the name of the Docker image that was used to start the
// container. If the original image was specified without a particular tag, the
// "latest" tag is assumed.
func (c Container) ImageName() string {
	imageName := c.containerInfo.Config.Image

	if !strings.Contains(imageName, ":") {
		imageName = fmt.Sprintf("%s:latest", imageName)
	}

	return imageName
}

// IsTugbot returns whether or not the current container is the tugbot container itself.
// The tugbot container is identified by the presence of the "tugbot.service"
// label in the container metadata.
func (c Container) IsTugbot() bool {
	val, ok := c.containerInfo.Config.Labels[TugbotService]

	return ok && val == "true"
}

// IsTugbotCandidate returns whether or not a container is a candidate to run by tugbot.
// A candidate container is identified by the presence of "tugbot-test",
// it doesn't contain "tugbot.created.from" in the container metadata and it state is "Exited".
func (c Container) IsTugbotCandidate() bool {
	ret := false
	log.Info("Checking container %v for being candidate ",c.containerInfo.Name)
	val, ok := c.containerInfo.Config.Labels[TugbotTest]
	log.Info("Val is %v, data is %v", val, c.containerInfo.Config.Labels[TugbotTest])
	if ok && val == "true" {
		if !c.IsCreatedByTugbot() {
			ret = c.containerInfo.State.StateString() == "exited"
		}
	}

	return ret
}

// IsCreatedByTugbot returns whether or not a container created by tugbot.
func (c Container) IsCreatedByTugbot() bool {
	val, ok := c.containerInfo.Config.Labels[TugbotCreatedFrom]

	return ok && val != ""
}

// IsEventListener returns whether or not a container should run when an event e is occurred.
func (c Container) IsEventListener(e *dockerclient.Event) bool {
	ret := false
	if e != nil {
		// check if container is subscribed to Docker events, i.e. 'tugbot-event-docker' label exists
		_, ret = c.containerInfo.Config.Labels[TugbotEventDocker]
		if ret {
			// filter by event type
			if typeFilter, ok := c.containerInfo.Config.Labels[TypeFilter]; ok {
				ret = sliceContains(e.Type, splitAndTrimSpaces(typeFilter, ","))
			}
			// filter by event action
			if actionFilter, ok := c.containerInfo.Config.Labels[ActionFilter]; ok {
				ret = ret && sliceContains(e.Action, splitAndTrimSpaces(actionFilter, ","))
			}
			// filter by container name or name regexp
			if containerFilter, ok := c.containerInfo.Config.Labels[ContainerFilter]; ok {
				ret = ret && inFilterOrList(e.Actor.Attributes["name"], containerFilter)
			}
		}
		// filter by event image
		if imageFilter, ok := c.containerInfo.Config.Labels[ImageFilter]; ok {
			// get image name from event.From field
			imageName := e.From
			// in case of "image" event.Type, event.ID contains image ID (name:tag) for 'pull' action and sha256:num for untag and delete
			if e.Type == "image" {
				imageName = e.ID
			}
			ret = ret && inFilterOrList(imageName, imageFilter)
		}
		// filter by event labels
		if labelFilter, ok := c.containerInfo.Config.Labels[LabelFilter]; ok {
			labels := splitAndTrimSpaces(labelFilter, ",")
			for _, label := range labels {
				ret = ret && mapContains(e.Actor.Attributes, splitAndTrimSpaces(label, "="))
			}
		}
	}

	return ret
}

// GetEventListenerTimer returns interval duration between a test container run and true
// if docker label exist and label value parsed into Duration, Otherwise false.
func (c Container) GetEventListenerInterval() (time.Duration, bool) {
	var ret time.Duration
	val, ok := c.containerInfo.Config.Labels[TugbotEventTimer]
	if ok {
		interval, err := time.ParseDuration(val)
		if err != nil {
			log.Errorf("Failed to parse %s docker label: %s into golang Duration (%v)", TugbotEventTimer, val, err)
			ok = false
		} else {
			ret = interval
		}
	}

	return ret, ok
}

// Any links in the HostConfig need to be re-written before they can be
// re-submitted to the Docker create API.
func (c Container) hostConfig() *dockerclient.HostConfig {
	hostConfig := c.containerInfo.HostConfig

	for i, link := range hostConfig.Links {
		name := link[0:strings.Index(link, ":")]
		alias := link[strings.LastIndex(link, "/"):]

		hostConfig.Links[i] = fmt.Sprintf("%s:%s", name, alias)
	}

	return hostConfig
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/phase2/rig/cli/util"
	"github.com/urfave/cli"
)

type Project struct {
	BaseCommand
	Config *ProjectConfig
}

func (cmd *Project) Commands() []cli.Command {
	cmd.Config = NewProjectConfig()

	command := cli.Command{
		Name:        "project",
		Usage:       "Run project-specific commands.",
		Description: "Run project-specific commands as part of development.\n\n\tConfigured scripts are driven by an Outrigger configuration file expected at your project root directory.\n\n\tBy default, this is a YAML file named 'outrigger.yml' with fallback to '.outrigger.yml'. It can be overridden by setting an environment variable $RIG_PROJECT_CONFIG_FILE.",
		Aliases:     []string{"run"},
		Category:    "Development",
		Before:      cmd.Before,
	}

	create := ProjectCreate{}
	command.Subcommands = append(command.Subcommands, create.Commands()...)

	sync := ProjectSync{}
	command.Subcommands = append(command.Subcommands, sync.Commands()...)

	if subcommands := cmd.GetScriptsAsSubcommands(command.Subcommands); subcommands != nil {
		command.Subcommands = append(command.Subcommands, subcommands...)
	}

	return []cli.Command{command}
}

// Processes script configuration into formal subcommands.
func (cmd *Project) GetScriptsAsSubcommands(otherSubcommands []cli.Command) []cli.Command {
	cmd.Config.ValidateProjectScripts(otherSubcommands)

	if cmd.Config.Scripts == nil {
		return nil
	}

	var commands = []cli.Command{}
	for id, script := range cmd.Config.Scripts {
		if len(script.Run) > 0 {
			command := cli.Command{
				Name:        fmt.Sprintf("run:%s", id),
				Usage:       script.Description,
				Description: fmt.Sprintf("%s\n\n\tThis command was configured in %s\n\n\tThere are %d steps in this script and any 'extra' arguments will be appended to the final step.", script.Description, cmd.Config.File, len(script.Run)),
				ArgsUsage:   "<args passed to last step>",
				Category:    "Configured Scripts",
				Before:      cmd.Before,
				Action:      cmd.Run,
			}

			if len(script.Alias) > 0 {
				command.Aliases = []string{script.Alias}
			}
			command.Description = command.Description + cmd.ScriptRunHelp(script)

			commands = append(commands, command)
		}
	}

	return commands
}

// Execute the selected projec script.
func (cmd *Project) Run(c *cli.Context) error {
	cmd.out.Verbose.Printf("Loaded project configuration from %s", cmd.Config.Path)
	if cmd.Config.Scripts == nil {
		cmd.out.Error.Fatal("There are no scripts discovered in: %s", cmd.Config.File)
	}

	key := strings.TrimPrefix(c.Command.Name, "run:")
	if script, ok := cmd.Config.Scripts[key]; ok {
		cmd.out.Verbose.Printf("Executing '%s': %s", key, script.Description)
		cmd.addCommandPath()
		dir := filepath.Dir(cmd.Config.Path)

		// Concat the commands together adding the args to this command as args to the last step
		scriptCommands := strings.Join(script.Run, cmd.GetCommandSeparator()) + " " + strings.Join(c.Args(), " ")

		shellCmd := cmd.GetCommand(scriptCommands)
		shellCmd.Dir = dir

		cmd.out.Verbose.Printf("Executing '%s' as '%s'", key, scriptCommands)
		if exitCode := util.PassthruCommand(shellCmd); exitCode != 0 {
			cmd.out.Error.Printf("Error running project script '%s': %d", key, exitCode)
			os.Exit(exitCode)
		}
	} else {
		cmd.out.Error.Printf("Unrecognized script '%s'", key)
	}

	return nil
}

// Construct a command to execute a configured script.
// @see https://github.com/medhoover/gom/blob/staging/config/command.go
func (cmd *Project) GetCommand(val string) *exec.Cmd {
	if runtime.GOOS == "windows" {
		return exec.Command("cmd", "/c", val)
	}

	return exec.Command("sh", "-c", val)
}

// Get command separator based on platform.
func (cmd *Project) GetCommandSeparator() string {
	if runtime.GOOS == "windows" {
		return " & "
	}

	return " && "
}

// Override the PATH environment variable for further shell executions.
// This is used on POSIX systems for lookup of scripts.
func (cmd *Project) addCommandPath() {
	binDir := cmd.Config.Bin
	if binDir != "" {
		cmd.out.Verbose.Printf("Adding '%s' to the PATH for script execution.", binDir)
		path := os.Getenv("PATH")
		os.Setenv("PATH", fmt.Sprintf("%s%c%s", binDir, os.PathListSeparator, path))
	}
}

// Generate help details based on script configuration.
func (cmd *Project) ScriptRunHelp(script *ProjectScript) string {
	help := fmt.Sprintf("\n\nSCRIPT STEPS:\n\t- ")
	help = help + strings.Join(script.Run, "\n\t- ") + " [args...]\n"

	return help
}
<commit_msg>Verbose logging improvements - show working directory.<commit_after>package commands

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/phase2/rig/cli/util"
	"github.com/urfave/cli"
)

type Project struct {
	BaseCommand
	Config *ProjectConfig
}

func (cmd *Project) Commands() []cli.Command {
	cmd.Config = NewProjectConfig()

	command := cli.Command{
		Name:        "project",
		Usage:       "Run project-specific commands.",
		Description: "Run project-specific commands as part of development.\n\n\tConfigured scripts are driven by an Outrigger configuration file expected at your project root directory.\n\n\tBy default, this is a YAML file named 'outrigger.yml' with fallback to '.outrigger.yml'. It can be overridden by setting an environment variable $RIG_PROJECT_CONFIG_FILE.",
		Aliases:     []string{"run"},
		Category:    "Development",
		Before:      cmd.Before,
	}

	create := ProjectCreate{}
	command.Subcommands = append(command.Subcommands, create.Commands()...)

	sync := ProjectSync{}
	command.Subcommands = append(command.Subcommands, sync.Commands()...)

	if subcommands := cmd.GetScriptsAsSubcommands(command.Subcommands); subcommands != nil {
		command.Subcommands = append(command.Subcommands, subcommands...)
	}

	return []cli.Command{command}
}

// Processes script configuration into formal subcommands.
func (cmd *Project) GetScriptsAsSubcommands(otherSubcommands []cli.Command) []cli.Command {
	cmd.Config.ValidateProjectScripts(otherSubcommands)

	if cmd.Config.Scripts == nil {
		return nil
	}

	var commands = []cli.Command{}
	for id, script := range cmd.Config.Scripts {
		if len(script.Run) > 0 {
			command := cli.Command{
				Name:        fmt.Sprintf("run:%s", id),
				Usage:       script.Description,
				Description: fmt.Sprintf("%s\n\n\tThis command was configured in %s\n\n\tThere are %d steps in this script and any 'extra' arguments will be appended to the final step.", script.Description, cmd.Config.File, len(script.Run)),
				ArgsUsage:   "<args passed to last step>",
				Category:    "Configured Scripts",
				Before:      cmd.Before,
				Action:      cmd.Run,
			}

			if len(script.Alias) > 0 {
				command.Aliases = []string{script.Alias}
			}
			command.Description = command.Description + cmd.ScriptRunHelp(script)

			commands = append(commands, command)
		}
	}

	return commands
}

// Execute the selected projec script.
func (cmd *Project) Run(c *cli.Context) error {
	cmd.out.Verbose.Printf("Loaded project configuration from %s", cmd.Config.Path)
	if cmd.Config.Scripts == nil {
		cmd.out.Error.Fatal("There are no scripts discovered in: %s", cmd.Config.File)
	}

	key := strings.TrimPrefix(c.Command.Name, "run:")
	if script, ok := cmd.Config.Scripts[key]; ok {
		cmd.out.Verbose.Printf("Initializing project script '%s': %s", key, script.Description)
		cmd.addCommandPath()
		dir := filepath.Dir(cmd.Config.Path)

		// Concat the commands together adding the args to this command as args to the last step
		scriptCommands := strings.Join(script.Run, cmd.GetCommandSeparator()) + " " + strings.Join(c.Args(), " ")

		shellCmd := cmd.GetCommand(scriptCommands)
		shellCmd.Dir = dir
		cmd.out.Verbose.Printf("Script execution - Working Directory: %s", dir)

		cmd.out.Verbose.Printf("Executing '%s' as '%s'", key, scriptCommands)
		if exitCode := util.PassthruCommand(shellCmd); exitCode != 0 {
			cmd.out.Error.Printf("Error running project script '%s': %d", key, exitCode)
			os.Exit(exitCode)
		}
	} else {
		cmd.out.Error.Printf("Unrecognized script '%s'", key)
	}

	return nil
}

// Construct a command to execute a configured script.
// @see https://github.com/medhoover/gom/blob/staging/config/command.go
func (cmd *Project) GetCommand(val string) *exec.Cmd {
	if runtime.GOOS == "windows" {
		return exec.Command("cmd", "/c", val)
	}

	return exec.Command("sh", "-c", val)
}

// Get command separator based on platform.
func (cmd *Project) GetCommandSeparator() string {
	if runtime.GOOS == "windows" {
		return " & "
	}

	return " && "
}

// Override the PATH environment variable for further shell executions.
// This is used on POSIX systems for lookup of scripts.
func (cmd *Project) addCommandPath() {
	binDir := cmd.Config.Bin
	if binDir != "" {
		cmd.out.Verbose.Printf("Script execution - Adding to $PATH: %s", binDir)
		path := os.Getenv("PATH")
		os.Setenv("PATH", fmt.Sprintf("%s%c%s", binDir, os.PathListSeparator, path))
	}
}

// Generate help details based on script configuration.
func (cmd *Project) ScriptRunHelp(script *ProjectScript) string {
	help := fmt.Sprintf("\n\nSCRIPT STEPS:\n\t- ")
	help = help + strings.Join(script.Run, "\n\t- ") + " [args...]\n"

	return help
}
<|endoftext|>