<commit_before>// Copyright 2016 Telefónica Investigación y Desarrollo, S.A.U
//
// 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 github

import (
	"archive/tar"
	"compress/gzip"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/google/go-github/github"
)

// Config type.
type Config struct {
	Events    []string `json:"events"`
	EventsURL string   `json:"eventsUrl"`
}

// Manager type.
// Manager to use GitHub API.
type Manager struct {
	Config *Config
}

// NewManager is the constructor for a GitHug Manager.
func NewManager(config *Config) *Manager {
	return &Manager{config}
}

// Client type.
type Client struct {
	Client     *github.Client
	HTTPClient *http.Client
	Config     *Config
}

// NewClient is the constructor for a GitHub Client.
func (githubManager Manager) NewClient(httpClient *http.Client) *Client {
	client := github.NewClient(httpClient)
	return &Client{client, httpClient, githubManager.Config}
}

// GetUser to get the user profile in GitHub.
func (githubClient Client) GetUser() (user *github.User, err error) {
	user, _, err = githubClient.Client.Users.Get("")
	return
}

// GetOrganizations to retrieve the user's organizations.
func (githubClient Client) GetOrganizations() (organizations []github.Organization, err error) {
	organizations, _, err = githubClient.Client.Organizations.List("", nil)
	return
}

// GetRepositories to retrieve the user's repositories.
func (githubClient Client) GetRepositories() (repositories []github.Repository, err error) {
	repositories, _, err = githubClient.Client.Repositories.List("", nil)
	return
}

// CreateHook to create a hook on a repository.
func (githubClient Client) CreateHook(owner, repo string) (hookID *int, err error) {
	hookName := "web"
	hookConfig := &github.Hook{
		Name:   &hookName,
		Events: githubClient.Config.Events,
		Config: map[string]interface{}{
			"url":          githubClient.Config.EventsURL,
			"content_type": "json",
		},
	}
	h, _, error := githubClient.Client.Repositories.CreateHook(owner, repo, hookConfig)
	if error != nil {
		log.Println("Error creating hook", error)
		return nil, error
	}
	return h.ID, nil
}

// DeleteHook to remove a hook on a repository.
func (githubClient Client) DeleteHook(owner, repo string, hookID int) error {
	_, error := githubClient.Client.Repositories.DeleteHook(owner, repo, hookID)
	if error != nil {
		log.Println("Error deleting hook", error)
		return error
	}
	return nil
}

// GetFileContent to download a file from a user's repository.
func (githubClient Client) GetFileContent(owner, repo, path, ref string) ([]byte, error) {
	options := &github.RepositoryContentGetOptions{Ref: ref}
	fileContent, _, _, err := githubClient.Client.Repositories.GetContents(owner, repo, path, options)
	if err != nil {
		return nil, err
	}
	log.Printf("SHA: %s", *fileContent.SHA)
	decodedFileContent, _ := fileContent.Decode()
	return decodedFileContent, nil
}

// GetFileSHA to get the file SHA.
func (githubClient Client) GetFileSHA(owner, repo, path, ref string) (string, error) {
	log.Printf("GetFileSHA %s %s", path, ref)
	options := &github.RepositoryContentGetOptions{Ref: ref}
	fileContent, _, _, err := githubClient.Client.Repositories.GetContents(owner, repo, path, options)
	if err != nil {
		return "", err
	}
	return *fileContent.SHA, nil
}

// DownloadProjectContent to download a whole repository with a specific reference (SHA).
func (githubClient Client) DownloadProjectContent(owner, repo, ref string) (string, error) {
	options := &github.RepositoryContentGetOptions{Ref: ref}
	url, _, err := githubClient.Client.Repositories.GetArchiveLink(owner, repo, github.Tarball, options)
	if err != nil {
		log.Println("Error in DownloadProjectContent")
		return "", err
	}
	resp, err := githubClient.HTTPClient.Get(url.String())
	if err != nil {
		log.Println("Error getting the project tar.gz")
		return "", err
	}
	defer resp.Body.Close()

	dir, err := ioutil.TempDir("", "gocilla")
	if err != nil {
		log.Println("Error creating temporary directory")
		return "", err
	}
	log.Printf("Temporary directory: %s", dir)

	gzipReader, err := gzip.NewReader(resp.Body)
	if err != nil {
		log.Println("Error getting gzip reader")
		return "", err
	}

	tarReader := tar.NewReader(gzipReader)

	var baseDir string

	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		} else if err != nil {
			log.Println("Error next tarReader")
			return "", err
		}

		info := header.FileInfo()
		if baseDir == "" {
			if info.IsDir() {
				baseDir = header.Name
			} else {
				continue
			}
		}

		relativePath, err := filepath.Rel(baseDir, header.Name)
		if err != nil {
			log.Println("Error getting relativePath")
			return "", err
		}

		path := filepath.Join(dir, relativePath)
		log.Printf("New file: %s", path)
		if info.IsDir() {
			if err = os.MkdirAll(path, info.Mode()); err != nil {
				log.Println("Error making dir")
				return "", err
			}
			continue
		}

		file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
		if err != nil {
			log.Println("Error creating file")
			return "", err
		}
		defer file.Close()
		_, err = io.Copy(file, tarReader)
		if err != nil {
			log.Println("Error copying file")
			return "", err
		}
	}
	return dir, nil
}
<commit_msg>Bigger page size to retrieve all user's repositories<commit_after>// Copyright 2016 Telefónica Investigación y Desarrollo, S.A.U
//
// 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 github

import (
	"archive/tar"
	"compress/gzip"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/google/go-github/github"
)

const pageSize = 1000

// Config type.
type Config struct {
	Events    []string `json:"events"`
	EventsURL string   `json:"eventsUrl"`
}

// Manager type.
// Manager to use GitHub API.
type Manager struct {
	Config *Config
}

// NewManager is the constructor for a GitHug Manager.
func NewManager(config *Config) *Manager {
	return &Manager{config}
}

// Client type.
type Client struct {
	Client     *github.Client
	HTTPClient *http.Client
	Config     *Config
}

// NewClient is the constructor for a GitHub Client.
func (githubManager Manager) NewClient(httpClient *http.Client) *Client {
	client := github.NewClient(httpClient)
	return &Client{client, httpClient, githubManager.Config}
}

// GetUser to get the user profile in GitHub.
func (githubClient Client) GetUser() (user *github.User, err error) {
	user, _, err = githubClient.Client.Users.Get("")
	return
}

// GetOrganizations to retrieve the user's organizations.
func (githubClient Client) GetOrganizations() (organizations []github.Organization, err error) {
	organizations, _, err = githubClient.Client.Organizations.List("", nil)
	return
}

// GetRepositories to retrieve the user's repositories.
func (githubClient Client) GetRepositories() (repositories []github.Repository, err error) {
	listOptions := github.ListOptions{PerPage: pageSize}
	repositoryListOptions := &github.RepositoryListOptions{ListOptions: listOptions}
	repositories, _, err = githubClient.Client.Repositories.List("", repositoryListOptions)
	return
}

// CreateHook to create a hook on a repository.
func (githubClient Client) CreateHook(owner, repo string) (hookID *int, err error) {
	hookName := "web"
	hookConfig := &github.Hook{
		Name:   &hookName,
		Events: githubClient.Config.Events,
		Config: map[string]interface{}{
			"url":          githubClient.Config.EventsURL,
			"content_type": "json",
		},
	}
	h, _, error := githubClient.Client.Repositories.CreateHook(owner, repo, hookConfig)
	if error != nil {
		log.Println("Error creating hook", error)
		return nil, error
	}
	return h.ID, nil
}

// DeleteHook to remove a hook on a repository.
func (githubClient Client) DeleteHook(owner, repo string, hookID int) error {
	_, error := githubClient.Client.Repositories.DeleteHook(owner, repo, hookID)
	if error != nil {
		log.Println("Error deleting hook", error)
		return error
	}
	return nil
}

// GetFileContent to download a file from a user's repository.
func (githubClient Client) GetFileContent(owner, repo, path, ref string) ([]byte, error) {
	options := &github.RepositoryContentGetOptions{Ref: ref}
	fileContent, _, _, err := githubClient.Client.Repositories.GetContents(owner, repo, path, options)
	if err != nil {
		return nil, err
	}
	log.Printf("SHA: %s", *fileContent.SHA)
	decodedFileContent, _ := fileContent.Decode()
	return decodedFileContent, nil
}

// GetFileSHA to get the file SHA.
func (githubClient Client) GetFileSHA(owner, repo, path, ref string) (string, error) {
	log.Printf("GetFileSHA %s %s", path, ref)
	options := &github.RepositoryContentGetOptions{Ref: ref}
	fileContent, _, _, err := githubClient.Client.Repositories.GetContents(owner, repo, path, options)
	if err != nil {
		return "", err
	}
	return *fileContent.SHA, nil
}

// DownloadProjectContent to download a whole repository with a specific reference (SHA).
func (githubClient Client) DownloadProjectContent(owner, repo, ref string) (string, error) {
	options := &github.RepositoryContentGetOptions{Ref: ref}
	url, _, err := githubClient.Client.Repositories.GetArchiveLink(owner, repo, github.Tarball, options)
	if err != nil {
		log.Println("Error in DownloadProjectContent")
		return "", err
	}
	resp, err := githubClient.HTTPClient.Get(url.String())
	if err != nil {
		log.Println("Error getting the project tar.gz")
		return "", err
	}
	defer resp.Body.Close()

	dir, err := ioutil.TempDir("", "gocilla")
	if err != nil {
		log.Println("Error creating temporary directory")
		return "", err
	}
	log.Printf("Temporary directory: %s", dir)

	gzipReader, err := gzip.NewReader(resp.Body)
	if err != nil {
		log.Println("Error getting gzip reader")
		return "", err
	}

	tarReader := tar.NewReader(gzipReader)

	var baseDir string

	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		} else if err != nil {
			log.Println("Error next tarReader")
			return "", err
		}

		info := header.FileInfo()
		if baseDir == "" {
			if info.IsDir() {
				baseDir = header.Name
			} else {
				continue
			}
		}

		relativePath, err := filepath.Rel(baseDir, header.Name)
		if err != nil {
			log.Println("Error getting relativePath")
			return "", err
		}

		path := filepath.Join(dir, relativePath)
		log.Printf("New file: %s", path)
		if info.IsDir() {
			if err = os.MkdirAll(path, info.Mode()); err != nil {
				log.Println("Error making dir")
				return "", err
			}
			continue
		}

		file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
		if err != nil {
			log.Println("Error creating file")
			return "", err
		}
		defer file.Close()
		_, err = io.Copy(file, tarReader)
		if err != nil {
			log.Println("Error copying file")
			return "", err
		}
	}
	return dir, nil
}
<|endoftext|>


<commit_before>// clock counts down to or up from a target time.
package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	target := time.Date(2016, 4, 16, 0, 0, 0, 0, time.Local)
	printTargetTime(target)
	exitOnEnterKey()

	var previous time.Time
	for {
		now := time.Now()
		now = now.Add(time.Duration(-now.Nanosecond())) // truncate to second
		if now != previous {
			previous = now
			remaining := target.Sub(now)
			printTimeRemaining(now, remaining)
		}
		time.Sleep(50 * time.Millisecond)
	}
}

func exitOnEnterKey() {
	go func() {
		buf := make([]byte, 1)
		_, _ = os.Stdin.Read(buf)
		os.Exit(0)
	}()
}

const (
	indent          = "\t"
	highlight_start = "\x1b[1;36m"
	highlight_end   = "\x1b[0m"
)

