<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/HouzuoGuo/tiedot/db"
	"path/filepath"
	"strconv"
	"github.com/bradhe/stopwatch"
)

type Repository struct {
	DB       *db.DB
	commands *db.Col
}

// HELPER FUNCTIONS
func repositoryFullName() string {
	return settings.RepositoryDirectory() + string(filepath.Separator) + settings.RepositoryFile()
}

//

func (r *Repository) InitDB() {
	start := stopwatch.Start()

	var err error

	b, err := ExistsPath(settings.RepositoryDirectory())
	if err != nil {
		parrot.Error("Got error when reading repository directory", err)
	}

	if !b {
		CreatePath(settings.RepositoryDirectory())
	}

	// (Create if not exist) open a database
	r.DB, err = db.OpenDB(repositoryFullName())
	if err != nil {
		parrot.Error("Got error creating repository directory", err)
	}

	watch := stopwatch.Stop(start)
    fmt.Printf("Milliseconds elappsed: %v\n", watch.Milliseconds())
}

func (r *Repository) InitSchema() {
	// Drop (delete) collection "Commands"
	if err := r.DB.Drop("Commands"); err != nil {
		parrot.Error("Commands collection cannot be deleted", err)
	}

	// Create two collections: Commands and Votes
	if err := r.DB.Create("Commands"); err != nil {
		parrot.Error("Commands collection already exists", err)
	}

	r.commands = r.DB.Use("Commands")

	if err := r.commands.Index([]string{"command_id"}); err != nil {
		parrot.Error("Error", err)
	}
}

func (r *Repository) CloseDB() {
	if err := r.DB.Close(); err != nil {
		parrot.Error("Error", err)
	}
}

func (r *Repository) BackupSchema() {
	// Drop (delete) collection "Commands"
	if err := r.DB.Drop("Commands.bkp"); err != nil {
		parrot.Error("Commands.bkp collection cannot be deleted", err)
	}

	if err := r.DB.Rename("Commands", "Commands.bkp"); err != nil {
		parrot.Error("Commands.bkp collection cannot be created", err)
	}
}

// functionalities

func (r *Repository) Put(c Command) {
	_, err := r.commands.Insert(c.ToMap())
	if err != nil {
		parrot.Error("Error", err)
	}

}

/*
func (r *Repository) GetOne() Command {
	command := Command{}
	r.DB.First(&command)
	return command
}
*/

func (r *Repository) FindById(id string) Command {
	var query interface{}
	json.Unmarshal([]byte(`[{"eq": "`+id+`", "in": ["command_id"]}]`), &query)

	queryResult := make(map[int]struct{})

	if err := db.EvalQuery(query, r.commands, &queryResult); err != nil {
		parrot.Error("Error", err)
	}

	var command = Command{}
	for id := range queryResult {
		// To get query result document, simply read it
		readBack, err := r.commands.Read(id)
		if err != nil {
			parrot.Error("Error", err)
		}
		parrot.Info("Query returned document: ") //, readBack)

		command.FromMap(readBack)
	}

	return command
}

func (r *Repository) GetAllCommands() []Command {
	commands := []Command{}

	var query interface{}
	queryResult := make(map[int]struct{})

	if err := db.EvalQuery(query, r.commands, &queryResult); err != nil {
		panic(err)
	}

	var command = Command{}
	for id := range queryResult {
		// To get query result document, simply read it
		readBack, err := r.commands.Read(id)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Query returned document %v\n", readBack)
		command.FromMap(readBack)

		commands = append(commands, command)
	}

	return commands
}

func (r *Repository) GetHistory(count int) []Command {
	commands := []Command{}
	//r.DB.Order("terminated_at desc").Find(&commands).Count(&count)
	return commands
}

func (r *Repository) GetExecutedCommands(count int) []ExecutedCommand {
	commands := r.GetAllCommands()

	parrot.Info("Count is: " + strconv.Itoa(count))

	//r.DB.Limit(count).Order("terminated_at desc").Find(&commands)

	executedCommands := make([]ExecutedCommand, len(commands))

	for i := 0; i < len(commands); i++ {
		executedCommands[i] = commands[i].AsExecutedCommand(i)
	}

	return executedCommands
}

func extend(slice []Command, element Command) []Command {
	n := len(slice)
	if n == cap(slice) {
		// Slice is full; must grow.
		// We double its size and add 1, so if the size is zero we still grow.
		newSlice := make([]Command, len(slice), 2*len(slice)+1)
		copy(newSlice, slice)
		slice = newSlice
	}
	slice = slice[0 : n+1]
	slice[n] = element
	return slice
}

// Append appends the items to the slice.
// First version: just loop calling Extend.
func append(slice []Command, items ...Command) []Command {
	for _, item := range items {
		slice = extend(slice, item)
	}
	return slice
}
<commit_msg>partial commit<commit_after>package main

import (
	"encoding/json"
	"time"
	"fmt"
	"github.com/boltdb/bolt"
	"path/filepath"
	"strconv"
	"github.com/bradhe/stopwatch"
)

type Repository struct {
	DB       *bolt.DB
}

// HELPER FUNCTIONS
func repositoryFullName() string {
	return settings.RepositoryDirectory() + string(filepath.Separator) + settings.RepositoryFile()
}

//

func (r *Repository) InitDB() {
	start := stopwatch.Start()

	var err error

	b, err := ExistsPath(settings.RepositoryDirectory())
	if err != nil {
		parrot.Error("Got error when reading repository directory", err)
	}

	if !b {
		CreatePath(settings.RepositoryDirectory())
	}

	r.DB, err = bolt.Open(repositoryFullName(), 0600, nil)
    if err != nil {
        parrot.Error("Got error creating repository directory", err)
    }
    defer r.DB.Close()

	watch := stopwatch.Stop(start)
    fmt.Printf("Milliseconds elappsed: %v\n", watch.Milliseconds())
}

func (r *Repository) InitSchema() error {
	err := r.DB.Update(func(tx *bolt.Tx) error {
	    b, err := tx.CreateBucket([]byte("Commands"))
	    if err != nil {
	        return fmt.Errorf("create bucket: %s", err)
	    }
	    return nil
	})

	return err
}

	//defer r.DB.Close()

func (r *Repository) CloseDB() {
	if err := r.DB.Close(); err != nil {
		parrot.Error("Error", err)
	}
}

func (r *Repository) BackupSchema() {
	/*
	// Drop (delete) collection "Commands"
	if err := r.DB.Drop("Commands.bkp"); err != nil {
		parrot.Error("Commands.bkp collection cannot be deleted", err)
	}

	if err := r.DB.Rename("Commands", "Commands.bkp"); err != nil {
		parrot.Error("Commands.bkp collection cannot be created", err)
	}
	*/
}

// functionalities

func (r *Repository) Put(c Command) error {
	err := db.Update(func(tx *bolt.Tx) error {
	    b, err := tx.CreateBucketIfNotExists([]byte("Commands"))
	    if err != nil {
	        return err
	    }
	    encoded, err := json.Marshal(c)
	    if err != nil {
	        return err
	    }
	    return b.Put([]byte(c.CreatedAt.Format(time.RFC3339)), encoded)
	})
	return err
}

/*
func (r *Repository) GetOne() Command {
	command := Command{}
	r.DB.First(&command)
	return command
}
*/

func (r *Repository) FindById(id string) Command {
	var query interface{}
	json.Unmarshal([]byte(`[{"eq": "`+id+`", "in": ["command_id"]}]`), &query)

	queryResult := make(map[int]struct{})

	if err := db.EvalQuery(query, r.commands, &queryResult); err != nil {
		parrot.Error("Error", err)
	}

	var command = Command{}
	for id := range queryResult {
		// To get query result document, simply read it
		readBack, err := r.commands.Read(id)
		if err != nil {
			parrot.Error("Error", err)
		}
		parrot.Info("Query returned document: ") //, readBack)

		command.FromMap(readBack)
	}

	return command
}

func (r *Repository) GetAllCommands() []Command {
	commands := []Command{}

	var query interface{}
	queryResult := make(map[int]struct{})

	if err := db.EvalQuery(query, r.commands, &queryResult); err != nil {
		panic(err)
	}

	var command = Command{}
	for id := range queryResult {
		// To get query result document, simply read it
		readBack, err := r.commands.Read(id)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Query returned document %v\n", readBack)
		command.FromMap(readBack)

		commands = append(commands, command)
	}

	return commands
}

func (r *Repository) GetHistory(count int) []Command {
	commands := []Command{}
	//r.DB.Order("terminated_at desc").Find(&commands).Count(&count)
	return commands
}

func (r *Repository) GetExecutedCommands(count int) []ExecutedCommand {
	commands := r.GetAllCommands()

	parrot.Info("Count is: " + strconv.Itoa(count))

	//r.DB.Limit(count).Order("terminated_at desc").Find(&commands)

	executedCommands := make([]ExecutedCommand, len(commands))

	for i := 0; i < len(commands); i++ {
		executedCommands[i] = commands[i].AsExecutedCommand(i)
	}

	return executedCommands
}

func extend(slice []Command, element Command) []Command {
	n := len(slice)
	if n == cap(slice) {
		// Slice is full; must grow.
		// We double its size and add 1, so if the size is zero we still grow.
		newSlice := make([]Command, len(slice), 2*len(slice)+1)
		copy(newSlice, slice)
		slice = newSlice
	}
	slice = slice[0 : n+1]
	slice[n] = element
	return slice
}

// Append appends the items to the slice.
// First version: just loop calling Extend.
func append(slice []Command, items ...Command) []Command {
	for _, item := range items {
		slice = extend(slice, item)
	}
	return slice
}
<|endoftext|>


<commit_before>package project

import (
	"errors"
	"reflect"
)

// A Repository maintains a mapping between types and values.
type Repository struct {
	repoTypes map[reflect.Type]interface{}
}

// Register associates the repo object with the type of modelType.
func (r *Repository) Register(modelType, repo interface{}) {
	r.repoTypes[reflect.TypeOf(modelType)] = repo
}

// Get returns the repository associated with the type of the provided model returns an error if we can't find one.
func (r *Repository) Get(modelType interface{}) (interface{}, error) {
	repo, ok := r.repoTypes[reflect.TypeOf(modelType)]
	if !ok {
		return nil, errors.New("Couldn't find repo for model")
	}
	return repo, nil
}

func New() *Repository {
	return &Repository{make(map[reflect.Type]interface{})}
}

var defaultRepo *Repository = New()

// Associates the provided repository with the type uses the default Repository.
func Register(modelType, repo interface{}) {
	defaultRepo.Register(modelType, repo)
}

// Get returns the repository associated with the type of the provided model from the default Repository.
func Get(modelType interface{}) (interface{}, error) {
	return defaultRepo.Get(modelType)
}
<commit_msg>golint tidyups.<commit_after>package project

import (
	"errors"
	"reflect"
)

// A Repository maintains a mapping between types and values.
type Repository struct {
	repoTypes map[reflect.Type]interface{}
}

// Register associates the repo object with the type of modelType.
func (r *Repository) Register(modelType, repo interface{}) {
	r.repoTypes[reflect.TypeOf(modelType)] = repo
}

// Get returns the repository associated with the type of the provided model returns an error if we can't find one.
func (r *Repository) Get(modelType interface{}) (interface{}, error) {
	repo, ok := r.repoTypes[reflect.TypeOf(modelType)]
	if !ok {
		return nil, errors.New("couldn't find repo for model")
	}
	return repo, nil
}

// New Repository.
func New() *Repository {
	return &Repository{make(map[reflect.Type]interface{})}
}

var defaultRepo = New()