func printTargetTime(target time.Time) {
	fmt.Print(indent, highlight_start, "Just Go", highlight_end, "\n")
	fmt.Print(indent, target.Format(time.UnixDate), "\n")
}

func printTimeRemaining(now time.Time, remaining time.Duration) {
	var sign string
	if remaining > 0 {
		sign = "-" // countdown is "T minus..."
	} else {
		sign = "+" // count up is "T plus..."
		remaining = -remaining
	}

	var days int
	if remaining >= 24*time.Hour {
		days = int(remaining / (24 * time.Hour))
		remaining = remaining % (24 * time.Hour)
	}

	fmt.Print(indent, now.Format(time.UnixDate), "  ", sign)
	if days > 0 {
		fmt.Print(days, "d")
	}
	fmt.Print(remaining, "          \r")
}
<commit_msg>May Day<commit_after>// clock counts down to or up from a target time.
package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	target := time.Date(2016, 5, 1, 0, 0, 0, 0, time.Local)
	printTargetTime(target)
	exitOnEnterKey()

	var previous time.Time
	for {
		now := time.Now()
		now = now.Add(time.Duration(-now.Nanosecond())) // truncate to second
		if now != previous {
			previous = now
			remaining := target.Sub(now)
			printTimeRemaining(now, remaining)
		}
		time.Sleep(50 * time.Millisecond)
	}
}

func exitOnEnterKey() {
	go func() {
		buf := make([]byte, 1)
		_, _ = os.Stdin.Read(buf)
		os.Exit(0)
	}()
}

const (
	indent          = "\t"
	highlight_start = "\x1b[1;36m"
	highlight_end   = "\x1b[0m"
)

func printTargetTime(target time.Time) {
	fmt.Print(indent, highlight_start, "Just Go", highlight_end, "\n")
	fmt.Print(indent, target.Format(time.UnixDate), "\n")
}

func printTimeRemaining(now time.Time, remaining time.Duration) {
	var sign string
	if remaining > 0 {
		sign = "-" // countdown is "T minus..."
	} else {
		sign = "+" // count up is "T plus..."
		remaining = -remaining
	}

	var days int
	if remaining >= 24*time.Hour {
		days = int(remaining / (24 * time.Hour))
		remaining = remaining % (24 * time.Hour)
	}

	fmt.Print(indent, now.Format(time.UnixDate), "  ", sign)
	if days > 0 {
		fmt.Print(days, "d")
	}
	fmt.Print(remaining, "          \r")
}
<|endoftext|>


<commit_before>package clock

import (
	"runtime"
	"sort"
	"sync"
	"time"
)

// Clock represents an interface to the functions in the standard library time
// package. Two implementations are available in the clock package. The first
// is a real-time clock which simply wraps the time package's functions. The
// second is a mock clock which will only make forward progress when
// programmatically adjusted.
type Clock interface {
	After(d time.Duration) <-chan time.Time
	AfterFunc(d time.Duration, f func()) *Timer
	Now() time.Time
	Sleep(d time.Duration)
	Tick(d time.Duration) <-chan time.Time
	Ticker(d time.Duration) *Ticker
	Timer(d time.Duration) *Timer
}

// New returns an instance of a real-time clock.
func New() Clock {
	return &clock{}
}

// clock implements a real-time clock by simply wrapping the time package functions.
type clock struct{}

func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) }

func (c *clock) AfterFunc(d time.Duration, f func()) *Timer {
	return &Timer{timer: time.AfterFunc(d, f)}
}

func (c *clock) Now() time.Time { return time.Now() }

func (c *clock) Sleep(d time.Duration) { time.Sleep(d) }

func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) }

func (c *clock) Ticker(d time.Duration) *Ticker {
	t := time.NewTicker(d)
	return &Ticker{C: t.C, ticker: t}
}

func (c *clock) Timer(d time.Duration) *Timer {
	t := time.NewTimer(d)
	return &Timer{C: t.C, timer: t}
}

// Mock represents a mock clock that only moves forward programmically.
// It can be preferable to a real-time clock when testing time-based functionality.
type Mock struct {
	mu     sync.Mutex
	now    time.Time   // current time
	timers clockTimers // tickers & timers
}

// NewMock returns an instance of a mock clock.
// The current time of the mock clock on initialization is the Unix epoch.
func NewMock() *Mock {
	return &Mock{now: time.Unix(0, 0)}
}

// Add moves the current time of the mock clock forward by the duration.
// This should only be called from a single goroutine at a time.
func (m *Mock) Add(d time.Duration) {
	// Calculate the final current time.
	t := m.now.Add(d)

	// Continue to execute timers until there are no more before the new time.
	for {
		if !m.runNextTimer(t) {
			break
		}
	}

	// Ensure that we end with the new time.
	m.mu.Lock()
	m.now = t
	m.mu.Unlock()

	// Give a small buffer to make sure the other goroutines get handled.
	gosched()
}

// Sets the current time of the mock clock to a specific one.
// This should only be called from a single goroutine at a time.
func (m *Mock) Set(t time.Time) {
	// Continue to execute timers until there are no more before the new time.
	for {
		if !m.runNextTimer(t) {
			break
		}
	}

	// Ensure that we end with the new time.
	m.mu.Lock()
	m.now = t
	m.mu.Unlock()

	// Give a small buffer to make sure the other goroutines get handled.
	gosched()
}

// runNextTimer executes the next timer in chronological order and moves the
// current time to the timer's next tick time. The next time is not executed if
// it's next time if after the max time. Returns true if a timer is executed.
func (m *Mock) runNextTimer(max time.Time) bool {
	m.mu.Lock()

	// Sort timers by time.
	sort.Sort(m.timers)

	// If we have no more timers then exit.
	if len(m.timers) == 0 {
		m.mu.Unlock()
		return false
	}

	// Retrieve next timer. Exit if next tick is after new time.
	t := m.timers[0]
	if t.Next().After(max) {
		m.mu.Unlock()
		return false
	}

	// Move "now" forward and unlock clock.
	m.now = t.Next()
	m.mu.Unlock()

	// Execute timer.
	t.Tick(m.now)
	return true
}

// After waits for the duration to elapse and then sends the current time on the returned channel.
func (m *Mock) After(d time.Duration) <-chan time.Time {
	return m.Timer(d).C
}

// AfterFunc waits for the duration to elapse and then executes a function.
// A Timer is returned that can be stopped.
func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer {
	t := m.Timer(d)
	t.C = nil
	t.fn = f
	return t
}

// Now returns the current wall time on the mock clock.
func (m *Mock) Now() time.Time {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.now
}

// Sleep pauses the goroutine for the given duration on the mock clock.
// The clock must be moved forward in a separate goroutine.
func (m *Mock) Sleep(d time.Duration) {
	<-m.After(d)
}

// Tick is a convenience function for Ticker().
// It will return a ticker channel that cannot be stopped.
func (m *Mock) Tick(d time.Duration) <-chan time.Time {
	return m.Ticker(d).C
}

// Ticker creates a new instance of Ticker.
func (m *Mock) Ticker(d time.Duration) *Ticker {
	m.mu.Lock()
	defer m.mu.Unlock()
	ch := make(chan time.Time, 1)
	t := &Ticker{
		C:    ch,
		c:    ch,
		mock: m,
		d:    d,
		next: m.now.Add(d),
	}
	m.timers = append(m.timers, (*internalTicker)(t))
	return t
}

// Timer creates a new instance of Timer.
func (m *Mock) Timer(d time.Duration) *Timer {
	m.mu.Lock()
	defer m.mu.Unlock()
	ch := make(chan time.Time, 1)
	t := &Timer{
		C:    ch,
		c:    ch,
		mock: m,
		next: m.now.Add(d),
	}
	m.timers = append(m.timers, (*internalTimer)(t))
	return t
}

func (m *Mock) removeClockTimer(t clockTimer) {
	m.mu.Lock()
	defer m.mu.Unlock()
	for i, timer := range m.timers {
		if timer == t {
			copy(m.timers[i:], m.timers[i+1:])
			m.timers[len(m.timers)-1] = nil
			m.timers = m.timers[:len(m.timers)-1]
			break
		}
	}
	sort.Sort(m.timers)
}

// clockTimer represents an object with an associated start time.
type clockTimer interface {
	Next() time.Time
	Tick(time.Time)
}

// clockTimers represents a list of sortable timers.
type clockTimers []clockTimer

func (a clockTimers) Len() int           { return len(a) }
func (a clockTimers) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) }

// Timer represents a single event.
// The current time will be sent on C, unless the timer was created by AfterFunc.
type Timer struct {
	C     <-chan time.Time
	c     chan time.Time
	timer *time.Timer // realtime impl, if set
	next  time.Time   // next tick time
	mock  *Mock       // mock clock, if set
	fn    func()      // AfterFunc function, if set
}

// Stop turns off the ticker.
func (t *Timer) Stop() {
	if t.timer != nil {
		t.timer.Stop()
	} else {
		t.mock.removeClockTimer((*internalTimer)(t))
	}
}

type internalTimer Timer

func (t *internalTimer) Next() time.Time { return t.next }
func (t *internalTimer) Tick(now time.Time) {
	if t.fn != nil {
		t.fn()
	} else {
		t.c <- now
	}
	t.mock.removeClockTimer((*internalTimer)(t))
	gosched()
}

// Ticker holds a channel that receives "ticks" at regular intervals.
type Ticker struct {
	C      <-chan time.Time
	c      chan time.Time
	ticker *time.Ticker  // realtime impl, if set
	next   time.Time     // next tick time
	mock   *Mock         // mock clock, if set
	d      time.Duration // time between ticks
}

// Stop turns off the ticker.
func (t *Ticker) Stop() {
	if t.ticker != nil {
		t.ticker.Stop()
	} else {
		t.mock.removeClockTimer((*internalTicker)(t))
	}
}

type internalTicker Ticker

func (t *internalTicker) Next() time.Time { return t.next }
func (t *internalTicker) Tick(now time.Time) {
	select {
	case t.c <- now:
	default:
	}
	t.next = now.Add(t.d)
	gosched()
}

// Sleep momentarily so that other goroutines can process.
func gosched() { runtime.Gosched() }
<commit_msg>Revert gosched() to previous version<commit_after>package clock

import (
	"sort"
	"sync"
	"time"
)

// Clock represents an interface to the functions in the standard library time
// package. Two implementations are available in the clock package. The first
// is a real-time clock which simply wraps the time package's functions. The
// second is a mock clock which will only make forward progress when
// programmatically adjusted.
type Clock interface {
	After(d time.Duration) <-chan time.Time
	AfterFunc(d time.Duration, f func()) *Timer
	Now() time.Time
	Sleep(d time.Duration)
	Tick(d time.Duration) <-chan time.Time
	Ticker(d time.Duration) *Ticker
	Timer(d time.Duration) *Timer
}

// New returns an instance of a real-time clock.
func New() Clock {
	return &clock{}
}

// clock implements a real-time clock by simply wrapping the time package functions.
type clock struct{}

func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) }

func (c *clock) AfterFunc(d time.Duration, f func()) *Timer {
	return &Timer{timer: time.AfterFunc(d, f)}
}

func (c *clock) Now() time.Time { return time.Now() }

func (c *clock) Sleep(d time.Duration) { time.Sleep(d) }

func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) }

func (c *clock) Ticker(d time.Duration) *Ticker {
	t := time.NewTicker(d)
	return &Ticker{C: t.C, ticker: t}
}

func (c *clock) Timer(d time.Duration) *Timer {
	t := time.NewTimer(d)
	return &Timer{C: t.C, timer: t}
}

// Mock represents a mock clock that only moves forward programmically.
// It can be preferable to a real-time clock when testing time-based functionality.
type Mock struct {
	mu     sync.Mutex
	now    time.Time   // current time
	timers clockTimers // tickers & timers
}

// NewMock returns an instance of a mock clock.
// The current time of the mock clock on initialization is the Unix epoch.
func NewMock() *Mock {
	return &Mock{now: time.Unix(0, 0)}
}

// Add moves the current time of the mock clock forward by the duration.
// This should only be called from a single goroutine at a time.
func (m *Mock) Add(d time.Duration) {
	// Calculate the final current time.
	t := m.now.Add(d)

	// Continue to execute timers until there are no more before the new time.
	for {
		if !m.runNextTimer(t) {
			break
		}
	}

	// Ensure that we end with the new time.
	m.mu.Lock()
	m.now = t
	m.mu.Unlock()

	// Give a small buffer to make sure the other goroutines get handled.
	gosched()
}

// Sets the current time of the mock clock to a specific one.
// This should only be called from a single goroutine at a time.
func (m *Mock) Set(t time.Time) {
	// Continue to execute timers until there are no more before the new time.
	for {
		if !m.runNextTimer(t) {
			break
		}
	}

	// Ensure that we end with the new time.
	m.mu.Lock()
	m.now = t
	m.mu.Unlock()

	// Give a small buffer to make sure the other goroutines get handled.
	gosched()
}

// runNextTimer executes the next timer in chronological order and moves the
// current time to the timer's next tick time. The next time is not executed if
// it's next time if after the max time. Returns true if a timer is executed.
func (m *Mock) runNextTimer(max time.Time) bool {
	m.mu.Lock()

	// Sort timers by time.
	sort.Sort(m.timers)

	// If we have no more timers then exit.
	if len(m.timers) == 0 {
		m.mu.Unlock()
		return false
	}

	// Retrieve next timer. Exit if next tick is after new time.
	t := m.timers[0]
	if t.Next().After(max) {
		m.mu.Unlock()
		return false
	}

	// Move "now" forward and unlock clock.
	m.now = t.Next()
	m.mu.Unlock()

	// Execute timer.
	t.Tick(m.now)
	return true
}

// After waits for the duration to elapse and then sends the current time on the returned channel.
func (m *Mock) After(d time.Duration) <-chan time.Time {
	return m.Timer(d).C
}

// AfterFunc waits for the duration to elapse and then executes a function.
// A Timer is returned that can be stopped.
func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer {
	t := m.Timer(d)
	t.C = nil
	t.fn = f
	return t
}

// Now returns the current wall time on the mock clock.
func (m *Mock) Now() time.Time {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.now
}

// Sleep pauses the goroutine for the given duration on the mock clock.
// The clock must be moved forward in a separate goroutine.
func (m *Mock) Sleep(d time.Duration) {
	<-m.After(d)
}

// Tick is a convenience function for Ticker().
// It will return a ticker channel that cannot be stopped.
func (m *Mock) Tick(d time.Duration) <-chan time.Time {
	return m.Ticker(d).C
}

// Ticker creates a new instance of Ticker.
func (m *Mock) Ticker(d time.Duration) *Ticker {
	m.mu.Lock()
	defer m.mu.Unlock()
	ch := make(chan time.Time, 1)
	t := &Ticker{
		C:    ch,
		c:    ch,
		mock: m,
		d:    d,
		next: m.now.Add(d),
	}
	m.timers = append(m.timers, (*internalTicker)(t))
	return t
}

// Timer creates a new instance of Timer.
func (m *Mock) Timer(d time.Duration) *Timer {
	m.mu.Lock()
	defer m.mu.Unlock()
	ch := make(chan time.Time, 1)
	t := &Timer{
		C:    ch,
		c:    ch,
		mock: m,
		next: m.now.Add(d),
	}
	m.timers = append(m.timers, (*internalTimer)(t))
	return t
}

func (m *Mock) removeClockTimer(t clockTimer) {
	m.mu.Lock()
	defer m.mu.Unlock()
	for i, timer := range m.timers {
		if timer == t {
			copy(m.timers[i:], m.timers[i+1:])
			m.timers[len(m.timers)-1] = nil
			m.timers = m.timers[:len(m.timers)-1]
			break
		}
	}
	sort.Sort(m.timers)
}

// clockTimer represents an object with an associated start time.
type clockTimer interface {
	Next() time.Time
	Tick(time.Time)
}

// clockTimers represents a list of sortable timers.
type clockTimers []clockTimer

func (a clockTimers) Len() int           { return len(a) }
func (a clockTimers) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) }

// Timer represents a single event.
// The current time will be sent on C, unless the timer was created by AfterFunc.
type Timer struct {
	C     <-chan time.Time
	c     chan time.Time
	timer *time.Timer // realtime impl, if set
	next  time.Time   // next tick time
	mock  *Mock       // mock clock, if set
	fn    func()      // AfterFunc function, if set
}

// Stop turns off the ticker.
func (t *Timer) Stop() {
	if t.timer != nil {
		t.timer.Stop()
	} else {
		t.mock.removeClockTimer((*internalTimer)(t))
	}
}

type internalTimer Timer

func (t *internalTimer) Next() time.Time { return t.next }
func (t *internalTimer) Tick(now time.Time) {
	if t.fn != nil {
		t.fn()
	} else {
		t.c <- now
	}
	t.mock.removeClockTimer((*internalTimer)(t))
	gosched()
}

// Ticker holds a channel that receives "ticks" at regular intervals.
type Ticker struct {
	C      <-chan time.Time
	c      chan time.Time
	ticker *time.Ticker  // realtime impl, if set
	next   time.Time     // next tick time
	mock   *Mock         // mock clock, if set
	d      time.Duration // time between ticks
}

// Stop turns off the ticker.
func (t *Ticker) Stop() {
	if t.ticker != nil {
		t.ticker.Stop()
	} else {
		t.mock.removeClockTimer((*internalTicker)(t))
	}
}

type internalTicker Ticker

func (t *internalTicker) Next() time.Time { return t.next }
func (t *internalTicker) Tick(now time.Time) {
	select {
	case t.c <- now:
	default:
	}
	t.next = now.Add(t.d)
	gosched()
}

// Sleep momentarily so that other goroutines can process.
func gosched() { time.Sleep(1 * time.Millisecond) }
<|endoftext|>


<commit_before>package color

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/mattn/go-colorable"
	"github.com/mattn/go-isatty"
)

// NoColor defines if the output is colorized or not. It's dynamically set to
// false or true based on the stdout's file descriptor referring to a terminal
// or not. This is a global option and affects all colors. For more control
// over each color block use the methods DisableColor() individually.
var NoColor = !isatty.IsTerminal(os.Stdout.Fd())

// Color defines a custom color object which is defined by SGR parameters.
type Color struct {
	params  []Attribute
	noColor *bool
}

// Attribute defines a single SGR Code
type Attribute int

const escape = "\x1b"

// Base attributes
const (
	Reset Attribute = iota
	Bold
	Faint
	Italic
	Underline
	BlinkSlow
	BlinkRapid
	ReverseVideo
	Concealed
	CrossedOut
)

// Foreground text colors
const (
	FgBlack Attribute = iota + 30
	FgRed
	FgGreen
	FgYellow
	FgBlue
	FgMagenta
	FgCyan
	FgWhite
)

// Foreground Hi-Intensity text colors
const (
	FgHiBlack Attribute = iota + 90
	FgHiRed
	FgHiGreen
	FgHiYellow
	FgHiBlue
	FgHiMagenta
	FgHiCyan
	FgHiWhite
)

// Background text colors
const (
	BgBlack Attribute = iota + 40
	BgRed
	BgGreen
	BgYellow
	BgBlue
	BgMagenta
	BgCyan
	BgWhite
)

// Background Hi-Intensity text colors
const (
	BgHiBlack Attribute = iota + 100
	BgHiRed
	BgHiGreen
	BgHiYellow
	BgHiBlue
	BgHiMagenta
	BgHiCyan
	BgHiWhite
)

// New returns a newly created color object.
func New(value ...Attribute) *Color {
	c := &Color{params: make([]Attribute, 0)}
	c.Add(value...)
	return c
}

// Set sets the given parameters immediately. It will change the color of
// output with the given SGR parameters until color.Unset() is called.
func Set(p ...Attribute) *Color {
	c := New(p...)
	c.Set()
	return c
}

// Unset resets all escape attributes and clears the output. Usually should
// be called after Set().
func Unset() {
	if NoColor {
		return
	}

	fmt.Fprintf(Output, "%s[%dm", escape, Reset)
}

// Set sets the SGR sequence.
func (c *Color) Set() *Color {
	if c.isNoColorSet() {
		return c
	}

	fmt.Fprintf(Output, c.format())
	return c
}

func (c *Color) unset() {
	if c.isNoColorSet() {
		return
	}

	Unset()
}

// Add is used to chain SGR parameters. Use as many as parameters to combine
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
func (c *Color) Add(value ...Attribute) *Color {
	c.params = append(c.params, value...)
	return c
}

func (c *Color) prepend(value Attribute) {
	c.params = append(c.params, 0)
	copy(c.params[1:], c.params[0:])
	c.params[0] = value
}

// Output defines the standard output of the print functions. By default
// os.Stdout is used.
var Output = colorable.NewColorableStdout()

// Print formats using the default formats for its operands and writes to
// standard output. Spaces are added between operands when neither is a
// string. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Print(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprint(Output, a...)
}

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
// This is the standard fmt.Printf() method wrapped with the given color.
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintf(Output, format, a...)
}

// Println formats using the default formats for its operands and writes to
// standard output. Spaces are always added between operands and a newline is
// appended. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Println(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintln(Output, a...)
}

// PrintFunc returns a new function that prints the passed arguments as
// colorized with color.Print().
func (c *Color) PrintFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Print(a...) }
}

// PrintfFunc returns a new function that prints the passed arguments as
// colorized with color.Printf().
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
	return func(format string, a ...interface{}) { c.Printf(format, a...) }
}

// PrintlnFunc returns a new function that prints the passed arguments as
// colorized with color.Println().
func (c *Color) PrintlnFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Println(a...) }
}

// SprintFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprint(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output, example:
//
//	put := New(FgYellow).SprintFunc()
//	fmt.Fprintf(color.Output, "This is a %s", put("warning"))
func (c *Color) SprintFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprint(a...))
	}
}

// SprintfFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
	return func(format string, a ...interface{}) string {
		return c.wrap(fmt.Sprintf(format, a...))
	}
}

// SprintlnFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprintln(a...))
	}
}

// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m"
// an example output might be: "1;36" -> bold cyan
func (c *Color) sequence() string {
	format := make([]string, len(c.params))
	for i, v := range c.params {
		format[i] = strconv.Itoa(int(v))
	}

	return strings.Join(format, ";")
}

// wrap wraps the s string with the colors attributes. The string is ready to
// be printed.
func (c *Color) wrap(s string) string {
	if c.isNoColorSet() {
		return s
	}

	return c.format() + s + c.unformat()
}

func (c *Color) format() string {
	return fmt.Sprintf("%s[%sm", escape, c.sequence())
}

func (c *Color) unformat() string {
	return fmt.Sprintf("%s[%dm", escape, Reset)
}