// Register the provided repository with the type uses the default Repository.
func Register(modelType, repo interface{}) {
	defaultRepo.Register(modelType, repo)
}

// Get returns the repository associated with the type of the provided model from the default Repository.
func Get(modelType interface{}) (interface{}, error) {
	return defaultRepo.Get(modelType)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

// thanks to https://github.com/influxdata/telegraf/blob/release-1.2/plugins/inputs/prometheus/parser.go
// and https://github.com/prometheus/common/tree/master/expfmt

package promrecv

import (
	"fmt"
	"io"
	stdlog "log"
	"math"
	"regexp"
	"strings"
	"sync"

	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/circonus-labs/circonus-agent/internal/tags"
	cgm "github.com/circonus-labs/circonus-gometrics/v3"
	"github.com/pkg/errors"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"
	"github.com/rs/zerolog/log"
	"github.com/spf13/viper"
)

var (
	id                  string
	nameCleanerRx       *regexp.Regexp
	metricNameSeparator = "`"
	metricsmu           sync.Mutex
	metrics             *cgm.CirconusMetrics
	parseRx             *regexp.Regexp
	logger              = log.With().Str("pkg", "promrecv").Logger()
)

func initCGM() error {
	metricsmu.Lock()
	defer metricsmu.Unlock()

	if metrics != nil {
		return nil
	}

	cmc := &cgm.Config{
		Debug: viper.GetBool(config.KeyDebugCGM),
		Log:   stdlog.New(log.With().Str("pkg", "promrecv").Logger(), "", 0),
	}
	// put cgm into manual mode (no interval, no api key, invalid submission url)
	cmc.Interval = "0"                            // disable automatic flush
	cmc.CheckManager.Check.SubmissionURL = "none" // disable check management (create/update)

	hm, err := cgm.NewCirconusMetrics(cmc)
	if err != nil {
		return errors.Wrap(err, "prom receiver cgm")
	}

	metrics = hm

	// inintialize any options for the receiver
	id = "prom"                                      // metric name (group) prefix to be used
	nameCleanerRx = regexp.MustCompile("[\r\n\"'`]") // used to strip unwanted characters

	return nil
}

// Flush returns current metrics
func Flush() *cgm.Metrics {
	initCGM()
	metricsmu.Lock()
	defer metricsmu.Unlock()

	return metrics.FlushMetrics()
}

// Parse handles incoming PUT/POST requests
func Parse(data io.ReadCloser) error {
	var parser expfmt.TextParser

	// formats supported from https://prometheus.io/docs/instrumenting/exposition_formats/

	metricFamilies, err := parser.TextToMetricFamilies(data)
	if err != nil {
		return err
	}

	initCGM()
	metricsmu.Lock()
	defer metricsmu.Unlock()

	for mn, mf := range metricFamilies {
		for _, m := range mf.Metric {
			metricName := id + metricNameSeparator + nameCleanerRx.ReplaceAllString(mn, "")
			streamTags := getLabels(m)
			if streamTags != "" {
				metricName += streamTags
			}
			if mf.GetType() == dto.MetricType_SUMMARY {
				metrics.Gauge(metricName+"_count", float64(m.GetSummary().GetSampleCount()))
				metrics.Gauge(metricName+"_sum", float64(m.GetSummary().GetSampleSum()))
				for qn, qv := range getQuantiles(m) {
					metrics.Gauge(metricName+"_"+qn, qv)
				}
			} else if mf.GetType() == dto.MetricType_HISTOGRAM {
				metrics.Gauge(metricName+"_count", float64(m.GetHistogram().GetSampleCount()))
				metrics.Gauge(metricName+"_sum", float64(m.GetHistogram().GetSampleSum()))
				for bn, bv := range getBuckets(m) {
					metrics.Gauge(metricName+"_"+bn, bv)
				}
			} else {
				if m.Gauge != nil {
					if m.GetGauge().Value != nil {
						metrics.Gauge(metricName, *m.GetGauge().Value)
					}
				} else if m.Counter != nil {
					if m.GetCounter().Value != nil {
						metrics.Gauge(metricName, *m.GetCounter().Value)
					}
				} else if m.Untyped != nil {
					if m.GetUntyped().Value != nil {
						metrics.Gauge(metricName, *m.GetUntyped().Value)
					}
				}
			}
		}
	}

	return nil
}

func getLabels(m *dto.Metric) string {
	labels := []string{}

	for _, label := range m.Label {
		if label.Name != nil && label.Value != nil {
			ln := nameCleanerRx.ReplaceAllString(*label.Name, "")
			lv := nameCleanerRx.ReplaceAllString(*label.Value, "")
			labels = append(labels, ln+tags.Delimiter+lv) // stream tags take form cat:val
		}
	}

	if len(labels) > 0 {
		tagList := strings.Join(labels, tags.Separator)
		t, err := tags.PrepStreamTags(tagList)
		if err != nil {
			logger.Warn().Err(err).Str("tags", tagList).Msg("ignoring labels")
		}
		if t != "" {
			return t
		}
	}

	return ""
}

func getQuantiles(m *dto.Metric) map[string]float64 {
	ret := make(map[string]float64)
	for _, q := range m.GetSummary().Quantile {
		if q.Value != nil && !math.IsNaN(*q.Value) {
			ret[fmt.Sprint(*q.Quantile)] = *q.Value
		}
	}
	return ret
}

func getBuckets(m *dto.Metric) map[string]uint64 {
	ret := make(map[string]uint64)
	for _, b := range m.GetHistogram().Bucket {
		if b.CumulativeCount != nil {
			ret[fmt.Sprint(*b.UpperBound)] = *b.CumulativeCount
		}
	}
	return ret
}
<commit_msg>upd: drop redundant locks<commit_after>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

// thanks to https://github.com/influxdata/telegraf/blob/release-1.2/plugins/inputs/prometheus/parser.go
// and https://github.com/prometheus/common/tree/master/expfmt

package promrecv

import (
	"fmt"
	"io"
	stdlog "log"
	"math"
	"regexp"
	"strings"
	"sync"

	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/circonus-labs/circonus-agent/internal/tags"
	cgm "github.com/circonus-labs/circonus-gometrics/v3"
	"github.com/pkg/errors"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/common/expfmt"
	"github.com/rs/zerolog/log"
	"github.com/spf13/viper"
)

var (
	id                  string
	nameCleanerRx       *regexp.Regexp
	metricNameSeparator = "`"
	metricsmu           sync.Mutex
	metrics             *cgm.CirconusMetrics
	parseRx             *regexp.Regexp
	logger              = log.With().Str("pkg", "promrecv").Logger()
)

func initCGM() error {
	metricsmu.Lock()
	defer metricsmu.Unlock()

	if metrics != nil {
		return nil
	}

	cmc := &cgm.Config{
		Debug: viper.GetBool(config.KeyDebugCGM),
		Log:   stdlog.New(log.With().Str("pkg", "promrecv").Logger(), "", 0),
	}
	// put cgm into manual mode (no interval, no api key, invalid submission url)
	cmc.Interval = "0"                            // disable automatic flush
	cmc.CheckManager.Check.SubmissionURL = "none" // disable check management (create/update)

	hm, err := cgm.NewCirconusMetrics(cmc)
	if err != nil {
		return errors.Wrap(err, "prom receiver cgm")
	}

	metrics = hm

	// inintialize any options for the receiver
	id = "prom"                                      // metric name (group) prefix to be used
	nameCleanerRx = regexp.MustCompile("[\r\n\"'`]") // used to strip unwanted characters

	return nil
}

// Flush returns current metrics
func Flush() *cgm.Metrics {
	initCGM()
	// metricsmu.Lock()
	// defer metricsmu.Unlock()

	return metrics.FlushMetrics()
}

// Parse handles incoming PUT/POST requests
func Parse(data io.ReadCloser) error {
	initCGM()
	// metricsmu.Lock()
	// defer metricsmu.Unlock()

	var parser expfmt.TextParser

	// formats supported from https://prometheus.io/docs/instrumenting/exposition_formats/

	metricFamilies, err := parser.TextToMetricFamilies(data)
	if err != nil {
		return err
	}

	for mn, mf := range metricFamilies {
		for _, m := range mf.Metric {
			metricName := id + metricNameSeparator + nameCleanerRx.ReplaceAllString(mn, "")
			streamTags := getLabels(m)
			if streamTags != "" {
				metricName += streamTags
			}
			if mf.GetType() == dto.MetricType_SUMMARY {
				metrics.Gauge(metricName+"_count", float64(m.GetSummary().GetSampleCount()))
				metrics.Gauge(metricName+"_sum", float64(m.GetSummary().GetSampleSum()))
				for qn, qv := range getQuantiles(m) {
					metrics.Gauge(metricName+"_"+qn, qv)
				}
			} else if mf.GetType() == dto.MetricType_HISTOGRAM {
				metrics.Gauge(metricName+"_count", float64(m.GetHistogram().GetSampleCount()))
				metrics.Gauge(metricName+"_sum", float64(m.GetHistogram().GetSampleSum()))
				for bn, bv := range getBuckets(m) {
					metrics.Gauge(metricName+"_"+bn, bv)
				}
			} else {
				if m.Gauge != nil {
					if m.GetGauge().Value != nil {
						metrics.Gauge(metricName, *m.GetGauge().Value)
					}
				} else if m.Counter != nil {
					if m.GetCounter().Value != nil {
						metrics.Gauge(metricName, *m.GetCounter().Value)
					}
				} else if m.Untyped != nil {
					if m.GetUntyped().Value != nil {
						metrics.Gauge(metricName, *m.GetUntyped().Value)
					}
				}
			}
		}
	}

	return nil
}

func getLabels(m *dto.Metric) string {
	labels := []string{}

	for _, label := range m.Label {
		if label.Name != nil && label.Value != nil {
			ln := nameCleanerRx.ReplaceAllString(*label.Name, "")
			lv := nameCleanerRx.ReplaceAllString(*label.Value, "")
			labels = append(labels, ln+tags.Delimiter+lv) // stream tags take form cat:val
		}
	}

	if len(labels) > 0 {
		tagList := strings.Join(labels, tags.Separator)
		t, err := tags.PrepStreamTags(tagList)
		if err != nil {
			logger.Warn().Err(err).Str("tags", tagList).Msg("ignoring labels")
		}
		if t != "" {
			return t
		}
	}

	return ""
}

func getQuantiles(m *dto.Metric) map[string]float64 {
	ret := make(map[string]float64)
	for _, q := range m.GetSummary().Quantile {
		if q.Value != nil && !math.IsNaN(*q.Value) {
			ret[fmt.Sprint(*q.Quantile)] = *q.Value
		}
	}
	return ret
}

func getBuckets(m *dto.Metric) map[string]uint64 {
	ret := make(map[string]uint64)
	for _, b := range m.GetHistogram().Bucket {
		if b.CumulativeCount != nil {
			ret[fmt.Sprint(*b.UpperBound)] = *b.CumulativeCount
		}
	}
	return ret
}
<|endoftext|>


<commit_before>package summary_test

import (
	"bytes"
	"github.com/go-task/task/v2/internal/logger"
	"github.com/go-task/task/v2/internal/summary"
	"github.com/go-task/task/v2/internal/taskfile"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestPrintsDependenciesIfPresent(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Deps: []*taskfile.Dep{
			{Task: "dep1"},
			{Task: "dep2"},
			{Task: "dep3"},
		},
	}

	summary.Print(&l, task)

	assert.Contains(t, buffer.String(), "\ndependencies:\n")
	assert.Contains(t, buffer.String(), "\n - dep1\n")
	assert.Contains(t, buffer.String(), "\n - dep2\n")
	assert.Contains(t, buffer.String(), "\n - dep3\n")
}

func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Deps: []*taskfile.Dep{},
	}

	summary.Print(&l, task)

	assert.NotContains(t, buffer.String(), "dependencies:")
}