// DisableColor disables the color output. Useful to not change any existing
// code and still being able to output. Can be used for flags like
// "--no-color". To enable back use EnableColor() method.
func (c *Color) DisableColor() {
	c.noColor = boolPtr(true)
}

// EnableColor enables the color output. Use it in conjuction with
// DisableColor(). Otherwise this method has no side effects.
func (c *Color) EnableColor() {
	c.noColor = boolPtr(false)
}

func (c *Color) isNoColorSet() bool {
	// check first if we have user setted action
	if c.noColor != nil {
		return *c.noColor
	}

	// if not return the global option, which is disabled by default
	return NoColor
}

// Equals returns a boolean value indicating whether two colors are equal.
func (c *Color) Equals(c2 *Color) bool {
	if len(c.params) != len(c2.params) {
		return false
	}

	for _, attr := range c.params {
		if !c2.attrExists(attr) {
			return false
		}
	}

	return true
}

func (c *Color) attrExists(a Attribute) bool {
	for _, attr := range c.params {
		if attr == a {
			return true
		}
	}

	return false
}

func boolPtr(v bool) *bool {
	return &v
}

// colorsCache is used to reduce the count of created Color objects and
// allows to reuse already created objects with required Attribute.
var colorsCache = make(map[Attribute]*Color)

var colorsCacheMu = new(sync.Mutex) // protects colorsCache

func getCachedColor(p Attribute) *Color {
	colorsCacheMu.Lock()
	defer colorsCacheMu.Unlock()

	c, ok := colorsCache[p]
	if !ok {
		c = New(p)
		colorsCache[p] = c
	}

	return c
}

func colorPrint(format string, p Attribute, a ...interface{}) {
	c := getCachedColor(p)

	if !strings.HasSuffix(format, "\n") {
		format += "\n"
	}

	if len(a) == 0 {
		c.Print(format)
	} else {
		c.Printf(format, a...)
	}
}

func colorString(format string, p Attribute, a ...interface{}) string {
	c := getCachedColor(p)

	if len(a) == 0 {
		return c.SprintFunc()(format)
	}

	return c.SprintfFunc()(format, a...)
}

// Black is an convenient helper function to print with black foreground. A
// newline is appended to format by default.
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }

// Red is an convenient helper function to print with red foreground. A
// newline is appended to format by default.
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }

// Green is an convenient helper function to print with green foreground. A
// newline is appended to format by default.
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }

// Yellow is an convenient helper function to print with yellow foreground.
// A newline is appended to format by default.
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }

// Blue is an convenient helper function to print with blue foreground. A
// newline is appended to format by default.
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }

// Magenta is an convenient helper function to print with magenta foreground.
// A newline is appended to format by default.
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }

// Cyan is an convenient helper function to print with cyan foreground. A
// newline is appended to format by default.
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }

// White is an convenient helper function to print with white foreground. A
// newline is appended to format by default.
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }

// BlackString is an convenient helper function to return a string with black
// foreground.
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }

// RedString is an convenient helper function to return a string with red
// foreground.
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }

// GreenString is an convenient helper function to return a string with green
// foreground.
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }

// YellowString is an convenient helper function to return a string with yellow
// foreground.
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }

// BlueString is an convenient helper function to return a string with blue
// foreground.
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }

// MagentaString is an convenient helper function to return a string with magenta
// foreground.
func MagentaString(format string, a ...interface{}) string {
	return colorString(format, FgMagenta, a...)
}

// CyanString is an convenient helper function to return a string with cyan
// foreground.
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }

// WhiteString is an convenient helper function to return a string with white
// foreground.
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
<commit_msg>set NoColor for "dumb" $TERMinals<commit_after>package color

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/mattn/go-colorable"
	"github.com/mattn/go-isatty"
)

// NoColor defines if the output is colorized or not. It's dynamically set to
// false or true based on the stdout's file descriptor referring to a terminal
// or not. This is a global option and affects all colors. For more control
// over each color block use the methods DisableColor() individually.
var NoColor = !isatty.IsTerminal(os.Stdout.Fd()) || os.Getenv("TERM") == "dumb"

// Color defines a custom color object which is defined by SGR parameters.
type Color struct {
	params  []Attribute
	noColor *bool
}

// Attribute defines a single SGR Code
type Attribute int

const escape = "\x1b"

// Base attributes
const (
	Reset Attribute = iota
	Bold
	Faint
	Italic
	Underline
	BlinkSlow
	BlinkRapid
	ReverseVideo
	Concealed
	CrossedOut
)

// Foreground text colors
const (
	FgBlack Attribute = iota + 30
	FgRed
	FgGreen
	FgYellow
	FgBlue
	FgMagenta
	FgCyan
	FgWhite
)

// Foreground Hi-Intensity text colors
const (
	FgHiBlack Attribute = iota + 90
	FgHiRed
	FgHiGreen
	FgHiYellow
	FgHiBlue
	FgHiMagenta
	FgHiCyan
	FgHiWhite
)

// Background text colors
const (
	BgBlack Attribute = iota + 40
	BgRed
	BgGreen
	BgYellow
	BgBlue
	BgMagenta
	BgCyan
	BgWhite
)

// Background Hi-Intensity text colors
const (
	BgHiBlack Attribute = iota + 100
	BgHiRed
	BgHiGreen
	BgHiYellow
	BgHiBlue
	BgHiMagenta
	BgHiCyan
	BgHiWhite
)

// New returns a newly created color object.
func New(value ...Attribute) *Color {
	c := &Color{params: make([]Attribute, 0)}
	c.Add(value...)
	return c
}

// Set sets the given parameters immediately. It will change the color of
// output with the given SGR parameters until color.Unset() is called.
func Set(p ...Attribute) *Color {
	c := New(p...)
	c.Set()
	return c
}

// Unset resets all escape attributes and clears the output. Usually should
// be called after Set().
func Unset() {
	if NoColor {
		return
	}

	fmt.Fprintf(Output, "%s[%dm", escape, Reset)
}

// Set sets the SGR sequence.
func (c *Color) Set() *Color {
	if c.isNoColorSet() {
		return c
	}

	fmt.Fprintf(Output, c.format())
	return c
}

func (c *Color) unset() {
	if c.isNoColorSet() {
		return
	}

	Unset()
}

// Add is used to chain SGR parameters. Use as many as parameters to combine
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
func (c *Color) Add(value ...Attribute) *Color {
	c.params = append(c.params, value...)
	return c
}

func (c *Color) prepend(value Attribute) {
	c.params = append(c.params, 0)
	copy(c.params[1:], c.params[0:])
	c.params[0] = value
}

// Output defines the standard output of the print functions. By default
// os.Stdout is used.
var Output = colorable.NewColorableStdout()

// Print formats using the default formats for its operands and writes to
// standard output. Spaces are added between operands when neither is a
// string. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Print(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprint(Output, a...)
}

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
// This is the standard fmt.Printf() method wrapped with the given color.
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintf(Output, format, a...)
}

// Println formats using the default formats for its operands and writes to
// standard output. Spaces are always added between operands and a newline is
// appended. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Println(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintln(Output, a...)
}

// PrintFunc returns a new function that prints the passed arguments as
// colorized with color.Print().
func (c *Color) PrintFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Print(a...) }
}

// PrintfFunc returns a new function that prints the passed arguments as
// colorized with color.Printf().
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
	return func(format string, a ...interface{}) { c.Printf(format, a...) }
}

// PrintlnFunc returns a new function that prints the passed arguments as
// colorized with color.Println().
func (c *Color) PrintlnFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Println(a...) }
}

// SprintFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprint(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output, example:
//
//	put := New(FgYellow).SprintFunc()
//	fmt.Fprintf(color.Output, "This is a %s", put("warning"))
func (c *Color) SprintFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprint(a...))
	}
}

// SprintfFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
	return func(format string, a ...interface{}) string {
		return c.wrap(fmt.Sprintf(format, a...))
	}
}

// SprintlnFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprintln(a...))
	}
}

// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m"
// an example output might be: "1;36" -> bold cyan
func (c *Color) sequence() string {
	format := make([]string, len(c.params))
	for i, v := range c.params {
		format[i] = strconv.Itoa(int(v))
	}

	return strings.Join(format, ";")
}

// wrap wraps the s string with the colors attributes. The string is ready to
// be printed.
func (c *Color) wrap(s string) string {
	if c.isNoColorSet() {
		return s
	}

	return c.format() + s + c.unformat()
}

func (c *Color) format() string {
	return fmt.Sprintf("%s[%sm", escape, c.sequence())
}

func (c *Color) unformat() string {
	return fmt.Sprintf("%s[%dm", escape, Reset)
}

// DisableColor disables the color output. Useful to not change any existing
// code and still being able to output. Can be used for flags like
// "--no-color". To enable back use EnableColor() method.
func (c *Color) DisableColor() {
	c.noColor = boolPtr(true)
}

// EnableColor enables the color output. Use it in conjuction with
// DisableColor(). Otherwise this method has no side effects.
func (c *Color) EnableColor() {
	c.noColor = boolPtr(false)
}

func (c *Color) isNoColorSet() bool {
	// check first if we have user setted action
	if c.noColor != nil {
		return *c.noColor
	}

	// if not return the global option, which is disabled by default
	return NoColor
}

// Equals returns a boolean value indicating whether two colors are equal.
func (c *Color) Equals(c2 *Color) bool {
	if len(c.params) != len(c2.params) {
		return false
	}

	for _, attr := range c.params {
		if !c2.attrExists(attr) {
			return false
		}
	}

	return true
}

func (c *Color) attrExists(a Attribute) bool {
	for _, attr := range c.params {
		if attr == a {
			return true
		}
	}

	return false
}

func boolPtr(v bool) *bool {
	return &v
}

// colorsCache is used to reduce the count of created Color objects and
// allows to reuse already created objects with required Attribute.
var colorsCache = make(map[Attribute]*Color)

var colorsCacheMu = new(sync.Mutex) // protects colorsCache

func getCachedColor(p Attribute) *Color {
	colorsCacheMu.Lock()
	defer colorsCacheMu.Unlock()

	c, ok := colorsCache[p]
	if !ok {
		c = New(p)
		colorsCache[p] = c
	}

	return c
}

func colorPrint(format string, p Attribute, a ...interface{}) {
	c := getCachedColor(p)

	if !strings.HasSuffix(format, "\n") {
		format += "\n"
	}

	if len(a) == 0 {
		c.Print(format)
	} else {
		c.Printf(format, a...)
	}
}

func colorString(format string, p Attribute, a ...interface{}) string {
	c := getCachedColor(p)

	if len(a) == 0 {
		return c.SprintFunc()(format)
	}

	return c.SprintfFunc()(format, a...)
}

// Black is an convenient helper function to print with black foreground. A
// newline is appended to format by default.
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }

// Red is an convenient helper function to print with red foreground. A
// newline is appended to format by default.
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }

// Green is an convenient helper function to print with green foreground. A
// newline is appended to format by default.
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }

// Yellow is an convenient helper function to print with yellow foreground.
// A newline is appended to format by default.
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }

// Blue is an convenient helper function to print with blue foreground. A
// newline is appended to format by default.
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }

// Magenta is an convenient helper function to print with magenta foreground.
// A newline is appended to format by default.
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }

// Cyan is an convenient helper function to print with cyan foreground. A
// newline is appended to format by default.
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }

// White is an convenient helper function to print with white foreground. A
// newline is appended to format by default.
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }

// BlackString is an convenient helper function to return a string with black
// foreground.
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }

// RedString is an convenient helper function to return a string with red
// foreground.
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }

// GreenString is an convenient helper function to return a string with green
// foreground.
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }

// YellowString is an convenient helper function to return a string with yellow
// foreground.
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }

// BlueString is an convenient helper function to return a string with blue
// foreground.
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }

// MagentaString is an convenient helper function to return a string with magenta
// foreground.
func MagentaString(format string, a ...interface{}) string {
	return colorString(format, FgMagenta, a...)
}

// CyanString is an convenient helper function to return a string with cyan
// foreground.
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }

// WhiteString is an convenient helper function to return a string with white
// foreground.
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"syscall"

	"github.com/kelseyhightower/confd/backends"
	"github.com/kelseyhightower/confd/log"
	"github.com/kelseyhightower/confd/resource/template"
)

func main() {
	flag.Parse()
	if printVersion {
		fmt.Printf("confd %s\n", Version)
		os.Exit(0)
	}
	if err := initConfig(); err != nil {
		log.Fatal(err.Error())
	}
	log.Notice("Starting confd")
	storeClient, err := backends.New(backendsConfig)
	if err != nil {
		log.Fatal(err.Error())
	}
	templateConfig.StoreClient = storeClient
	if onetime {
		if err := template.Process(templateConfig); err != nil {
			os.Exit(1)
		}
		os.Exit(0)
	}
	stopChan := make(chan bool)
	doneChan := make(chan bool)
	errChan := make(chan error, 10)
	var processor template.Processor
	switch {
	case watch:
		processor = template.WatchProcessor(templateConfig, stopChan, doneChan, errChan)
	default:
		processor = template.IntervalProcessor(templateConfig, stopChan, doneChan, errChan, config.Interval)
	}
	go processor.Process()
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	for {
		select {
		case err := <-errChan:
			log.Error(err.Error())
		case s := <-signalChan:
			log.Info(fmt.Sprintf("Captured %v. Exiting...", s))
			close(doneChan)
		case <-doneChan:
			os.Exit(0)
		}
	}
}
<commit_msg>Enable watch from the config file<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"syscall"

	"github.com/kelseyhightower/confd/backends"
	"github.com/kelseyhightower/confd/log"
	"github.com/kelseyhightower/confd/resource/template"
)

func main() {
	flag.Parse()
	if printVersion {
		fmt.Printf("confd %s\n", Version)
		os.Exit(0)
	}
	if err := initConfig(); err != nil {
		log.Fatal(err.Error())
	}
	log.Notice("Starting confd")
	storeClient, err := backends.New(backendsConfig)
	if err != nil {
		log.Fatal(err.Error())
	}
	templateConfig.StoreClient = storeClient
	if onetime {
		if err := template.Process(templateConfig); err != nil {
			os.Exit(1)
		}
		os.Exit(0)
	}
	stopChan := make(chan bool)
	doneChan := make(chan bool)
	errChan := make(chan error, 10)
	var processor template.Processor
	switch {
	case config.Watch:
		processor = template.WatchProcessor(templateConfig, stopChan, doneChan, errChan)
	default:
		processor = template.IntervalProcessor(templateConfig, stopChan, doneChan, errChan, config.Interval)
	}
	go processor.Process()
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	for {
		select {
		case err := <-errChan:
			log.Error(err.Error())
		case s := <-signalChan:
			log.Info(fmt.Sprintf("Captured %v. Exiting...", s))
			close(doneChan)
		case <-doneChan:
			os.Exit(0)
		}
	}
}
<|endoftext|>


<commit_before>package main

const NGINX_BUILD_VERSION = "0.6.7"

// nginx
const (
	NGINX_VERSION             = "1.9.10"
	NGINX_DOWNLOAD_URL_PREFIX = "http://nginx.org/download"
)

// pcre
const (
	PCRE_VERSION             = "8.38"
	PCRE_DOWNLOAD_URL_PREFIX = "http://ftp.csx.cam.ac.uk/pub/software/programming/pcre"
)

// openssl
const (
	OPENSSL_VERSION             = "1.0.2e"
	OPENSSL_DOWNLOAD_URL_PREFIX = "http://www.openssl.org/source"
)

// zlib
const (
	ZLIB_VERSION             = "1.2.8"
	ZLIB_DOWNLOAD_URL_PREFIX = "http://zlib.net"
)

// openResty
const (
	OPENRESTY_VERSION             = "1.9.7.2"
	OPENRESTY_DOWNLOAD_URL_PREFIX = "http://openresty.org/download"
)

// tengine
const (
	TENGINE_VERSION             = "2.1.2"
	TENGINE_DOWNLOAD_URL_PREFIX = "http://tengine.taobao.org/download"
)

// component enumerations
const (
	COMPONENT_NGINX = iota
	COMPONENT_OPENRESTY
	COMPONENT_TENGINE
	COMPONENT_PCRE
	COMPONENT_OPENSSL
	COMPONENT_ZLIB
	COMPONENT_MAX
)
<commit_msg>bumped openssl version to 1.0.2f.<commit_after>package main

const NGINX_BUILD_VERSION = "0.6.7"

// nginx
const (
	NGINX_VERSION             = "1.9.10"
	NGINX_DOWNLOAD_URL_PREFIX = "http://nginx.org/download"
)

// pcre
const (
	PCRE_VERSION             = "8.38"
	PCRE_DOWNLOAD_URL_PREFIX = "http://ftp.csx.cam.ac.uk/pub/software/programming/pcre"
)

// openssl
const (
	OPENSSL_VERSION             = "1.0.2f"
	OPENSSL_DOWNLOAD_URL_PREFIX = "http://www.openssl.org/source"
)

// zlib
const (
	ZLIB_VERSION             = "1.2.8"
	ZLIB_DOWNLOAD_URL_PREFIX = "http://zlib.net"
)

// openResty
const (
	OPENRESTY_VERSION             = "1.9.7.2"
	OPENRESTY_DOWNLOAD_URL_PREFIX = "http://openresty.org/download"
)

// tengine
const (
	TENGINE_VERSION             = "2.1.2"
	TENGINE_DOWNLOAD_URL_PREFIX = "http://tengine.taobao.org/download"
)

// component enumerations
const (
	COMPONENT_NGINX = iota
	COMPONENT_OPENRESTY
	COMPONENT_TENGINE
	COMPONENT_PCRE
	COMPONENT_OPENSSL
	COMPONENT_ZLIB
	COMPONENT_MAX
)
<|endoftext|>


<commit_before>package models

import (
	"github.com/jinzhu/gorm"
	"github.com/matematik7/gongo/authorization"
	"github.com/matematik7/gongo/files"
)

type DiaryEntry struct {
	gorm.Model
	Title      string             `valid:"required"`
	Text       string             `gorm:"type:text" valid:"required"`
	Author     authorization.User `valid:"-"`
	AuthorID   uint               `valid:"required"`
	Comments   []Comment          `valid:"-"`
	MapEntry   MapEntry           `valid:"-"`
	MapEntryID uint
	Images     []files.Image `gorm:"many2many:diary_image"`

	NumComments uint `gorm:"-"`
	Viewed      bool `gorm:"-"`
	NewComments bool `gorm:"-"`
}

type Comment struct {
	gorm.Model
	DiaryEntryID uint
	Comment      string             `gorm:"type:text" valid:"required"`
	Author       authorization.User `valid:"-"`
	AuthorID     uint
}

type EntryUserRead struct {
	gorm.Model
	DiaryEntryID uint
	UserID       uint
}
<commit_msg>Hacky timezone handling<commit_after>package models

import (
	"log"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/matematik7/gongo/authorization"
	"github.com/matematik7/gongo/files"
)

var location *time.Location

func init() {
	var err error
	location, err = time.LoadLocation("Europe/Ljubljana")
	if err != nil {
		log.Fatal(err)
	}
}

type DiaryEntry struct {
	gorm.Model
	Title      string             `valid:"required"`
	Text       string             `gorm:"type:text" valid:"required"`
	Author     authorization.User `valid:"-"`
	AuthorID   uint               `valid:"required"`
	Comments   []Comment          `valid:"-"`
	MapEntry   MapEntry           `valid:"-"`
	MapEntryID uint
	Images     []files.Image `gorm:"many2many:diary_image"`
	Published  bool

	NumComments uint `gorm:"-"`
	Viewed      bool `gorm:"-"`
	NewComments bool `gorm:"-"`
}

// TODO: handle timezone nicer
func (de *DiaryEntry) AfterFind() error {
	de.CreatedAt = de.CreatedAt.In(location)
	de.UpdatedAt = de.UpdatedAt.In(location)

	return nil
}

type Comment struct {
	gorm.Model
	DiaryEntryID uint
	Comment      string             `gorm:"type:text" valid:"required"`
	Author       authorization.User `valid:"-"`
	AuthorID     uint
}

func (c *Comment) AfterFind() error {
	c.CreatedAt = c.CreatedAt.In(location)

	return nil
}

type EntryUserRead struct {
	gorm.Model
	DiaryEntryID uint
	UserID       uint
}
<|endoftext|>


<commit_before>package main

import "testing"

func TestPrintStuff(t *testing.T) {
    if actual, expected := PrintSomeStuff("lowks"), "hello lowks"; actual != expected {
        t.Errorf("Expected %s to Equal %s", actual, expected)
    } else {
        t.Log("Successfully tested")
    }
}

func TestaddTwoNumbers(t *testing.T) {
    if actual, expected := addTwoNumbers(5,7), 12; actual != expected {
        t.Errorf("Expected %s to Equal %s", actual, expected)
    } else {
        t.Log("Successfully tested")
    }
}
<commit_msg>re Added skip test and corrected previous test. Turns out needs to be camelcase<commit_after>package main

import "testing"

func TestPrintStuff(t *testing.T) {
    if actual, expected := PrintSomeStuff("lowks"), "hello lowks"; actual != expected {
        t.Errorf("Expected %s to Equal %s", actual, expected)
    } else {
        t.Log("Successfully tested")
    }
}

func TestAddTwoNumbers(t *testing.T) {
    if actual, expected := addTwoNumbers(5,7), 12; actual != expected {
        t.Errorf("Expected %s to Equal %s", actual, expected)
    } else {
        t.Log("Successfully tested")
    }
}

func TestSkipTest(t *testing.T) {
    t.Skip("This is a skipped test")
}
<|endoftext|>


<commit_before>// -*- tab-width: 4; -*-

package main

import (
	"bufio"
	"bytes"
	"encoding/gob"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
)

type Cached struct {
	Tweets       Tweets
	Lastmodified string
}

// key: url
type Cache map[string]Cached

func (cache Cache) Store(configpath string) {
	b := new(bytes.Buffer)
	enc := gob.NewEncoder(b)
	err := enc.Encode(cache)
	if err != nil {
		panic(err)
	}

	f, err := os.OpenFile(fmt.Sprintf("%s/cache", configpath),
		os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}

	defer f.Close()

	if _, err = f.Write(b.Bytes()); err != nil {
		panic(err)
	}
}

func LoadCache(configpath string) Cache {
	cache := make(Cache)

	f, err := os.Open(fmt.Sprintf("%s/cache", configpath))
	if err != nil {
		if !os.IsNotExist(err) {
			panic(err)
		}
		return cache
	}
	defer f.Close()

	dec := gob.NewDecoder(f)
	err = dec.Decode(&cache)
	if err != nil {
		panic(err)
	}
	return cache
}