func TestPrintTaskName(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Task: "my-task-name",
	}

	summary.Print(&l, task)

	assert.Contains(t, buffer.String(), "task: my-task-name\n")
}

func TestPrintTaskCommandsIfPresent(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Cmds: []*taskfile.Cmd{
			{Cmd: "command-1"},
			{Cmd: "command-2"},
			{Task: "task-1"},
		},
	}

	summary.Print(&l, task)

	assert.Contains(t, buffer.String(), "\ncommands:\n")
	assert.Contains(t, buffer.String(), "\n - command-1\n")
	assert.Contains(t, buffer.String(), "\n - command-2\n")
	assert.Contains(t, buffer.String(), "\n - Task: task-1\n")
}

func TestDoesNotPrintCommandIfMissing(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Cmds: []*taskfile.Cmd{},
	}

	summary.Print(&l, task)

	assert.NotContains(t, buffer.String(), "commands")
}

func TestFullSummary(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Task:    "sample-task",
		Summary: "line1\nline2\nline3\n",
		Deps: []*taskfile.Dep{
			{Task: "dependency"},
		},
		Cmds: []*taskfile.Cmd{
			{Cmd: "command"},
		},
	}

	summary.Print(&l, task)

	assert.Equal(t, expectedOutput(), buffer.String())
}

func expectedOutput() string {
	expected :=
		`task: sample-task

	line1
	line2
	line3

	dependencies:
	 - dependency

	commands:
	 - command
	`
	return expected
}
<commit_msg>refactoring<commit_after>package summary_test

import (
	"bytes"
	"github.com/go-task/task/v2/internal/logger"
	"github.com/go-task/task/v2/internal/summary"
	"github.com/go-task/task/v2/internal/taskfile"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestPrintsDependenciesIfPresent(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Deps: []*taskfile.Dep{
			{Task: "dep1"},
			{Task: "dep2"},
			{Task: "dep3"},
		},
	}

	summary.Print(&l, task)

	assert.Contains(t, buffer.String(), "\ndependencies:\n"+" - dep1\n"+" - dep2\n"+" - dep3\n")
}

func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Deps: []*taskfile.Dep{},
	}

	summary.Print(&l, task)

	assert.NotContains(t, buffer.String(), "dependencies:")
}

func TestPrintTaskName(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Task: "my-task-name",
	}

	summary.Print(&l, task)

	assert.Contains(t, buffer.String(), "task: my-task-name\n")
}

func TestPrintTaskCommandsIfPresent(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Cmds: []*taskfile.Cmd{
			{Cmd: "command-1"},
			{Cmd: "command-2"},
			{Task: "task-1"},
		},
	}

	summary.Print(&l, task)

	assert.Contains(t, buffer.String(), "\ncommands:\n")
	assert.Contains(t, buffer.String(), "\n - command-1\n")
	assert.Contains(t, buffer.String(), "\n - command-2\n")
	assert.Contains(t, buffer.String(), "\n - Task: task-1\n")
}

func TestDoesNotPrintCommandIfMissing(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Cmds: []*taskfile.Cmd{},
	}

	summary.Print(&l, task)

	assert.NotContains(t, buffer.String(), "commands")
}

func TestFullSummary(t *testing.T) {
	buffer := &bytes.Buffer{}
	l := logger.Logger{
		Stdout:  buffer,
		Stderr:  buffer,
		Verbose: false,
	}
	task := &taskfile.Task{
		Task:    "sample-task",
		Summary: "line1\nline2\nline3\n",
		Deps: []*taskfile.Dep{
			{Task: "dependency"},
		},
		Cmds: []*taskfile.Cmd{
			{Cmd: "command"},
		},
	}

	summary.Print(&l, task)

	assert.Equal(t, expectedOutput(), buffer.String())
}

func expectedOutput() string {
	expected := `task: sample-task

line1
line2
line3

dependencies:
 - dependency

commands:
 - command
`
	return expected
}
<|endoftext|>


<commit_before>package creds

import (
	"fmt"
	"strings"

	"github.com/akerl/timber/v2/log"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sts"

	"github.com/akerl/speculate/v2/version"
)

var logger = log.NewLogger("speculate")

var userAgentItems = [][]string{{"speculate", version.Version}}

// UserAgentItem defines an entry in the HTTP User Agent field
type UserAgentItem struct {
	Name, Version string
	Extra         []string
}

// Creds defines a set of AWS credentials
type Creds struct {
	AccessKey, SecretKey, SessionToken, Region string
	UserAgentItems                             []UserAgentItem
}

var namespaces = map[string]string{
	"aws":        "aws.amazon",
	"aws-us-gov": "amazonaws-us-gov",
}

func (c Creds) setUserAgent(sess *session.Session) {
	sess.Handlers.Build.PushBack(request.MakeAddToUserAgentHandler("speculate", version.Version))
	for _, x := range c.UserAgentItems {
		sess.Handlers.Build.PushBack(request.MakeAddToUserAgentHandler(x.Name, x.Version, x.Extra...))
	}
}

// Session returns an AWS SDK session suitable for making API clients
func (c Creds) Session() (*session.Session, error) {
	logger.InfoMsg("creating new session")
	config := aws.NewConfig()
	config.WithCredentialsChainVerboseErrors(true)
	if c.AccessKey != "" {
		logger.InfoMsgf("setting session credentials to %s", c.AccessKey)
		config.WithCredentials(c.ToSdk())
	}
	if c.Region != "" {
		logger.InfoMsgf("setting session region to %s", c.Region)
		config.WithRegion(c.Region)
	}
	sess, err := session.NewSessionWithOptions(session.Options{
		Config:            *config,
		SharedConfigState: session.SharedConfigEnable,
	})
	if err != nil {
		return sess, err
	}
	c.setUserAgent(sess)
	return sess, nil
}

// Client returns an AWS STS client for these creds
func (c Creds) Client() (*sts.STS, error) {
	logger.InfoMsg("creating new STS client")
	session, err := c.Session()
	if err != nil {
		return nil, err
	}
	return sts.New(session), nil
}

func (c Creds) identity() (*sts.GetCallerIdentityOutput, error) {
	client, err := c.Client()
	if err != nil {
		return nil, err
	}
	logger.InfoMsg("looking up identity")
	return client.GetCallerIdentity(&sts.GetCallerIdentityInput{})
}

func (c Creds) partition() (string, error) {
	logger.InfoMsg("looking up partition")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	pieces := strings.Split(*identity.Arn, ":")
	logger.InfoMsgf("found partition: %s", pieces[1])
	return pieces[1], nil
}

func (c Creds) namespace() (string, error) {
	logger.InfoMsg("looking up namespace")
	partition, err := c.partition()
	if err != nil {
		return "", err
	}
	result, ok := namespaces[partition]
	if ok {
		logger.InfoMsgf("found namespace: %s", result)
		return result, nil
	}
	return "", fmt.Errorf("unknown partition: %s", partition)
}

// AccountID returns the user's account ID
func (c Creds) AccountID() (string, error) {
	logger.InfoMsg("looking up accountID")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	logger.InfoMsgf("found accountID: %s", *identity.Account)
	return *identity.Account, nil
}

// MfaArn returns the user's virtual MFA token ARN
func (c Creds) MfaArn() (string, error) {
	logger.InfoMsg("looking up mfa arn")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	if !strings.Contains(*identity.Arn, ":user/") {
		return "", fmt.Errorf("failed to parse MFA ARN for non-user: %s", *identity.Arn)
	}
	mfaArn := strings.Replace(*identity.Arn, ":user/", ":mfa/", 1)
	logger.InfoMsgf("found mfa arn: %s", mfaArn)
	return mfaArn, nil
}

// UserName returns the current user name
func (c Creds) UserName() (string, error) {
	logger.InfoMsg("looking up current user name")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	arnChunks := strings.Split(*identity.Arn, "/")
	userName := arnChunks[len(arnChunks)-1]
	logger.InfoMsgf("found user name: %s", userName)
	return userName, nil
}
<commit_msg>shift to user agent item<commit_after>package creds

import (
	"fmt"
	"strings"

	"github.com/akerl/timber/v2/log"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sts"

	"github.com/akerl/speculate/v2/version"
)

var logger = log.NewLogger("speculate")

// UserAgentItem defines an entry in the HTTP User Agent field
type UserAgentItem struct {
	Name, Version string
	Extra         []string
}

var speculateUserAgentItem = UserAgentItem{
	Name:    "speculate",
	Version: version.Version,
}

// Creds defines a set of AWS credentials
type Creds struct {
	AccessKey, SecretKey, SessionToken, Region string
	UserAgentItems                             []UserAgentItem
}

var namespaces = map[string]string{
	"aws":        "aws.amazon",
	"aws-us-gov": "amazonaws-us-gov",
}

func addUserAgentHandler(sess *session.Session, item UserAgentItem) {
	h := request.MakeAddToUserAgentHandler(item.Name, item.Version, item.Extra...)
	sess.Handlers.Build.PushBack(h)
}

func (c Creds) setUserAgent(sess *session.Session) {
	addUserAgentHandler(sess, speculateUserAgentItem)
	for _, x := range c.UserAgentItems {
		addUserAgentHandler(sess, x)
	}
}

// Session returns an AWS SDK session suitable for making API clients
func (c Creds) Session() (*session.Session, error) {
	logger.InfoMsg("creating new session")
	config := aws.NewConfig()
	config.WithCredentialsChainVerboseErrors(true)
	if c.AccessKey != "" {
		logger.InfoMsgf("setting session credentials to %s", c.AccessKey)
		config.WithCredentials(c.ToSdk())
	}
	if c.Region != "" {
		logger.InfoMsgf("setting session region to %s", c.Region)
		config.WithRegion(c.Region)
	}
	sess, err := session.NewSessionWithOptions(session.Options{
		Config:            *config,
		SharedConfigState: session.SharedConfigEnable,
	})
	if err != nil {
		return sess, err
	}
	c.setUserAgent(sess)
	return sess, nil
}

// Client returns an AWS STS client for these creds
func (c Creds) Client() (*sts.STS, error) {
	logger.InfoMsg("creating new STS client")
	session, err := c.Session()
	if err != nil {
		return nil, err
	}
	return sts.New(session), nil
}

func (c Creds) identity() (*sts.GetCallerIdentityOutput, error) {
	client, err := c.Client()
	if err != nil {
		return nil, err
	}
	logger.InfoMsg("looking up identity")
	return client.GetCallerIdentity(&sts.GetCallerIdentityInput{})
}

func (c Creds) partition() (string, error) {
	logger.InfoMsg("looking up partition")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	pieces := strings.Split(*identity.Arn, ":")
	logger.InfoMsgf("found partition: %s", pieces[1])
	return pieces[1], nil
}

func (c Creds) namespace() (string, error) {
	logger.InfoMsg("looking up namespace")
	partition, err := c.partition()
	if err != nil {
		return "", err
	}
	result, ok := namespaces[partition]
	if ok {
		logger.InfoMsgf("found namespace: %s", result)
		return result, nil
	}
	return "", fmt.Errorf("unknown partition: %s", partition)
}

// AccountID returns the user's account ID
func (c Creds) AccountID() (string, error) {
	logger.InfoMsg("looking up accountID")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	logger.InfoMsgf("found accountID: %s", *identity.Account)
	return *identity.Account, nil
}