func (cache Cache) FetchTweets(sources map[string]string) {
	var mu sync.RWMutex

	tweetsch := make(chan Tweets)
	var wg sync.WaitGroup
	// max parallel http fetchers
	var fetchers = make(chan struct{}, maxfetchers)

	for nick, url := range sources {
		wg.Add(1)

		fetchers <- struct{}{}

		// anon func takes needed variables as arg, avoiding capture of iterator variables
		go func(nick string, url string) {
			defer wg.Done()

			if strings.HasPrefix(url, "file://") {
				err := ReadLocalFile(url, nick, tweetsch, cache, &mu)
				if err != nil {
					if debug {
						log.Printf("%s: Failed to read and cache local file: %s", url, err)
					}
				}
				return
			}

			req, err := http.NewRequest("GET", url, nil)
			if err != nil {
				if debug {
					log.Printf("%s: http.NewRequest fail: %s", url, err)
				}
				tweetsch <- nil
				return
			}

			if conf.Nick != "" && conf.Twturl != "" {
				req.Header.Set("User-Agent",
					fmt.Sprintf("%s/%s (+%s; @%s)", progname, GetVersion(),
						conf.Twturl, conf.Nick))
			}

			mu.RLock()
			if cached, ok := cache[url]; ok {
				if cached.Lastmodified != "" {
					req.Header.Set("If-Modified-Since", cached.Lastmodified)
				}
			}
			mu.RUnlock()

			client := http.Client{}
			resp, err := client.Do(req)
			if err != nil {
				if debug {
					log.Printf("%s: client.Do fail: %s", url, err)
				}
				tweetsch <- nil
				return
			}
			defer resp.Body.Close()

			actualurl := resp.Request.URL.String()
			if actualurl != url {
				url = actualurl
			}

			var tweets Tweets

			switch resp.StatusCode {
			case http.StatusOK: // 200
				scanner := bufio.NewScanner(resp.Body)
				tweets = ParseFile(scanner, Tweeter{Nick: nick, URL: url})
				lastmodified := resp.Header.Get("Last-Modified")
				mu.Lock()
				cache[url] = Cached{Tweets: tweets, Lastmodified: lastmodified}
				mu.Unlock()
			case http.StatusNotModified: // 304
				mu.RLock()
				tweets = cache[url].Tweets
				mu.RUnlock()
			}

			tweetsch <- tweets

		}(nick, url)

		<-fetchers
	}

	// close tweets channel when all goroutines are done
	go func() {
		wg.Wait()
		close(tweetsch)
	}()

	if debug {
		log.Print("fetching:\n")
	}

	var n = 0
	// loop until channel closed
	for tweets := range tweetsch {
		n++
		if debug {
			log.Printf("%d ", len(sources)+1-n)
		}
		if debug && len(tweets) > 0 {
			log.Printf("%s\n", tweets[0].Tweeter.URL)
		}
	}
	if debug {
		log.Print("\n")
	}
}

func ReadLocalFile(url string, nick string, tweetsch chan<- Tweets, cache Cache, mu *sync.RWMutex) error {
	path := string(url[6:])
	file, err := os.Stat(path)
	if err != nil {
		if debug {
			log.Printf("%s: Can't stat local file: %s", path, err)
		}
		return err
	}
	if cached, ok := (cache)[url]; ok {
		if cached.Lastmodified == file.ModTime().String() {
			tweets := (cache)[url].Tweets
			tweetsch <- tweets
			return nil
		}
	}
	data, err := ioutil.ReadFile(path)
	if err != nil {
		if debug {
			log.Printf("%s: Can't read local file: %s", path, err)
		}
		tweetsch <- nil
		return err
	}
	scanner := bufio.NewScanner(bytes.NewReader(data))
	tweets := ParseFile(scanner, Tweeter{Nick: nick, URL: url})
	lastmodified := file.ModTime().String()
	mu.Lock()
	cache[url] = Cached{Tweets: tweets, Lastmodified: lastmodified}
	mu.Unlock()
	tweetsch <- tweets
	return nil
}

func (cache Cache) GetAll() Tweets {
	var alltweets Tweets
	for url, cached := range cache {
		alltweets = append(alltweets, cached.Tweets...)
		if debug {
			log.Printf("%s\n", url)
		}
	}
	return alltweets
}

func (cache Cache) GetByURL(url string) Tweets {
	if cached, ok := cache[url]; ok {
		return cached.Tweets
	}
	return Tweets{}
}
<commit_msg>Remove unnecessary type conv<commit_after>// -*- tab-width: 4; -*-

package main

import (
	"bufio"
	"bytes"
	"encoding/gob"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
)

type Cached struct {
	Tweets       Tweets
	Lastmodified string
}

// key: url
type Cache map[string]Cached

func (cache Cache) Store(configpath string) {
	b := new(bytes.Buffer)
	enc := gob.NewEncoder(b)
	err := enc.Encode(cache)
	if err != nil {
		panic(err)
	}

	f, err := os.OpenFile(fmt.Sprintf("%s/cache", configpath),
		os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}

	defer f.Close()

	if _, err = f.Write(b.Bytes()); err != nil {
		panic(err)
	}
}

func LoadCache(configpath string) Cache {
	cache := make(Cache)

	f, err := os.Open(fmt.Sprintf("%s/cache", configpath))
	if err != nil {
		if !os.IsNotExist(err) {
			panic(err)
		}
		return cache
	}
	defer f.Close()

	dec := gob.NewDecoder(f)
	err = dec.Decode(&cache)
	if err != nil {
		panic(err)
	}
	return cache
}

func (cache Cache) FetchTweets(sources map[string]string) {
	var mu sync.RWMutex

	tweetsch := make(chan Tweets)
	var wg sync.WaitGroup
	// max parallel http fetchers
	var fetchers = make(chan struct{}, maxfetchers)

	for nick, url := range sources {
		wg.Add(1)

		fetchers <- struct{}{}

		// anon func takes needed variables as arg, avoiding capture of iterator variables
		go func(nick string, url string) {
			defer wg.Done()

			if strings.HasPrefix(url, "file://") {
				err := ReadLocalFile(url, nick, tweetsch, cache, &mu)
				if err != nil {
					if debug {
						log.Printf("%s: Failed to read and cache local file: %s", url, err)
					}
				}
				return
			}

			req, err := http.NewRequest("GET", url, nil)
			if err != nil {
				if debug {
					log.Printf("%s: http.NewRequest fail: %s", url, err)
				}
				tweetsch <- nil
				return
			}

			if conf.Nick != "" && conf.Twturl != "" {
				req.Header.Set("User-Agent",
					fmt.Sprintf("%s/%s (+%s; @%s)", progname, GetVersion(),
						conf.Twturl, conf.Nick))
			}

			mu.RLock()
			if cached, ok := cache[url]; ok {
				if cached.Lastmodified != "" {
					req.Header.Set("If-Modified-Since", cached.Lastmodified)
				}
			}
			mu.RUnlock()

			client := http.Client{}
			resp, err := client.Do(req)
			if err != nil {
				if debug {
					log.Printf("%s: client.Do fail: %s", url, err)
				}
				tweetsch <- nil
				return
			}
			defer resp.Body.Close()

			actualurl := resp.Request.URL.String()
			if actualurl != url {
				url = actualurl
			}

			var tweets Tweets

			switch resp.StatusCode {
			case http.StatusOK: // 200
				scanner := bufio.NewScanner(resp.Body)
				tweets = ParseFile(scanner, Tweeter{Nick: nick, URL: url})
				lastmodified := resp.Header.Get("Last-Modified")
				mu.Lock()
				cache[url] = Cached{Tweets: tweets, Lastmodified: lastmodified}
				mu.Unlock()
			case http.StatusNotModified: // 304
				mu.RLock()
				tweets = cache[url].Tweets
				mu.RUnlock()
			}

			tweetsch <- tweets

		}(nick, url)

		<-fetchers
	}

	// close tweets channel when all goroutines are done
	go func() {
		wg.Wait()
		close(tweetsch)
	}()

	if debug {
		log.Print("fetching:\n")
	}

	var n = 0
	// loop until channel closed
	for tweets := range tweetsch {
		n++
		if debug {
			log.Printf("%d ", len(sources)+1-n)
		}
		if debug && len(tweets) > 0 {
			log.Printf("%s\n", tweets[0].Tweeter.URL)
		}
	}
	if debug {
		log.Print("\n")
	}
}

func ReadLocalFile(url string, nick string, tweetsch chan<- Tweets, cache Cache, mu *sync.RWMutex) error {
	path := url[6:]
	file, err := os.Stat(path)
	if err != nil {
		if debug {
			log.Printf("%s: Can't stat local file: %s", path, err)
		}
		return err
	}
	if cached, ok := (cache)[url]; ok {
		if cached.Lastmodified == file.ModTime().String() {
			tweets := (cache)[url].Tweets
			tweetsch <- tweets
			return nil
		}
	}
	data, err := ioutil.ReadFile(path)
	if err != nil {
		if debug {
			log.Printf("%s: Can't read local file: %s", path, err)
		}
		tweetsch <- nil
		return err
	}
	scanner := bufio.NewScanner(bytes.NewReader(data))
	tweets := ParseFile(scanner, Tweeter{Nick: nick, URL: url})
	lastmodified := file.ModTime().String()
	mu.Lock()
	cache[url] = Cached{Tweets: tweets, Lastmodified: lastmodified}
	mu.Unlock()
	tweetsch <- tweets
	return nil
}

func (cache Cache) GetAll() Tweets {
	var alltweets Tweets
	for url, cached := range cache {
		alltweets = append(alltweets, cached.Tweets...)
		if debug {
			log.Printf("%s\n", url)
		}
	}
	return alltweets
}

func (cache Cache) GetByURL(url string) Tweets {
	if cached, ok := cache[url]; ok {
		return cached.Tweets
	}
	return Tweets{}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/fzzy/radix/redis"
	"github.com/samalba/dockerclient"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
)

const CACHE_PREFIX = "docker"
const EVENTS_CHANNEL = "docker_events"

type Cache struct {
	redisConn *redis.Client
	id        string
	ttl       time.Duration
}

func NewCache(cacheUrl string, id string, ttl time.Duration) (*Cache, error) {
	u, err := url.Parse(cacheUrl)
	if err != nil {
		return nil, err
	}
	redisConn, err := redis.DialTimeout("tcp", u.Host,
		time.Duration(30)*time.Second)
	if err != nil {
		return nil, err
	}
	if len(u.Path) > 2 {
		db, err := strconv.Atoi(u.Path[1:])
		if err == nil {
			return nil, fmt.Errorf("Wrong Redis db: %s", err)
		}
		r := redisConn.Cmd("select", db)
		if r.Err != nil {
			return nil, r.Err
		}
	}
	if u.User != nil {
		if pwd, ok := u.User.Password(); ok {
			r := redisConn.Cmd("auth", pwd)
			if r.Err != nil {
				return nil, r.Err
			}
		}
	}
	cache := &Cache{redisConn, id, ttl}
	cache.publishEvent("new_host", id)
	return cache, nil
}

func (cache *Cache) publishEvent(args ...string) error {
	event := strings.Join(args, ":")
	r := cache.redisConn.Cmd("publish", EVENTS_CHANNEL, event)
	if r.Err != nil {
		return r.Err
	}
	return nil
}

func (cache *Cache) getHostTimestamp() int64 {
	localts := time.Now().UTC().Unix()
	r := cache.redisConn.Cmd("time")
	if r.Err != nil {
		// Redis is too old to support the "time" command
		return localts
	}
	ts, err := r.List()
	if err == nil {
		return localts
	}
	// Return time from Redis (all hosts will be synchronized)
	timestamp, err := strconv.ParseInt(ts[0], 10, 64)
	if err != nil {
		return localts
	}
	return timestamp
}

func (cache *Cache) refreshHostLastUpdate() error {
	lastUpdate := strconv.FormatInt(cache.getHostTimestamp(), 10)
	return cache.SetHostParam("last_update", lastUpdate)
}

func (cache *Cache) deleteHost(id string) {
	c := cache.redisConn
	c.Append("multi")
	key := CACHE_PREFIX + ":hosts"
	c.Append("srem", key, id)
	key += ":" + id
	c.Append("del", key)
	key += ":containers"
	c.Append("del", key)
	c.Append("exec")
	for i := 0; i < 5; i++ {
		c.GetReply()
	}
}

func (cache *Cache) ClearExpiredHosts() error {
	r := cache.redisConn.Cmd("smembers", CACHE_PREFIX+":hosts")
	if r.Err != nil {
		return r.Err
	}
	hosts, err := r.List()
	if err != nil {
		return err
	}
	for _, host := range hosts {
		if host == cache.id {
			// ignore myself
			continue
		}
		key := fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, host)
		r := cache.redisConn.Cmd("hget", key, "last_update")
		if r.Err != nil {
			continue
		}
		lastUpdate, err := r.Int64()
		if err != nil {
			continue
		}
		r = cache.redisConn.Cmd("hget", key, "update_interval")
		if r.Err != nil {
			continue
		}
		updateInterval, err := r.Int64()
		if err != nil {
			continue
		}
		timestamp := cache.getHostTimestamp()
		// If the host missed 2 updates, we consider it expired
		if (timestamp - lastUpdate) < (2 * updateInterval) {
			continue
		}
		cache.deleteHost(host)
		cache.publishEvent("expired_host", host)
	}
	return nil
}

// Converts a struct to a map of strings
func structToMap(st interface{}, out *map[string]string, prefix string) {
	val := reflect.ValueOf(st)
	if reflect.TypeOf(st).Kind() == reflect.Ptr {
		val = val.Elem()
	}
	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)
		f := valueField.Interface()
		val := reflect.ValueOf(f)
		if val.Kind() == reflect.Ptr {
			val = val.Elem()
		}
		name := prefix + strings.ToLower(typeField.Name)
		if val.Kind() == reflect.Struct {
			structToMap(f, out, name+"_")
			continue
		}
		switch val.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			(*out)[name] = strconv.FormatInt(val.Int(), 10)
		case reflect.Bool:
			(*out)[name] = strconv.FormatBool(val.Bool())
		case reflect.String:
			(*out)[name] = val.String()
		case reflect.Array, reflect.Slice, reflect.Map:
			b, err := json.Marshal(val.Interface())
			if err != nil {
				continue
			}
			(*out)[name] = string(b)
		}
	}
}

func (cache *Cache) SetContainerInfo(container *dockerclient.ContainerInfo) error {
	m := make(map[string]string)
	structToMap(container, &m, "")
	key := fmt.Sprintf("%s:containers:%s", CACHE_PREFIX, container.Id)
	c := cache.redisConn
	c.Append("multi")
	c.Append("del", key)
	for k, v := range m {
		c.Append("hset", key, k, v)
	}
	c.Append("expire", key, int(cache.ttl.Seconds()))
	c.Append("exec")
	for i := 0; i < len(m)+4; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}

	// Set the container's json
	key += ":json"
	b, err := json.Marshal(container)
	if err != nil {
		return err
	}
	c.Append("multi")
	c.Append("del", key)
	c.Append("set", key, string(b))
	c.Append("expire", key, int(cache.ttl.Seconds()))
	c.Append("exec")
	for i := 0; i < 5; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}

	// Save container info to extra keys for easier identification
	c.Append("multi")
	key = fmt.Sprintf("%s:containers", CACHE_PREFIX)
	c.Append("sadd", key, container.Id)
	key = fmt.Sprintf("%s:images", CACHE_PREFIX)
	c.Append("sadd", key, container.Image)
	key = fmt.Sprintf("%s:images:%s:containers", CACHE_PREFIX, container.Image)
	c.Append("sadd", key, container.Id)
	c.Append("exec")
	for i := 0; i < 5; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}

	return nil
}

func (cache *Cache) AddContainer(container *dockerclient.ContainerInfo) error {
	err := cache.SetContainerInfo(container)
	if err != nil {
		return err
	}
	key := fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	r := cache.redisConn.Cmd("hincrby", key, "containers_running", 1)
	if r.Err != nil {
		return r.Err
	}
	err = cache.refreshHostLastUpdate()
	if err != nil {
		return err
	}
	cache.publishEvent("new_container", cache.id, container.Id)
	return nil
}

func (cache *Cache) DeleteContainer(container *dockerclient.ContainerInfo) error {
	key := fmt.Sprintf("%s:containers:%s", CACHE_PREFIX, container.Id)
	c := cache.redisConn
	c.Append("multi")
	c.Append("del", key)
	key = fmt.Sprintf("%s:hosts:%s:containers", CACHE_PREFIX, cache.id)
	c.Append("srem", key, container.Id)
	key = fmt.Sprintf("%s:images:%s:containers", CACHE_PREFIX, container.Image)
	c.Append("srem", key, container.Id)
	key = fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	c.Append("hincrby", key, "containers_running", -1)
	c.Append("exec")
	for i := 0; i < 5; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}
	err := cache.refreshHostLastUpdate()
	if err != nil {
		return err
	}
	cache.publishEvent("delete_container", cache.id, container.Id)
	return nil
}

func (cache *Cache) SetContainersList(containers []dockerclient.Container) error {
	// Store containers list and set containers info
	key := fmt.Sprintf("%s:hosts:%s:containers", CACHE_PREFIX, cache.id)
	c := cache.redisConn
	c.Append("multi")
	c.Append("del", key)
	for _, container := range containers {
		c.Append("sadd", key, container.Id)
	}
	c.Append("expire", key, int(cache.ttl.Seconds()))
	c.Append("exec")
	for i := 0; i < len(containers)+4; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}
	// Store some host stats
	err := cache.SetHostParam("containers_running", strconv.Itoa(len(containers)))
	if err != nil {
		return err
	}
	err = cache.SetHostParam("update_interval", strconv.Itoa(int(cache.ttl.Seconds())))
	if err != nil {
		return err
	}
	err = cache.refreshHostLastUpdate()
	if err != nil {
		return err
	}
	key = fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	r := cache.redisConn.Cmd("expire", key, int(cache.ttl.Seconds()))
	if err != nil {
		return err
	}
	// Register the host in the host list
	key = CACHE_PREFIX + ":hosts"
	r = cache.redisConn.Cmd("sadd", key, cache.id)
	if r.Err != nil {
		return r.Err
	}
	r = cache.redisConn.Cmd("expire", key, int(cache.ttl.Seconds()))
	if r.Err != nil {
		return r.Err
	}
	cache.publishEvent("refresh_containers", cache.id)
	return nil
}

func (cache *Cache) SetHostParam(key string, value string) error {
	hKey := fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	r := cache.redisConn.Cmd("hset", hKey, key, value)
	if r.Err != nil {
		return r.Err
	}
	return nil
}
<commit_msg>record docker host<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/fzzy/radix/redis"
	"github.com/samalba/dockerclient"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
)

const CACHE_PREFIX = "docker"
const EVENTS_CHANNEL = "docker_events"

type Cache struct {
	redisConn *redis.Client
	id        string
	ttl       time.Duration
}

func NewCache(cacheUrl string, id string, ttl time.Duration) (*Cache, error) {
	u, err := url.Parse(cacheUrl)
	if err != nil {
		return nil, err
	}
	redisConn, err := redis.DialTimeout("tcp", u.Host,
		time.Duration(30)*time.Second)
	if err != nil {
		return nil, err
	}
	if len(u.Path) > 2 {
		db, err := strconv.Atoi(u.Path[1:])
		if err == nil {
			return nil, fmt.Errorf("Wrong Redis db: %s", err)
		}
		r := redisConn.Cmd("select", db)
		if r.Err != nil {
			return nil, r.Err
		}
	}
	if u.User != nil {
		if pwd, ok := u.User.Password(); ok {
			r := redisConn.Cmd("auth", pwd)
			if r.Err != nil {
				return nil, r.Err
			}
		}
	}
	cache := &Cache{redisConn, id, ttl}
	cache.publishEvent("new_host", id)
	return cache, nil
}

func (cache *Cache) publishEvent(args ...string) error {
	event := strings.Join(args, ":")
	r := cache.redisConn.Cmd("publish", EVENTS_CHANNEL, event)
	if r.Err != nil {
		return r.Err
	}
	return nil
}

func (cache *Cache) getHostTimestamp() int64 {
	localts := time.Now().UTC().Unix()
	r := cache.redisConn.Cmd("time")
	if r.Err != nil {
		// Redis is too old to support the "time" command
		return localts
	}
	ts, err := r.List()
	if err == nil {
		return localts
	}
	// Return time from Redis (all hosts will be synchronized)
	timestamp, err := strconv.ParseInt(ts[0], 10, 64)
	if err != nil {
		return localts
	}
	return timestamp
}

func (cache *Cache) refreshHostLastUpdate() error {
	lastUpdate := strconv.FormatInt(cache.getHostTimestamp(), 10)
	return cache.SetHostParam("last_update", lastUpdate)
}

func (cache *Cache) deleteHost(id string) {
	c := cache.redisConn
	c.Append("multi")
	key := CACHE_PREFIX + ":hosts"
	c.Append("srem", key, id)
	key += ":" + id
	c.Append("del", key)
	key += ":containers"
	c.Append("del", key)
	c.Append("exec")
	for i := 0; i < 5; i++ {
		c.GetReply()
	}
}

func (cache *Cache) ClearExpiredHosts() error {
	r := cache.redisConn.Cmd("smembers", CACHE_PREFIX+":hosts")
	if r.Err != nil {
		return r.Err
	}
	hosts, err := r.List()
	if err != nil {
		return err
	}
	for _, host := range hosts {
		if host == cache.id {
			// ignore myself
			continue
		}
		key := fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, host)
		r := cache.redisConn.Cmd("hget", key, "last_update")
		if r.Err != nil {
			continue
		}
		lastUpdate, err := r.Int64()
		if err != nil {
			continue
		}
		r = cache.redisConn.Cmd("hget", key, "update_interval")
		if r.Err != nil {
			continue
		}
		updateInterval, err := r.Int64()
		if err != nil {
			continue
		}
		timestamp := cache.getHostTimestamp()
		// If the host missed 2 updates, we consider it expired
		if (timestamp - lastUpdate) < (2 * updateInterval) {
			continue
		}
		cache.deleteHost(host)
		cache.publishEvent("expired_host", host)
	}
	return nil
}

// Converts a struct to a map of strings
func structToMap(st interface{}, out *map[string]string, prefix string) {
	val := reflect.ValueOf(st)
	if reflect.TypeOf(st).Kind() == reflect.Ptr {
		val = val.Elem()
	}
	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)
		f := valueField.Interface()
		val := reflect.ValueOf(f)
		if val.Kind() == reflect.Ptr {
			val = val.Elem()
		}
		name := prefix + strings.ToLower(typeField.Name)
		if val.Kind() == reflect.Struct {
			structToMap(f, out, name+"_")
			continue
		}
		switch val.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			(*out)[name] = strconv.FormatInt(val.Int(), 10)
		case reflect.Bool:
			(*out)[name] = strconv.FormatBool(val.Bool())
		case reflect.String:
			(*out)[name] = val.String()
		case reflect.Array, reflect.Slice, reflect.Map:
			b, err := json.Marshal(val.Interface())
			if err != nil {
				continue
			}
			(*out)[name] = string(b)
		}
	}
}