// MfaArn returns the user's virtual MFA token ARN
func (c Creds) MfaArn() (string, error) {
	logger.InfoMsg("looking up mfa arn")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	if !strings.Contains(*identity.Arn, ":user/") {
		return "", fmt.Errorf("failed to parse MFA ARN for non-user: %s", *identity.Arn)
	}
	mfaArn := strings.Replace(*identity.Arn, ":user/", ":mfa/", 1)
	logger.InfoMsgf("found mfa arn: %s", mfaArn)
	return mfaArn, nil
}

// UserName returns the current user name
func (c Creds) UserName() (string, error) {
	logger.InfoMsg("looking up current user name")
	identity, err := c.identity()
	if err != nil {
		return "", err
	}
	arnChunks := strings.Split(*identity.Arn, "/")
	userName := arnChunks[len(arnChunks)-1]
	logger.InfoMsgf("found user name: %s", userName)
	return userName, nil
}
<|endoftext|>


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

package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/googlecodelabs/tools/claat/render"
	"github.com/googlecodelabs/tools/claat/types"
	"github.com/googlecodelabs/tools/claat/util"
)

// Options type to make the CmdExport signature succinct.
type CmdExportOptions struct {
	// AuthToken is the token to use for the Drive API.
	AuthToken string
	// Expenv is the codelab environment to export to.
	Expenv string
	// ExtraVars is extra template variables.
	ExtraVars map[string]string
	// GlobalGA is the global Google Analytics account to use.
	GlobalGA string
	// Output is the output directory, or "-" for stdout.
	Output string
	// Prefix is a URL prefix to prepend when using HTML format.
	Prefix string
	// Srcs is the sources to export codelabs from.
	Srcs []string
	// Tmplout is the output format.
	Tmplout string
}

// CmdExport is the "claat export ..." subcommand.
// It returns a process exit code.
func CmdExport(opts CmdExportOptions) int {
	var exitCode int
	if len(opts.Srcs) == 0 {
		log.Fatalf("Need at least one source. Try '-h' for options.")
	}
	type result struct {
		src  string
		meta *types.Meta
		err  error
	}
	srcs := util.Unique(opts.Srcs)
	ch := make(chan *result, len(srcs))
	for _, src := range srcs {
		go func(src string) {
			meta, err := exportCodelab(src, opts)
			ch <- &result{src, meta, err}
		}(src)
	}
	for range srcs {
		res := <-ch
		if res.err != nil {
			exitCode = 1
			log.Printf(reportErr, res.src, res.err)
		} else if !isStdout(opts.Output) {
			log.Printf(reportOk, res.meta.ID)
		}
	}
	return exitCode
}

// exportCodelab fetches codelab src from either local disk or remote,
// parses and stores the results on disk, in a dir ancestored by output.
//
// Stored results include codelab content formatted in tmplout, its assets
// and metadata in JSON format.
//
// There's a special case where basedir has a value of "-", in which
// nothing is stored on disk and the only output, codelab formatted content,
// is printed to stdout.
func exportCodelab(src string, opts CmdExportOptions) (*types.Meta, error) {
	clab, err := slurpCodelab(src, opts.AuthToken)
	if err != nil {
		return nil, err
	}
	var client *http.Client // need for downloadImages
	if clab.typ == srcGoogleDoc {
		client, err = driveClient(opts.AuthToken)
		if err != nil {
			return nil, err
		}
	}

	// codelab export context
	lastmod := types.ContextTime(clab.mod)
	meta := &clab.Meta
	ctx := &types.Context{
		Source:  src,
		Env:     opts.Expenv,
		Format:  opts.Tmplout,
		Prefix:  opts.Prefix,
		MainGA:  opts.GlobalGA,
		Updated: &lastmod,
	}

	dir := opts.Output // output dir or stdout
	if !isStdout(dir) {
		dir = codelabDir(dir, meta)
		// download or copy codelab assets to disk, and rewrite image URLs
		mdir := filepath.Join(dir, imgDirname)
		if _, err := slurpImages(client, src, mdir, clab.Steps); err != nil {
			return nil, err
		}
	}
	// write codelab and its metadata to disk
	return meta, writeCodelab(dir, clab.Codelab, opts.ExtraVars, ctx)
}

// writeCodelab stores codelab main content in ctx.Format and its metadata
// in JSON format on disk.
// extraVars is extra variables to pass into the template context.
func writeCodelab(dir string, clab *types.Codelab, extraVars map[string]string, ctx *types.Context) error {
	// output to stdout does not include metadata
	if !isStdout(dir) {
		// make sure codelab dir exists
		if err := os.MkdirAll(dir, 0755); err != nil {
			return err
		}
		// codelab metadata
		cm := &types.ContextMeta{Context: *ctx, Meta: clab.Meta}
		f := filepath.Join(dir, metaFilename)
		if err := writeMeta(f, cm); err != nil {
			return err
		}
	}

	// main content file(s)
	data := &struct {
		render.Context
		Current *types.Step
		StepNum int
		Prev    bool
		Next    bool
	}{Context: render.Context{
		Env:      ctx.Env,
		Prefix:   ctx.Prefix,
		GlobalGA: ctx.MainGA,
		Meta:     &clab.Meta,
		Steps:    clab.Steps,
		Extra:    extraVars,
	}}
	if ctx.Format != "offline" {
		w := os.Stdout
		if !isStdout(dir) {
			ext := ctx.Format
			if ext != "md" {
				ext = "html"
			}
			f, err := os.Create(filepath.Join(dir, "index."+ext))
			if err != nil {
				return err
			}
			w = f
			defer f.Close()
		}
		return render.Execute(w, ctx.Format, data)
	}
	for i, step := range clab.Steps {
		data.Current = step
		data.StepNum = i + 1
		data.Prev = i > 0
		data.Next = i < len(clab.Steps)-1
		w := os.Stdout
		if !isStdout(dir) {
			name := "index.html"
			if i > 0 {
				name = fmt.Sprintf("step-%d.html", i+1)
			}
			f, err := os.Create(filepath.Join(dir, name))
			if err != nil {
				return err
			}
			w = f
			defer f.Close()
		}
		if err := render.Execute(w, ctx.Format, data); err != nil {
			return err
		}
	}
	return nil
}

func slurpImages(client *http.Client, src, dir string, steps []*types.Step) (map[string]string, error) {
	// make sure img dir exists
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, err
	}

	type res struct {
		url, file string
		err       error
	}

	ch := make(chan *res, 100)
	defer close(ch)
	var count int
	for _, st := range steps {
		nodes := imageNodes(st.Content.Nodes)
		count += len(nodes)
		for _, n := range nodes {
			go func(n *types.ImageNode) {
				url := n.Src
				file, err := slurpBytes(client, src, dir, url)
				if err == nil {
					n.Src = filepath.Join(imgDirname, file)
				}
				ch <- &res{url, file, err}
			}(n)
		}
	}

	var err error
	imap := make(map[string]string, count)
	for i := 0; i < count; i++ {
		r := <-ch
		imap[r.file] = r.url
		if r.err != nil && err == nil {
			// record first error
			err = fmt.Errorf("%s => %s: %v", r.url, r.file, r.err)
		}
	}

	return imap, err
}

// imageNodes filters out everything except types.NodeImage nodes, recursively.
func imageNodes(nodes []types.Node) []*types.ImageNode {
	var imgs []*types.ImageNode
	for _, n := range nodes {
		switch n := n.(type) {
		case *types.ImageNode:
			imgs = append(imgs, n)
		case *types.ListNode:
			imgs = append(imgs, imageNodes(n.Nodes)...)
		case *types.ItemsListNode:
			for _, i := range n.Items {
				imgs = append(imgs, imageNodes(i.Nodes)...)
			}
		case *types.HeaderNode:
			imgs = append(imgs, imageNodes(n.Content.Nodes)...)
		case *types.URLNode:
			imgs = append(imgs, imageNodes(n.Content.Nodes)...)
		case *types.ButtonNode:
			imgs = append(imgs, imageNodes(n.Content.Nodes)...)
		case *types.InfoboxNode:
			imgs = append(imgs, imageNodes(n.Content.Nodes)...)
		case *types.GridNode:
			for _, r := range n.Rows {
				for _, c := range r {
					imgs = append(imgs, imageNodes(c.Content.Nodes)...)
				}
			}
		}
	}
	return imgs
}

// importNodes filters out everything except types.NodeImport nodes, recursively.
func importNodes(nodes []types.Node) []*types.ImportNode {
	var imps []*types.ImportNode
	for _, n := range nodes {
		switch n := n.(type) {
		case *types.ImportNode:
			imps = append(imps, n)
		case *types.ListNode:
			imps = append(imps, importNodes(n.Nodes)...)
		case *types.InfoboxNode:
			imps = append(imps, importNodes(n.Content.Nodes)...)
		case *types.GridNode:
			for _, r := range n.Rows {
				for _, c := range r {
					imps = append(imps, importNodes(c.Content.Nodes)...)
				}
			}
		}
	}
	return imps
}

// writeMeta writes codelab metadata to a local disk location
// specified by path.
func writeMeta(path string, cm *types.ContextMeta) error {
	b, err := json.MarshalIndent(cm, "", "  ")
	if err != nil {
		return err
	}
	b = append(b, '\n')
	return ioutil.WriteFile(path, b, 0644)
}

// codelabDir returns codelab root directory.
// The base argument is codelab parent directory.
func codelabDir(base string, m *types.Meta) string {
	return filepath.Join(base, m.ID)
}
<commit_msg>Update html.go<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package render

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	"io"
	"sort"
	"strconv"
	"strings"

	"github.com/googlecodelabs/tools/claat/types"
)

// TODO: render HTML using golang/x/net/html or template.

var (
	doubleQuote = []byte{'"'}
	lessThan    = []byte{'<'}
	greaterThan = []byte{'>'}
	newLine     = []byte{'\n'}
)

// HTML renders nodes as the markup for the target env.
func HTML(env string, nodes ...types.Node) (htmlTemplate.HTML, error) {
	var buf bytes.Buffer
	if err := WriteHTML(&buf, env, nodes...); err != nil {
		return "", err
	}
	return htmlTemplate.HTML(buf.String()), nil
}

// WriteHTML does the same as HTML but outputs rendered markup to w.
func WriteHTML(w io.Writer, env string, nodes ...types.Node) error {
	hw := htmlWriter{w: w, env: env}
	return hw.write(nodes...)
}

// ReplaceDoubleCurlyBracketsWithEntity replaces Double Curly Brackets with their charater entity.
func ReplaceDoubleCurlyBracketsWithEntity(s string) string {
	return strings.Replace(s, "{{", "{{", -1)
}

type htmlWriter struct {
	w   io.Writer // output writer
	env string    // target environment
	err error     // error during any writeXxx methods
}

func (hw *htmlWriter) matchEnv(v []string) bool {
	if len(v) == 0 || hw.env == "" {
		return true
	}
	i := sort.SearchStrings(v, hw.env)
	return i < len(v) && v[i] == hw.env
}

func (hw *htmlWriter) write(nodes ...types.Node) error {
	for _, n := range nodes {
		if !hw.matchEnv(n.Env()) {
			continue
		}
		switch n := n.(type) {
		case *types.TextNode:
			hw.text(n)
		case *types.ImageNode:
			hw.image(n)
		case *types.URLNode:
			hw.url(n)
		case *types.ButtonNode:
			hw.button(n)
		case *types.CodeNode:
			hw.code(n)
			hw.writeBytes(newLine)
		case *types.ListNode:
			hw.list(n)
			hw.writeBytes(newLine)
		case *types.ImportNode:
			if len(n.Content.Nodes) == 0 {
				break
			}
			hw.list(n.Content)
			hw.writeBytes(newLine)
		case *types.ItemsListNode:
			hw.itemsList(n)
			hw.writeBytes(newLine)
		case *types.GridNode:
			hw.grid(n)
			hw.writeBytes(newLine)
		case *types.InfoboxNode:
			hw.infobox(n)
			hw.writeBytes(newLine)
		case *types.SurveyNode:
			hw.survey(n)
			hw.writeBytes(newLine)
		case *types.HeaderNode:
			hw.header(n)
			hw.writeBytes(newLine)
		case *types.YouTubeNode:
			hw.youtube(n)
			hw.writeBytes(newLine)
		}
		if hw.err != nil {
			return hw.err
		}
	}
	return nil
}

func (hw *htmlWriter) writeBytes(b []byte) {
	if hw.err != nil {
		return
	}
	_, hw.err = hw.w.Write(b)
}

func (hw *htmlWriter) writeString(s string) {
	hw.writeBytes([]byte(s))
}

func (hw *htmlWriter) writeFmt(f string, a ...interface{}) {
	hw.writeString(fmt.Sprintf(f, a...))
}

func (hw *htmlWriter) writeEscape(s string) {
	s = htmlTemplate.HTMLEscapeString(s)
	hw.writeString(ReplaceDoubleCurlyBracketsWithEntity(s))
}