func (cache *Cache) SetContainerInfo(container *dockerclient.ContainerInfo) error {
	m := make(map[string]string)
	structToMap(container, &m, "")
	key := fmt.Sprintf("%s:containers:%s", CACHE_PREFIX, container.Id)
	c := cache.redisConn
	c.Append("multi")
	c.Append("del", key)
	for k, v := range m {
		c.Append("hset", key, k, v)
	}
	c.Append("hset", key, "host", cache.id)
	c.Append("expire", key, int(cache.ttl.Seconds()))
	c.Append("exec")
	for i := 0; i < len(m)+4; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}

	// Set the container's json
	key += ":json"
	b, err := json.Marshal(container)
	if err != nil {
		return err
	}
	c.Append("multi")
	c.Append("del", key)
	c.Append("set", key, string(b))
	c.Append("expire", key, int(cache.ttl.Seconds()))
	c.Append("exec")
	for i := 0; i < 5; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}

	// Save container info to extra keys for easier identification
	c.Append("multi")
	key = fmt.Sprintf("%s:containers", CACHE_PREFIX)
	c.Append("sadd", key, container.Id)
	key = fmt.Sprintf("%s:images", CACHE_PREFIX)
	c.Append("sadd", key, container.Image)
	key = fmt.Sprintf("%s:images:%s:containers", CACHE_PREFIX, container.Image)
	c.Append("sadd", key, container.Id)
	c.Append("exec")
	for i := 0; i < 5; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}

	return nil
}

func (cache *Cache) AddContainer(container *dockerclient.ContainerInfo) error {
	err := cache.SetContainerInfo(container)
	if err != nil {
		return err
	}
	key := fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	r := cache.redisConn.Cmd("hincrby", key, "containers_running", 1)
	if r.Err != nil {
		return r.Err
	}
	err = cache.refreshHostLastUpdate()
	if err != nil {
		return err
	}
	cache.publishEvent("new_container", cache.id, container.Id)
	return nil
}

func (cache *Cache) DeleteContainer(container *dockerclient.ContainerInfo) error {
	key := fmt.Sprintf("%s:containers:%s", CACHE_PREFIX, container.Id)
	c := cache.redisConn
	c.Append("multi")
	c.Append("del", key)
	key = fmt.Sprintf("%s:hosts:%s:containers", CACHE_PREFIX, cache.id)
	c.Append("srem", key, container.Id)
	key = fmt.Sprintf("%s:images:%s:containers", CACHE_PREFIX, container.Image)
	c.Append("srem", key, container.Id)
	key = fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	c.Append("hincrby", key, "containers_running", -1)
	c.Append("exec")
	for i := 0; i < 5; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}
	err := cache.refreshHostLastUpdate()
	if err != nil {
		return err
	}
	cache.publishEvent("delete_container", cache.id, container.Id)
	return nil
}

func (cache *Cache) SetContainersList(containers []dockerclient.Container) error {
	// Store containers list and set containers info
	key := fmt.Sprintf("%s:hosts:%s:containers", CACHE_PREFIX, cache.id)
	c := cache.redisConn
	c.Append("multi")
	c.Append("del", key)
	for _, container := range containers {
		c.Append("sadd", key, container.Id)
	}
	c.Append("expire", key, int(cache.ttl.Seconds()))
	c.Append("exec")
	for i := 0; i < len(containers)+4; i++ {
		r := c.GetReply()
		if r.Err != nil {
			return r.Err
		}
	}
	// Store some host stats
	err := cache.SetHostParam("containers_running", strconv.Itoa(len(containers)))
	if err != nil {
		return err
	}
	err = cache.SetHostParam("update_interval", strconv.Itoa(int(cache.ttl.Seconds())))
	if err != nil {
		return err
	}
	err = cache.refreshHostLastUpdate()
	if err != nil {
		return err
	}
	key = fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	r := cache.redisConn.Cmd("expire", key, int(cache.ttl.Seconds()))
	if err != nil {
		return err
	}
	// Register the host in the host list
	key = CACHE_PREFIX + ":hosts"
	r = cache.redisConn.Cmd("sadd", key, cache.id)
	if r.Err != nil {
		return r.Err
	}
	r = cache.redisConn.Cmd("expire", key, int(cache.ttl.Seconds()))
	if r.Err != nil {
		return r.Err
	}
	cache.publishEvent("refresh_containers", cache.id)
	return nil
}

func (cache *Cache) SetHostParam(key string, value string) error {
	hKey := fmt.Sprintf("%s:hosts:%s", CACHE_PREFIX, cache.id)
	r := cache.redisConn.Cmd("hset", hKey, key, value)
	if r.Err != nil {
		return r.Err
	}
	return nil
}
<|endoftext|>


<commit_before>package ui

import (
	"bytes"
	"strings"
	"text/template"

	"github.com/robhurring/jit/jit"
)

const (
	issueInfoTemplate = `
@y{{ .Key }}: @{!w}{{ .Fields.Summary }}
@{!k}{{ .URL }}

@bReporter:@| {{ if .Fields.Reporter }}{{ .Fields.Reporter.DisplayName }}{{end}}
@bAssigned:@| {{ if .Fields.Assignee }}{{ .Fields.Assignee.DisplayName }}{{end}}
@bDeveloper:@| {{ if .Fields.PrimaryDeveloper }}{{ .Fields.PrimaryDeveloper.DisplayName }}{{ end }}
@bReviewer:@| {{ if .Fields.CodeReviewer }}{{ .Fields.CodeReviewer.DisplayName }}{{ end }}
@bAssigned:@| {{ if .Fields.Assignee }}{{ .Fields.Assignee.DisplayName }}{{ end }}
{{ if .Links }}
@{!k}-----------------------8<-------------------------------------------------------@|

@yLinks ({{ len .Links }}):@|
{{ range $link := .Links }}
  @r{{ $link.Type }}@|
  {{ $link.Key }}: @{!k}[{{ $link.Status }}]@|:  @{!w}{{ $link.Summary | trim }}@|
{{ end }}
@{!k}-----------------------8<-------------------------------------------------------@|
{{ end }}
@{!m}Status:@| {{ .Fields.Status.Name }}

{{ .Fields.Description | trim }}
{{ if .Fields.Comment.Comments }}
@{!k}-----------------------8<-------------------------------------------------------@|

@yComments ({{ len .Fields.Comment.Comments }}):@|
{{ range $comment := .Fields.Comment.Comments }}
"{{ $comment.Body | trim }}"
@{!k}{{ $comment.Author.DisplayName }}@|
{{ end }}
{{ end }}`

	pullRequestTemplate = `
/cc {{ .CodeReviewer | username }}

[JIRA {{ .Key }}]({{ .URL }})
{{ if .Associated }}

### Associated
{{ range $associated := .Associated }}
{{ $associated }}{{ end }}
{{ end }}
### Summary

### Testing

` + "`rake spec`"

	pullRequestInfoTemplate = `
@{!w}{{ .Title }}@|

{{ .Body | escapeColors }}
`
)

var (
	templates *template.Template
)

func init() {
	templateFuncs := template.FuncMap{
		"trim":         strings.TrimSpace,
		"username":     jit.FindUsername,
		"escapeColors": escapeColors,
	}

	t := template.New("all")
	t, _ = t.New("issue.info").Funcs(templateFuncs).Parse(issueInfoTemplate)
	t, _ = t.New("pull-request.body").Funcs(templateFuncs).Parse(pullRequestTemplate)
	t, _ = t.New("pull-request.info").Funcs(templateFuncs).Parse(pullRequestInfoTemplate)

	templates = t
}

func RenderTemplate(name string, data interface{}) string {
	var b bytes.Buffer
	templates.ExecuteTemplate(&b, name, data)
	return b.String()
}

func PrintTemplate(name string, data interface{}) {
	Println(RenderTemplate(name, data))
}

func escapeColors(data string) string {
	return strings.Replace(data, "@", "@@", -1)
}
<commit_msg>pull-request preview shows head and base<commit_after>package ui

import (
	"bytes"
	"strings"
	"text/template"

	"github.com/robhurring/jit/jit"
)

const (
	issueInfoTemplate = `
@y{{ .Key }}: @{!w}{{ .Fields.Summary }}
@{!k}{{ .URL }}

@bReporter:@| {{ if .Fields.Reporter }}{{ .Fields.Reporter.DisplayName }}{{end}}
@bAssigned:@| {{ if .Fields.Assignee }}{{ .Fields.Assignee.DisplayName }}{{end}}
@bDeveloper:@| {{ if .Fields.PrimaryDeveloper }}{{ .Fields.PrimaryDeveloper.DisplayName }}{{ end }}
@bReviewer:@| {{ if .Fields.CodeReviewer }}{{ .Fields.CodeReviewer.DisplayName }}{{ end }}
@bAssigned:@| {{ if .Fields.Assignee }}{{ .Fields.Assignee.DisplayName }}{{ end }}
{{ if .Links }}
@{!k}-----------------------8<-------------------------------------------------------@|

@yLinks ({{ len .Links }}):@|
{{ range $link := .Links }}
  @r{{ $link.Type }}@|
  {{ $link.Key }}: @{!k}[{{ $link.Status }}]@|:  @{!w}{{ $link.Summary | trim }}@|
{{ end }}
@{!k}-----------------------8<-------------------------------------------------------@|
{{ end }}
@{!m}Status:@| {{ .Fields.Status.Name }}

{{ .Fields.Description | trim }}
{{ if .Fields.Comment.Comments }}
@{!k}-----------------------8<-------------------------------------------------------@|

@yComments ({{ len .Fields.Comment.Comments }}):@|
{{ range $comment := .Fields.Comment.Comments }}
"{{ $comment.Body | trim }}"
@{!k}{{ $comment.Author.DisplayName }}@|
{{ end }}
{{ end }}`

	pullRequestTemplate = `
/cc {{ .CodeReviewer | username }}

[JIRA {{ .Key }}]({{ .URL }})
{{ if .Associated }}
### Associated
{{ range $associated := .Associated }}
{{ $associated }}{{ end }}
{{ end }}
### Summary

### Testing

` + "`bundle exec rspec`"

	pullRequestInfoTemplate = `
@yThis pull request will merge @r{{ .Head }}@y into @r{{ .Base }}@y!@|

@{!w}{{ .Title }}@|

{{ .Body | escapeColors }}
`
)

var (
	templates *template.Template
)

func init() {
	templateFuncs := template.FuncMap{
		"trim":         strings.TrimSpace,
		"username":     jit.FindUsername,
		"escapeColors": escapeColors,
	}

	t := template.New("all")
	t, _ = t.New("issue.info").Funcs(templateFuncs).Parse(issueInfoTemplate)
	t, _ = t.New("pull-request.body").Funcs(templateFuncs).Parse(pullRequestTemplate)
	t, _ = t.New("pull-request.info").Funcs(templateFuncs).Parse(pullRequestInfoTemplate)

	templates = t
}

func RenderTemplate(name string, data interface{}) string {
	var b bytes.Buffer
	templates.ExecuteTemplate(&b, name, data)
	return b.String()
}

func PrintTemplate(name string, data interface{}) {
	Println(RenderTemplate(name, data))
}

func escapeColors(data string) string {
	return strings.Replace(data, "@", "@@", -1)
}
<|endoftext|>