func (hw *htmlWriter) text(n *types.TextNode) {
	if n.Bold {
		hw.writeString("")
	}
	if n.Italic {
		hw.writeString("")
	}
	if n.Code {
		hw.writeString("")
	}
	s := htmlTemplate.HTMLEscapeString(n.Value)
	s = ReplaceDoubleCurlyBracketsWithEntity(s)
	hw.writeString(strings.Replace(s, "\n", "
", -1))
	if n.Code {
		hw.writeString("")
	}
	if n.Italic {
		hw.writeString("")
	}
	if n.Bold {
		hw.writeString("")
	}
}

func (hw *htmlWriter) image(n *types.ImageNode) {
	hw.writeString(" 0 {
		hw.writeFmt(` style="max-width: %.2fpx"`, n.MaxWidth)
	}
	hw.writeString(` src="`)
	hw.writeString(n.Src)
	hw.writeBytes(doubleQuote)
	hw.writeBytes(greaterThan)
}

func (hw *htmlWriter) url(n *types.URLNode) {
	hw.writeString("<a")
	if n.URL != "" {
		hw.writeString(` href="`)
		hw.writeString(n.URL)
		hw.writeBytes(doubleQuote)
	}
	if n.Name != "" {
		hw.writeString(` name="`)
		hw.writeEscape(n.Name)
		hw.writeBytes(doubleQuote)
	}
	if n.Target != "" {
		hw.writeString(` target="`)
		hw.writeEscape(n.Target)
		hw.writeBytes(doubleQuote)
	}
	hw.writeBytes(greaterThan)
	hw.write(n.Content.Nodes...)
	hw.writeString("")
}

func (hw *htmlWriter) button(n *types.ButtonNode) {
	hw.writeString("<paper-button")
	if n.Colored {
		hw.writeString(` class="colored"`)
	}
	if n.Raised {
		hw.writeString(" raised")
	}
	hw.writeBytes(greaterThan)
	if n.Download {
		hw.writeString(`<iron-icon icon="file-download"></iron-icon>`)
	}
	hw.write(n.Content.Nodes...)
	hw.writeString("</paper-button>")
}

func (hw *htmlWriter) code(n *types.CodeNode) {
	hw.writeString("
")
	if !n.Term {
		hw.writeString("<code")
		if n.Lang != "" {
			hw.writeFmt(" language=%q class=%q", n.Lang, n.Lang)
		}
		hw.writeBytes(greaterThan)
	}
	hw.writeEscape(n.Value)
	if !n.Term {
		hw.writeString("")
	}
	hw.writeString("
")
}

func (hw *htmlWriter) list(n *types.ListNode) {
	wrap := n.Block() == true
	if wrap {
		hw.writeString("
")
	}
	hw.write(n.Nodes...)
	if wrap {
		hw.writeString("
")
	}
}

func (hw *htmlWriter) itemsList(n *types.ItemsListNode) {
	tag := "ul"
	if n.Type() == types.NodeItemsList && (n.Start > 0 || n.ListType != "") {
		tag = "ol"
	}
	hw.writeBytes(lessThan)
	hw.writeString(tag)
	switch n.Type() {
	case types.NodeItemsCheck:
		hw.writeString(` class="checklist"`)
	case types.NodeItemsFAQ:
		hw.writeString(` class="faq"`)
	default:
		if n.ListType != "" {
			hw.writeString(` type="`)
			hw.writeString(n.ListType)
			hw.writeBytes(doubleQuote)
		}
		if n.Start > 0 {
			hw.writeFmt(` start="%d"`, n.Start)
		}
	}
	hw.writeBytes(greaterThan)
	hw.writeBytes(newLine)

	for _, i := range n.Items {
		hw.writeString("
")
		hw.write(i.Nodes...)
		hw.writeString("
\n")
	}

	hw.writeString("</")
	hw.writeString(tag)
	hw.writeBytes(greaterThan)
}

func (hw *htmlWriter) grid(n *types.GridNode) {
	hw.writeString("
\n")
	for _, r := range n.Rows {
		hw.writeString("
")
		for _, c := range r {
			hw.writeFmt(`<td colspan="%d" rowspan="%d">`, c.Colspan, c.Rowspan)
			hw.write(c.Content.Nodes...)
			hw.writeString("	")
		}
		hw.writeString("
\n")
	}
	hw.writeString("
")
}

func (hw *htmlWriter) infobox(n *types.InfoboxNode) {
	hw.writeString(`<aside class="`)
	hw.writeEscape(string(n.Kind))
	hw.writeString(`">`)
	hw.write(n.Content.Nodes...)
	hw.writeString("")
}

func (hw *htmlWriter) survey(n *types.SurveyNode) {
	hw.writeString(`<google-codelab-survey survey-id="`)
	hw.writeString(n.ID)
	hw.writeBytes(doubleQuote)
	hw.writeString(">\n")
	for _, g := range n.Groups {
		hw.writeString("
")
		hw.writeEscape(g.Name)
		hw.writeString("
\n<paper-radio-group>\n")
		for _, o := range g.Options {
			hw.writeString("<paper-radio-button>")
			hw.writeEscape(o)
			hw.writeString("</paper-radio-button>\n")
		}
		hw.writeString("</paper-radio-group>\n")
	}
	hw.writeString("</google-codelab-survey>")
}

func (hw *htmlWriter) header(n *types.HeaderNode) {
	tag := "h" + strconv.Itoa(n.Level)
	hw.writeBytes(lessThan)
	hw.writeString(tag)
	switch n.Type() {
	case types.NodeHeaderCheck:
		hw.writeString(` class="checklist"`)
	case types.NodeHeaderFAQ:
		hw.writeString(` class="faq"`)
	}
	hw.writeBytes(greaterThan)
	hw.write(n.Content.Nodes...)
	hw.writeString("</")
	hw.writeString(tag)
	hw.writeBytes(greaterThan)
}

func (hw *htmlWriter) youtube(n *types.YouTubeNode) {
	hw.writeFmt("<google-youtube fluid video-id=%q></google-youtube>", n.VideoID)
}
<|endoftext|>


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

// The path package implements utility routines for manipulating
// slash-separated filename paths.
package path

import (
	"io/ioutil"
	"os"
	"strings"
)

// Clean returns the shortest path name equivalent to path
// by purely lexical processing.  It applies the following rules
// iteratively until no further processing can be done:
//
//	1. Replace multiple slashes with a single slash.
//	2. Eliminate each . path name element (the current directory).
//	3. Eliminate each inner .. path name element (the parent directory)
//	   along with the non-.. element that precedes it.
//	4. Eliminate .. elements that begin a rooted path:
//	   that is, replace "/.." by "/" at the beginning of a path.
//
// If the result of this process is an empty string, Clean
// returns the string ".".
//
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot right,''
// http://plan9.bell-labs.com/sys/doc/lexnames.html
func Clean(path string) string {
	if path == "" {
		return "."
	}

	rooted := path[0] == '/'
	n := len(path)

	// Invariants:
	//	reading from path; r is index of next byte to process.
	//	writing to buf; w is index of next byte to write.
	//	dotdot is index in buf where .. must stop, either because
	//		it is the leading slash or it is a leading ../../.. prefix.
	buf := []byte(path)
	r, w, dotdot := 0, 0, 0
	if rooted {
		r, w, dotdot = 1, 1, 1
	}

	for r < n {
		switch {
		case path[r] == '/':
			// empty path element
			r++
		case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
			// . element
			r++
		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
			// .. element: remove to last /
			r += 2
			switch {
			case w > dotdot:
				// can backtrack
				w--
				for w > dotdot && buf[w] != '/' {
					w--
				}
			case !rooted:
				// cannot backtrack, but not rooted, so append .. element.
				if w > 0 {
					buf[w] = '/'
					w++
				}
				buf[w] = '.'
				w++
				buf[w] = '.'
				w++
				dotdot = w
			}
		default:
			// real path element.
			// add slash if needed
			if rooted && w != 1 || !rooted && w != 0 {
				buf[w] = '/'
				w++
			}
			// copy element
			for ; r < n && path[r] != '/'; r++ {
				buf[w] = path[r]
				w++
			}
		}
	}

	// Turn empty string into "."
	if w == 0 {
		buf[w] = '.'
		w++
	}

	return string(buf[0:w])
}

// Split splits path immediately following the final slash,
// separating it into a directory and file name component.
// If there is no slash in path, DirFile returns an empty dir and
// file set to path.
func Split(path string) (dir, file string) {
	for i := len(path) - 1; i >= 0; i-- {
		if path[i] == '/' {
			return path[0 : i+1], path[i+1:]
		}
	}
	return "", path
}

// Join joins any number of path elements into a single path, adding a
// separating slash if necessary.  All empty strings are ignored.
func Join(elem ...string) string {
	for i, e := range elem {
		if e != "" {
			return Clean(strings.Join(elem[i:], "/"))
		}
	}
	return ""
}

// Ext returns the file name extension used by path.
// The extension is the suffix beginning at the final dot
// in the final slash-separated element of path;
// it is empty if there is no dot.
func Ext(path string) string {
	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
		if path[i] == '.' {
			return path[i:]
		}
	}
	return ""
}

// Visitor methods are invoked for corresponding file tree entries
// visited by Walk. The parameter path is the full path of d relative
// to root.
type Visitor interface {
	VisitDir(path string, f *os.FileInfo) bool
	VisitFile(path string, f *os.FileInfo)
}

func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) {
	if !f.IsDirectory() {
		v.VisitFile(path, f)
		return
	}

	if !v.VisitDir(path, f) {
		return // skip directory entries
	}

	list, err := ioutil.ReadDir(path)
	if err != nil {
		if errors != nil {
			errors <- err
		}
	}

	for _, e := range list {
		walk(Join(path, e.Name), e, v, errors)
	}
}

// Walk walks the file tree rooted at root, calling v.VisitDir or
// v.VisitFile for each directory or file in the tree, including root.
// If v.VisitDir returns false, Walk skips the directory's entries;
// otherwise it invokes itself for each directory entry in sorted order.
// An error reading a directory does not abort the Walk.
// If errors != nil, Walk sends each directory read error
// to the channel.  Otherwise Walk discards the error.
func Walk(root string, v Visitor, errors chan<- os.Error) {
	f, err := os.Lstat(root)
	if err != nil {
		if errors != nil {
			errors <- err
		}
		return // can't progress
	}
	walk(root, f, v, errors)
}

// Base returns the last path element of the slash-separated name.
// Trailing slashes are removed before extracting the last element.  If the name is
// empty, "." is returned.  If it consists entirely of slashes, "/" is returned.
func Base(name string) string {
	if name == "" {
		return "."
	}
	// Strip trailing slashes.
	for len(name) > 0 && name[len(name)-1] == '/' {
		name = name[0 : len(name)-1]
	}
	// Find the last element
	if i := strings.LastIndex(name, "/"); i >= 0 {
		name = name[i+1:]
	}
	// If empty now, it had only slashes.
	if name == "" {
		return "/"
	}
	return name
}
<commit_msg>path: fix comment for Split()<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The path package implements utility routines for manipulating
// slash-separated filename paths.
package path

import (
	"io/ioutil"
	"os"
	"strings"
)

// Clean returns the shortest path name equivalent to path
// by purely lexical processing.  It applies the following rules
// iteratively until no further processing can be done:
//
//	1. Replace multiple slashes with a single slash.
//	2. Eliminate each . path name element (the current directory).
//	3. Eliminate each inner .. path name element (the parent directory)
//	   along with the non-.. element that precedes it.
//	4. Eliminate .. elements that begin a rooted path:
//	   that is, replace "/.." by "/" at the beginning of a path.
//
// If the result of this process is an empty string, Clean
// returns the string ".".
//
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot right,''
// http://plan9.bell-labs.com/sys/doc/lexnames.html
func Clean(path string) string {
	if path == "" {
		return "."
	}

	rooted := path[0] == '/'
	n := len(path)

	// Invariants:
	//	reading from path; r is index of next byte to process.
	//	writing to buf; w is index of next byte to write.
	//	dotdot is index in buf where .. must stop, either because
	//		it is the leading slash or it is a leading ../../.. prefix.
	buf := []byte(path)
	r, w, dotdot := 0, 0, 0
	if rooted {
		r, w, dotdot = 1, 1, 1
	}

	for r < n {
		switch {
		case path[r] == '/':
			// empty path element
			r++
		case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
			// . element
			r++
		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
			// .. element: remove to last /
			r += 2
			switch {
			case w > dotdot:
				// can backtrack
				w--
				for w > dotdot && buf[w] != '/' {
					w--
				}
			case !rooted:
				// cannot backtrack, but not rooted, so append .. element.
				if w > 0 {
					buf[w] = '/'
					w++
				}
				buf[w] = '.'
				w++
				buf[w] = '.'
				w++
				dotdot = w
			}
		default:
			// real path element.
			// add slash if needed
			if rooted && w != 1 || !rooted && w != 0 {
				buf[w] = '/'
				w++
			}
			// copy element
			for ; r < n && path[r] != '/'; r++ {
				buf[w] = path[r]
				w++
			}
		}
	}

	// Turn empty string into "."
	if w == 0 {
		buf[w] = '.'
		w++
	}

	return string(buf[0:w])
}

// Split splits path immediately following the final slash,
// separating it into a directory and file name component.
// If there is no slash in path, Split returns an empty dir and
// file set to path.
func Split(path string) (dir, file string) {
	for i := len(path) - 1; i >= 0; i-- {
		if path[i] == '/' {
			return path[0 : i+1], path[i+1:]
		}
	}
	return "", path
}

// Join joins any number of path elements into a single path, adding a
// separating slash if necessary.  All empty strings are ignored.
func Join(elem ...string) string {
	for i, e := range elem {
		if e != "" {
			return Clean(strings.Join(elem[i:], "/"))
		}
	}
	return ""
}

// Ext returns the file name extension used by path.
// The extension is the suffix beginning at the final dot
// in the final slash-separated element of path;
// it is empty if there is no dot.
func Ext(path string) string {
	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
		if path[i] == '.' {
			return path[i:]
		}
	}
	return ""
}

// Visitor methods are invoked for corresponding file tree entries
// visited by Walk. The parameter path is the full path of d relative
// to root.
type Visitor interface {
	VisitDir(path string, f *os.FileInfo) bool
	VisitFile(path string, f *os.FileInfo)
}

func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) {
	if !f.IsDirectory() {
		v.VisitFile(path, f)
		return
	}

	if !v.VisitDir(path, f) {
		return // skip directory entries
	}

	list, err := ioutil.ReadDir(path)
	if err != nil {
		if errors != nil {
			errors <- err
		}
	}

	for _, e := range list {
		walk(Join(path, e.Name), e, v, errors)
	}
}

// Walk walks the file tree rooted at root, calling v.VisitDir or
// v.VisitFile for each directory or file in the tree, including root.
// If v.VisitDir returns false, Walk skips the directory's entries;
// otherwise it invokes itself for each directory entry in sorted order.
// An error reading a directory does not abort the Walk.
// If errors != nil, Walk sends each directory read error
// to the channel.  Otherwise Walk discards the error.
func Walk(root string, v Visitor, errors chan<- os.Error) {
	f, err := os.Lstat(root)
	if err != nil {
		if errors != nil {
			errors <- err
		}
		return // can't progress
	}
	walk(root, f, v, errors)
}

// Base returns the last path element of the slash-separated name.
// Trailing slashes are removed before extracting the last element.  If the name is
// empty, "." is returned.  If it consists entirely of slashes, "/" is returned.
func Base(name string) string {
	if name == "" {
		return "."
	}
	// Strip trailing slashes.
	for len(name) > 0 && name[len(name)-1] == '/' {
		name = name[0 : len(name)-1]
	}
	// Find the last element
	if i := strings.LastIndex(name, "/"); i >= 0 {
		name = name[i+1:]
	}
	// If empty now, it had only slashes.
	if name == "" {
		return "/"
	}
	return name
}
<|endoftext|>


<commit_before>package crypto

import (
	"crypto/rand"
	"github.com/conformal/btcec"
	"math/big"
)

type Key interface {
	Sign(b []byte) ([]byte, error)
	PublicCompressed() []byte
	PrivateBytes() []byte
}

type baseKey struct {
	priv btcec.PrivateKey
}

// Returns DER encoded signature from input hash
func (k *baseKey) Sign(hash []byte) ([]byte, error) {
	sig, err := k.priv.Sign(hash)
	if err != nil {
		return nil, err
	}
	return sig.Serialize(), nil
}

// Verifies a hash using DER encoded signature
func Verify(pubKey, signature, hash []byte) (bool, error) {
	sig, err := btcec.ParseDERSignature(signature, btcec.S256())
	if err != nil {
		return false, err
	}
	pk, err := btcec.ParsePubKey(pubKey, btcec.S256())
	if err != nil {
		return false, nil
	}
	return sig.Verify(hash, pk), nil
}

func (k *baseKey) PrivateBytes() []byte {
	return k.priv.D.Bytes()
}

func (k *baseKey) PublicCompressed() []byte {
	return (*btcec.PublicKey)(&k.priv.PublicKey).SerializeCompressed()
}

type RootDeterministicKey struct {
	baseKey
	Seed Hash
}

type AccountKey struct {
	baseKey
}

var order = btcec.S256().N

type genFunc func(*big.Int) bool

func newKey(priv, inc *big.Int, f genFunc) (*baseKey, error) {
	pk := big.NewInt(0).Set(priv)
	for ; f(pk); inc.Add(inc, one) {
		pk.SetBytes(Sha512Half(inc.Bytes()))
	}
	privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pk.Bytes())
	return &baseKey{*privKey}, nil
}

func ParsePublicKey(b []byte) (*btcec.PublicKey, error) {
	key, err := btcec.ParsePubKey(b, btcec.S256())
	return (*btcec.PublicKey)(key), err
}

func ParsePublicKeyFromHash(hash []byte) (*btcec.PublicKey, error) {
	h, err := NewRippleHash(string(hash))
	if err != nil {
		return nil, err
	}
	return ParsePublicKey(h.Payload())
}

// If seed is nil, generate a random one
func GenerateRootDeterministicKey(seed []byte) (*RootDeterministicKey, error) {
	if seed == nil {
		seed = make([]byte, 16)
		_, err := rand.Read(seed)
		if err != nil {
			return nil, err
		}
	}
	s, err := NewRippleFamilySeed(seed)
	if err != nil {
		return nil, err
	}
	inc := big.NewInt(0).SetBytes(seed)
	inc.Lsh(inc, 32)
	f := func(priv *big.Int) bool { return priv.Cmp(order) >= 0 }
	key, err := newKey(order, inc, f)
	if err != nil {
		return nil, err
	}
	key.priv.X, key.priv.Y = key.priv.ScalarBaseMult(key.PrivateBytes())
	return &RootDeterministicKey{
		baseKey: *key,
		Seed:    s,
	}, nil
}

func (r *RootDeterministicKey) GenerateAccountId(sequence int32) (Hash, error) {
	inc := big.NewInt(0).SetBytes(r.PublicCompressed())
	inc.Lsh(inc, 32).Add(inc, big.NewInt(int64(sequence))).Lsh(inc, 32).Add(inc, zero)
	f := func(priv *big.Int) bool { return priv.Cmp(order) >= 0 }
	key, err := newKey(order, inc, f)
	if err != nil {
		return nil, err
	}
	key.priv.X, key.priv.Y = key.priv.ScalarBaseMult(key.PrivateBytes())
	key.priv.X, key.priv.Y = key.priv.Add(key.priv.X, key.priv.Y, r.priv.X, r.priv.Y)
	b := Sha256RipeMD160(key.PublicCompressed())
	return NewRippleAccount(b)
}

func (r *RootDeterministicKey) GenerateAccountKey(sequence int32) (*AccountKey, error) {
	generator := big.NewInt(0).SetBytes(r.PublicCompressed())
	inc := big.NewInt(0).Set(generator)
	inc.Lsh(inc, 32).Add(inc, big.NewInt(int64(sequence))).Lsh(inc, 32).Add(inc, zero)
	f := func(priv *big.Int) bool { return priv.Cmp(generator) >= 0 || priv.Cmp(zero) <= 0 }
	key, err := newKey(zero, inc, f)
	if err != nil {
		return nil, err
	}
	key.priv.D.Add(key.priv.D, r.priv.D).Mod(key.priv.D, order)
	key.priv.X, key.priv.Y = key.priv.Curve.ScalarBaseMult(key.PrivateBytes())
	return &AccountKey{baseKey: *key}, nil
}

func (r *RootDeterministicKey) PublicGenerator() (Hash, error) {
	return NewRippleFamilyGenerator(r.PublicCompressed())
}

func (r *RootDeterministicKey) PublicNodeKey() (Hash, error) {
	return NewRipplePublicNode(r.PublicCompressed())
}

func (r *RootDeterministicKey) PrivateNodeKey() (Hash, error) {
	return NewRipplePrivateNode(r.PrivateBytes())
}

func (a *AccountKey) PublicAccountKey() (Hash, error) {
	return NewRipplePublicAccount(a.PublicCompressed())
}

func (a *AccountKey) PrivateAccountKey() (Hash, error) {
	return NewRipplePrivateAccount(a.PrivateBytes())
}
<commit_msg>Remove unnecessary error handling in key.go<commit_after>package crypto

import (
	"crypto/rand"
	"github.com/conformal/btcec"
	"math/big"
)

type Key interface {
	Sign(b []byte) ([]byte, error)
	PublicCompressed() []byte
	PrivateBytes() []byte
}

type baseKey struct {
	priv btcec.PrivateKey
}

// Returns DER encoded signature from input hash
func (k *baseKey) Sign(hash []byte) ([]byte, error) {
	sig, err := k.priv.Sign(hash)
	if err != nil {
		return nil, err
	}
	return sig.Serialize(), nil
}

// Verifies a hash using DER encoded signature
func Verify(pubKey, signature, hash []byte) (bool, error) {
	sig, err := btcec.ParseDERSignature(signature, btcec.S256())
	if err != nil {
		return false, err
	}
	pk, err := btcec.ParsePubKey(pubKey, btcec.S256())
	if err != nil {
		return false, nil
	}
	return sig.Verify(hash, pk), nil
}

func (k *baseKey) PrivateBytes() []byte {
	return k.priv.D.Bytes()
}

func (k *baseKey) PublicCompressed() []byte {
	return (*btcec.PublicKey)(&k.priv.PublicKey).SerializeCompressed()
}

type RootDeterministicKey struct {
	baseKey
	Seed Hash
}

type AccountKey struct {
	baseKey
}

var order = btcec.S256().N

type genFunc func(*big.Int) bool

func newKey(priv, inc *big.Int, f genFunc) *baseKey {
	pk := big.NewInt(0).Set(priv)
	for ; f(pk); inc.Add(inc, one) {
		pk.SetBytes(Sha512Half(inc.Bytes()))
	}
	privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pk.Bytes())
	return &baseKey{*privKey}
}

func ParsePublicKey(b []byte) (*btcec.PublicKey, error) {
	key, err := btcec.ParsePubKey(b, btcec.S256())
	return (*btcec.PublicKey)(key), err
}

func ParsePublicKeyFromHash(hash []byte) (*btcec.PublicKey, error) {
	h, err := NewRippleHash(string(hash))
	if err != nil {
		return nil, err
	}
	return ParsePublicKey(h.Payload())
}

// If seed is nil, generate a random one
func GenerateRootDeterministicKey(seed []byte) (*RootDeterministicKey, error) {
	if seed == nil {
		seed = make([]byte, 16)
		_, err := rand.Read(seed)
		if err != nil {
			return nil, err
		}
	}
	s, err := NewRippleFamilySeed(seed)
	if err != nil {
		return nil, err
	}
	inc := big.NewInt(0).SetBytes(seed)
	inc.Lsh(inc, 32)
	f := func(priv *big.Int) bool { return priv.Cmp(order) >= 0 }
	key := newKey(order, inc, f)
	key.priv.X, key.priv.Y = key.priv.ScalarBaseMult(key.PrivateBytes())
	return &RootDeterministicKey{
		baseKey: *key,
		Seed:    s,
	}, nil
}

func (r *RootDeterministicKey) GenerateAccountId(sequence int32) (Hash, error) {
	inc := big.NewInt(0).SetBytes(r.PublicCompressed())
	inc.Lsh(inc, 32).Add(inc, big.NewInt(int64(sequence))).Lsh(inc, 32).Add(inc, zero)
	f := func(priv *big.Int) bool { return priv.Cmp(order) >= 0 }
	key := newKey(order, inc, f)
	key.priv.X, key.priv.Y = key.priv.ScalarBaseMult(key.PrivateBytes())
	key.priv.X, key.priv.Y = key.priv.Add(key.priv.X, key.priv.Y, r.priv.X, r.priv.Y)
	b := Sha256RipeMD160(key.PublicCompressed())
	return NewRippleAccount(b)
}

func (r *RootDeterministicKey) GenerateAccountKey(sequence int32) (*AccountKey, error) {
	generator := big.NewInt(0).SetBytes(r.PublicCompressed())
	inc := big.NewInt(0).Set(generator)
	inc.Lsh(inc, 32).Add(inc, big.NewInt(int64(sequence))).Lsh(inc, 32).Add(inc, zero)
	f := func(priv *big.Int) bool { return priv.Cmp(generator) >= 0 || priv.Cmp(zero) <= 0 }
	key := newKey(zero, inc, f)
	key.priv.D.Add(key.priv.D, r.priv.D).Mod(key.priv.D, order)
	key.priv.X, key.priv.Y = key.priv.Curve.ScalarBaseMult(key.PrivateBytes())
	return &AccountKey{baseKey: *key}, nil
}

func (r *RootDeterministicKey) PublicGenerator() (Hash, error) {
	return NewRippleFamilyGenerator(r.PublicCompressed())
}

func (r *RootDeterministicKey) PublicNodeKey() (Hash, error) {
	return NewRipplePublicNode(r.PublicCompressed())
}

func (r *RootDeterministicKey) PrivateNodeKey() (Hash, error) {
	return NewRipplePrivateNode(r.PrivateBytes())
}

func (a *AccountKey) PublicAccountKey() (Hash, error) {
	return NewRipplePublicAccount(a.PublicCompressed())
}

func (a *AccountKey) PrivateAccountKey() (Hash, error) {
	return NewRipplePrivateAccount(a.PrivateBytes())
}
<|endoftext|>


<commit_before>/*
 * Groovy music player daemon.
 *
 * Copyright (c) 2014, Alessandro Ghedini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package player

// #cgo pkg-config: mpv
// #include <mpv/client.h>
import "C"

import "fmt"
import "log"
import "strconv"
import "sync"

import "github.com/vaughan0/go-ini"

import "library"
import "notify"
import "util"

type Event byte;
type Status byte;

const (
	StatusStarting Status = iota;
	StatusPlaying;
	StatusPaused;
	StatusStopped;
);

func (s Status) String() string {
	switch s {
		case StatusStarting:
			return "start";

		case StatusPlaying:
			return "play";

		case StatusPaused:
			return "pause";

		case StatusStopped:
			return "stop";
	}

	return "invalid";
}

type Player struct {
	handle *C.mpv_handle;
	Status Status;

	library string;
	notify  bool;

	HandleStatusChange func();
	HandleTrackChange func();

	Wait sync.WaitGroup;
}

func (p *Player) ChangeStatus(status Status) {
	p.Status = status;
	p.HandleStatusChange();
}

func (p *Player) Play() error {
	switch p.Status {
		case StatusStarting, StatusPlaying:
			return nil;

		case StatusStopped:
			count, err := p.GetProperty("playlist-count");
			if err == nil && count.(int64) > 0 {
				return p.GotoTrack(0);
			}

			return p.AddTrack("", true);

		case StatusPaused:
			return p.SetProperty("pause", false);
	}

	return fmt.Errorf("Invalid player state");
}

func (p *Player) Pause() error {
	switch p.Status {
		case StatusStarting, StatusPaused, StatusStopped:
			return nil;

		case StatusPlaying:
			return p.SetProperty("pause", true);
	}

	return fmt.Errorf("Invalid player state");
}

func (p *Player) Toggle() error {
	switch p.Status {
		case StatusStarting:
			return nil;

		case StatusPaused, StatusStopped:
			return p.Play();

		case StatusPlaying:
			return p.Pause();
	}

	return fmt.Errorf("Invalid player state");
}

func (p *Player) Next() error {
	return p.Command([]string{"playlist_next", "force"})
}

func (p *Player) Prev() error {
	return p.Command([]string{"playlist_prev", "weak"})
}

func (p *Player) Stop() error {
	err := p.Command([]string{"stop"})

	p.HandleTrackChange();

	return err;
}

func (p *Player) Seek(seconds int64) error {
	secs := strconv.FormatInt(seconds, 10);
	return p.Command([]string{"seek", secs});
}

func (p *Player) List() ([]string, int64, int64, error) {
	count, err := p.GetProperty("playlist-count");
	if err != nil {
		return nil, 0, -1, nil;
	}

	pos, err := p.GetProperty("playlist-pos");
	if err != nil {
		return nil, 0, -1, nil;
	}

	playlist, err := p.GetProperty("playlist");
	if err != nil {
		return nil, 0, -1, nil;
	}

	var files []string;

	for _, entry := range playlist.([]interface{}) {
		if entry == nil {
			continue;
		}

		entry_map := entry.(map[string]interface{});
		files = append(files, entry_map["filename"].(string));
	}

	return files, count.(int64), pos.(int64), nil;
}

func (p *Player) AddTrack(path string, play bool) error {
	var mode string;

	if play {
		mode = "append-play";
	} else {
		mode = "append";
	}

	if path == "" {
		var err error;
		path, err = library.Random(p.library);
		if err != nil {
			return fmt.Errorf("Could not get random track: %s",err);
		}
	}

	return p.Command([]string{"loadfile", path, mode});
}

func (p *Player) AddList(path string) error {
	return p.Command([]string{"loadlist", path, "append"});
}

func (p *Player) GotoTrack(index int64) error {
	return p.SetProperty("playlist-pos", index);
}

func (p *Player) RemoveTrack(index int64) error {
	var track string;

	if index < 0 {
		track = "current";
	} else {
		track = strconv.FormatInt(index, 10);
	}

	return p.Command([]string{"playlist_remove", track});
}

func (p *Player) Quit() error {
	return p.Command([]string{"quit"});
}

func (p *Player) GetTrackMetadata() (map[string]string, error) {
	metadata, err := p.GetProperty("metadata");

	metadata_str := map[string]string{};

	for key, val := range metadata.(map[string]interface{}) {
		metadata_str[key] = val.(string);
	}

	return metadata_str, err;
}

func (p *Player) GetTrackLength() (float64, error) {
	length, err := p.GetProperty("length");
	if err != nil {
		return 0.0, nil;
	}

	return length.(float64), nil;
}

func (p *Player) GetTrackPath() (string, error) {
	path, err := p.GetProperty("path");
	return path.(string), err;
}

func (p *Player) GetTrackPosition(percent bool) (float64, error) {
	var err error;
	var pos interface{};

	if !percent {
		pos, err = p.GetProperty("time-pos");
	} else {
		pos, err = p.GetProperty("percent-pos");
	}

	if err != nil {
		return 0.0, err;
	}

	return pos.(float64), nil;
}

func (p *Player) GetTrackTitle() (string, error) {
	title, err := p.GetProperty("media-title");
	if err != nil {
		return "", nil;
	}

	metadata, err := p.GetTrackMetadata();
	if err != nil {
		return title.(string), nil;
	}

	artist := metadata["artist"];
	if artist != "" {
		return fmt.Sprintf("%s - %s", artist, title.(string)), nil;
	}

	return title.(string), nil;
}

func (p *Player) SetLoopStatus(mode string) error {
	switch mode {
		case "none":
			p.SetProperty("loop-file", false);
			p.SetProperty("loop", "no");

		case "track":
			p.SetLoopStatus("none");
			p.SetProperty("loop-file", true);

		case "list":
			p.SetLoopStatus("none");
			p.SetProperty("loop", "inf");

		default:
			return fmt.Errorf("Invalid mode");
	}

	return nil;
}

func Run(cfg ini.File) (*Player, error) {
	p := new(Player);

	p.Status = StatusStarting;

	p.handle = C.mpv_create();
	if p.handle == nil {
		return nil, fmt.Errorf("Could not create player");
	}

	err := p.SetOptionString("no-config", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-config': %s", err);
	}

	err = p.SetOptionString("no-video", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-video': %s", err);
	}

	err = p.SetOptionString("no-sub", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-sub': %s", err);
	}

	err = p.SetOptionString("no-softvol", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-softvol': %s", err);
	}

	if cfg["default"]["cache"] != "" {
		p.SetOptionString("cache", cfg["default"]["cache"]);
	}

	if cfg["default"]["gapless"] != "" {
		p.SetOptionString("gapless-audio", cfg["default"]["gapless"]);
	}

	p.library, _ = util.ExpandUser(cfg["default"]["library"]);

	if cfg["default"]["notify"] == "yes" {
		p.notify = true;
	} else {
		p.notify = false;
	}

	if cfg["default"]["replaygain"] != "" {
		rgain_af := fmt.Sprintf("volume=replaygain-%s", cfg["default"]["replaygain"]);
		if cfg["default"]["filters"] != "" {
			cfg["default"]["filters"] += "," + rgain_af;
		} else {
			cfg["default"]["filters"] = rgain_af;
		}
	}

	if cfg["default"]["output"] != "" {
		p.SetOptionString("ao", cfg["default"]["output"]);
	}

	if cfg["default"]["filters"] != "" {
		p.SetOptionString("af", cfg["default"]["filters"]);
	}

	if cfg["default"]["scripts"] != "" {
		p.SetOptionString("lua", cfg["default"]["scripts"]);
	}

	C.mpv_request_log_messages(p.handle, C.CString("warn"));

	mp_err := C.mpv_initialize(p.handle);
	if mp_err != 0 {
		return nil, ErrorString(mp_err);
	}

	p.Wait.Add(1);

	go p.EventLoop();

	return p, nil;
}

func (p *Player) EventLoop() {
	for {
		ev := C.mpv_wait_event(p.handle, -1);
		ev_name := C.GoString(C.mpv_event_name(ev.event_id));

		switch ev_name {
			case "idle":
				if p.Status == StatusStarting {
					p.Status = StatusStopped;
				}

				if p.Status == StatusStopped {
					break;
				}

				err := p.AddTrack("", true);
				if err != nil {
					log.Println(
						"Could not add track: %s", err,
					);

					p.ChangeStatus(StatusStopped);
				}

			case "pause":
				p.ChangeStatus(StatusPaused);

			case "playback-restart", "unpause":
				p.ChangeStatus(StatusPlaying);

			case "metadata-update":
				if p.notify {
					msg, _ := p.GetTrackTitle();
					notify.Notify("Now Playing:", msg,
					              "media-playback-start");
				}

				p.HandleTrackChange();

			case "log-message":
				mp_log := (*C.mpv_event_log_message)(ev.data);
				log.Printf("%s: %s: %s",
				           C.GoString(mp_log.level),
				           C.GoString(mp_log.prefix),
				           C.GoString(mp_log.text));

			case "shutdown":
				p.Wait.Done();
				return;
		}
	}
}
<commit_msg>player: check error when retrieving track properties<commit_after>/*
 * Groovy music player daemon.
 *
 * Copyright (c) 2014, Alessandro Ghedini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package player

// #cgo pkg-config: mpv
// #include <mpv/client.h>
import "C"

import "fmt"
import "log"
import "strconv"
import "sync"

import "github.com/vaughan0/go-ini"

import "library"
import "notify"
import "util"

type Event byte;
type Status byte;

const (
	StatusStarting Status = iota;
	StatusPlaying;
	StatusPaused;
	StatusStopped;
);

func (s Status) String() string {
	switch s {
		case StatusStarting:
			return "start";

		case StatusPlaying:
			return "play";

		case StatusPaused:
			return "pause";

		case StatusStopped:
			return "stop";
	}

	return "invalid";
}

type Player struct {
	handle *C.mpv_handle;
	Status Status;

	library string;
	notify  bool;

	HandleStatusChange func();
	HandleTrackChange func();

	Wait sync.WaitGroup;
}

func (p *Player) ChangeStatus(status Status) {
	p.Status = status;
	p.HandleStatusChange();
}

func (p *Player) Play() error {
	switch p.Status {
		case StatusStarting, StatusPlaying:
			return nil;

		case StatusStopped:
			count, err := p.GetProperty("playlist-count");
			if err == nil && count.(int64) > 0 {
				return p.GotoTrack(0);
			}

			return p.AddTrack("", true);

		case StatusPaused:
			return p.SetProperty("pause", false);
	}

	return fmt.Errorf("Invalid player state");
}

func (p *Player) Pause() error {
	switch p.Status {
		case StatusStarting, StatusPaused, StatusStopped:
			return nil;

		case StatusPlaying:
			return p.SetProperty("pause", true);
	}

	return fmt.Errorf("Invalid player state");
}

func (p *Player) Toggle() error {
	switch p.Status {
		case StatusStarting:
			return nil;

		case StatusPaused, StatusStopped:
			return p.Play();

		case StatusPlaying:
			return p.Pause();
	}

	return fmt.Errorf("Invalid player state");
}

func (p *Player) Next() error {
	return p.Command([]string{"playlist_next", "force"})
}

func (p *Player) Prev() error {
	return p.Command([]string{"playlist_prev", "weak"})
}

func (p *Player) Stop() error {
	err := p.Command([]string{"stop"})

	p.HandleTrackChange();

	return err;
}

func (p *Player) Seek(seconds int64) error {
	secs := strconv.FormatInt(seconds, 10);
	return p.Command([]string{"seek", secs});
}

func (p *Player) List() ([]string, int64, int64, error) {
	count, err := p.GetProperty("playlist-count");
	if err != nil {
		return nil, 0, -1, nil;
	}

	pos, err := p.GetProperty("playlist-pos");
	if err != nil {
		return nil, 0, -1, nil;
	}

	playlist, err := p.GetProperty("playlist");
	if err != nil {
		return nil, 0, -1, nil;
	}

	var files []string;

	for _, entry := range playlist.([]interface{}) {
		if entry == nil {
			continue;
		}

		entry_map := entry.(map[string]interface{});
		files = append(files, entry_map["filename"].(string));
	}

	return files, count.(int64), pos.(int64), nil;
}

func (p *Player) AddTrack(path string, play bool) error {
	var mode string;

	if play {
		mode = "append-play";
	} else {
		mode = "append";
	}

	if path == "" {
		var err error;
		path, err = library.Random(p.library);
		if err != nil {
			return fmt.Errorf("Could not get random track: %s",err);
		}
	}

	return p.Command([]string{"loadfile", path, mode});
}

func (p *Player) AddList(path string) error {
	return p.Command([]string{"loadlist", path, "append"});
}

func (p *Player) GotoTrack(index int64) error {
	return p.SetProperty("playlist-pos", index);
}

func (p *Player) RemoveTrack(index int64) error {
	var track string;

	if index < 0 {
		track = "current";
	} else {
		track = strconv.FormatInt(index, 10);
	}

	return p.Command([]string{"playlist_remove", track});
}

func (p *Player) Quit() error {
	return p.Command([]string{"quit"});
}

func (p *Player) GetTrackMetadata() (map[string]string, error) {
	metadata, err := p.GetProperty("metadata");
	if err != nil {
		return nil, err;
	}

	metadata_str := map[string]string{};

	for key, val := range metadata.(map[string]interface{}) {
		metadata_str[key] = val.(string);
	}

	return metadata_str, err;
}

func (p *Player) GetTrackLength() (float64, error) {
	length, err := p.GetProperty("length");
	if err != nil {
		return 0.0, err;
	}

	return length.(float64), nil;
}

func (p *Player) GetTrackPath() (string, error) {
	path, err := p.GetProperty("path");
	if err != nil {
		return "", err;
	}

	return path.(string), nil;
}

func (p *Player) GetTrackPosition(percent bool) (float64, error) {
	var err error;
	var pos interface{};

	if !percent {
		pos, err = p.GetProperty("time-pos");
	} else {
		pos, err = p.GetProperty("percent-pos");
	}

	if err != nil {
		return 0.0, err;
	}

	return pos.(float64), nil;
}

func (p *Player) GetTrackTitle() (string, error) {
	title, err := p.GetProperty("media-title");
	if err != nil {
		return "", nil;
	}

	metadata, err := p.GetTrackMetadata();
	if err != nil {
		return title.(string), nil;
	}

	artist := metadata["artist"];
	if artist != "" {
		return fmt.Sprintf("%s - %s", artist, title.(string)), nil;
	}

	return title.(string), nil;
}

func (p *Player) SetLoopStatus(mode string) error {
	switch mode {
		case "none":
			p.SetProperty("loop-file", false);
			p.SetProperty("loop", "no");

		case "track":
			p.SetLoopStatus("none");
			p.SetProperty("loop-file", true);

		case "list":
			p.SetLoopStatus("none");
			p.SetProperty("loop", "inf");

		default:
			return fmt.Errorf("Invalid mode");
	}

	return nil;
}

func Run(cfg ini.File) (*Player, error) {
	p := new(Player);

	p.Status = StatusStarting;

	p.handle = C.mpv_create();
	if p.handle == nil {
		return nil, fmt.Errorf("Could not create player");
	}

	err := p.SetOptionString("no-config", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-config': %s", err);
	}

	err = p.SetOptionString("no-video", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-video': %s", err);
	}

	err = p.SetOptionString("no-sub", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-sub': %s", err);
	}

	err = p.SetOptionString("no-softvol", "");
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-softvol': %s", err);
	}

	if cfg["default"]["cache"] != "" {
		p.SetOptionString("cache", cfg["default"]["cache"]);
	}

	if cfg["default"]["gapless"] != "" {
		p.SetOptionString("gapless-audio", cfg["default"]["gapless"]);
	}

	p.library, _ = util.ExpandUser(cfg["default"]["library"]);

	if cfg["default"]["notify"] == "yes" {
		p.notify = true;
	} else {
		p.notify = false;
	}

	if cfg["default"]["replaygain"] != "" {
		rgain_af := fmt.Sprintf("volume=replaygain-%s", cfg["default"]["replaygain"]);
		if cfg["default"]["filters"] != "" {
			cfg["default"]["filters"] += "," + rgain_af;
		} else {
			cfg["default"]["filters"] = rgain_af;
		}
	}

	if cfg["default"]["output"] != "" {
		p.SetOptionString("ao", cfg["default"]["output"]);
	}

	if cfg["default"]["filters"] != "" {
		p.SetOptionString("af", cfg["default"]["filters"]);
	}

	if cfg["default"]["scripts"] != "" {
		p.SetOptionString("lua", cfg["default"]["scripts"]);
	}

	C.mpv_request_log_messages(p.handle, C.CString("warn"));

	mp_err := C.mpv_initialize(p.handle);
	if mp_err != 0 {
		return nil, ErrorString(mp_err);
	}

	p.Wait.Add(1);

	go p.EventLoop();

	return p, nil;
}

func (p *Player) EventLoop() {
	for {
		ev := C.mpv_wait_event(p.handle, -1);
		ev_name := C.GoString(C.mpv_event_name(ev.event_id));

		switch ev_name {
			case "idle":
				if p.Status == StatusStarting {
					p.Status = StatusStopped;
				}

				if p.Status == StatusStopped {
					break;
				}

				err := p.AddTrack("", true);
				if err != nil {
					log.Println(
						"Could not add track: %s", err,
					);

					p.ChangeStatus(StatusStopped);
				}

			case "pause":
				p.ChangeStatus(StatusPaused);

			case "playback-restart", "unpause":
				p.ChangeStatus(StatusPlaying);

			case "metadata-update":
				if p.notify {
					msg, _ := p.GetTrackTitle();
					notify.Notify("Now Playing:", msg,
					              "media-playback-start");
				}

				p.HandleTrackChange();

			case "log-message":
				mp_log := (*C.mpv_event_log_message)(ev.data);
				log.Printf("%s: %s: %s",
				           C.GoString(mp_log.level),
				           C.GoString(mp_log.prefix),
				           C.GoString(mp_log.text));

			case "shutdown":
				p.Wait.Done();
				return;
		}
	}
}
<|endoftext|>