<commit_before>package nyb

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"sync"
	"time"

	"github.com/hako/durafmt"
	"github.com/ugjka/dumbirc"
)

//Settings for bot
type Settings struct {
	IrcNick    string
	IrcChans   []string
	IrcServer  string
	IrcTrigger string
	IrcUseTLS  bool
	Bot        *dumbirc.Connection
	LogChan    LogChan
	Stopper    chan bool
	Email      string
	Nominatim  string
	extra
}

type extra struct {
	zones           TZS
	last            TZ
	next            TZ
	remaining       int
	nominatimResult NominatimResults
	//We close this when we get WELCOME msg on join in irc
	start chan bool
	//This is used to prevent sending ping before we
	//have response from previous ping (any activity on irc)
	//pingpong(pp) sends a signal to ping timer
	pp chan bool
	sync.Once
	sync.WaitGroup
}

//New creates new bot
func New(nick string, chans []string, trigger string, server string,
	tls bool, email string, nominatim string) *Settings {
	return &Settings{
		nick,
		chans,
		server,
		trigger,
		tls,
		dumbirc.New(nick, "nyebot", server, tls),
		newLogChan(),
		make(chan bool),
		email,
		nominatim,
		extra{
			start: make(chan bool),
			pp:    make(chan bool, 1),
		},
	}
}

var stFinished = "That's it, Year %d is here AoE"

//Start starts the bot
func (s *Settings) Start() {
	log.SetOutput(s.LogChan)
	log.Println("Starting the bot...")
	defer s.Wait()
	//
	//Set up irc
	//
	bot := s.Bot
	s.addCallbacks()
	s.addTriggers()

	s.Add(1)
	go s.ircControl()

	bot.Start()
	select {
	case <-s.start:
		log.Println("Got start...")
	case <-s.Stopper:
		return
	}

	if err := s.decodeZones(Zones); err != nil {
		log.Println("Fatal error:", err)
		close(s.Stopper)
		return
	}
	for {
		s.loopTimeZones()
		select {
		case <-s.Stopper:
			return
		default:
		}
		bot.MsgBulk(s.IrcChans, fmt.Sprintf(stFinished, target.Year()))
		log.Println("All zones finished...")
		target = target.AddDate(1, 0, 0)
		log.Printf("Wrapping the target date around to %d\n", target.Year())
	}
}

func (s *Settings) decodeZones(z []byte) error {
	if err := json.Unmarshal(z, &s.zones); err != nil {
		return err
	}
	sort.Sort(sort.Reverse(s.zones))
	return nil
}

//Stop stops the bot
func (s *Settings) Stop() {
	select {
	case <-s.Stopper:
		return
	default:
		close(s.Stopper)
	}
}

var reconnectInterval = time.Second * 30
var pingInterval = time.Minute * 1

func (s *Settings) ircControl() {
	bot := s.Bot
	defer s.Done()
	for {
		timer := time.NewTimer(pingInterval)
		select {
		case err := <-bot.Errchan:
			log.Println("Error:", err)
			log.Printf("Reconnecting to irc in %s...\n", reconnectInterval)
			time.AfterFunc(reconnectInterval, func() {
				select {
				case <-s.Stopper:
					return
				default:
					bot.Start()
				}
			})
		case <-s.Stopper:
			timer.Stop()
			log.Println("Stopping the bot...")
			log.Println("Disconnecting...")
			bot.Disconnect()
			return
		case <-timer.C:
			timer.Stop()
			//pingpong stuff
			select {
			case <-s.pp:
				log.Println("Sending PING...")
				bot.Ping()
			default:
				log.Println("Got no PONG...")
			}
		}

	}
}

var stNextNewYear = "Next New Year in %s in %s"
var stHappyNewYear = "Happy New Year in %s"

func (s *Settings) loopTimeZones() {
	zones := s.zones
	bot := s.Bot
	for i := 0; i < len(zones); i++ {
		dur := time.Minute * time.Duration(zones[i].Offset*60)
		s.next = zones[i]
		if i == 0 {
			s.last = zones[len(zones)-1]
		} else {
			s.last = zones[i-1]
		}
		s.remaining = len(zones) - i
		if timeNow().UTC().Add(dur).Before(target) {
			time.Sleep(time.Second * 2)
			log.Println("Zone pending:", zones[i].Offset)
			humandur := durafmt.Parse(target.Sub(timeNow().UTC().Add(dur)))
			msg := fmt.Sprintf(stNextNewYear, removeMilliseconds(humandur), zones[i])
			bot.MsgBulk(s.IrcChans, msg)
			//Wait till Target in Timezone
			timer := NewTimer(target.Sub(timeNow().UTC().Add(dur)))
			select {
			case <-timer.C:
				timer.Stop()
				msg = fmt.Sprintf(stHappyNewYear, zones[i])
				bot.MsgBulk(s.IrcChans, msg)
				log.Println("Announcing zone:", zones[i].Offset)
			case <-s.Stopper:
				timer.Stop()
				return
			}
		}
	}
}
<commit_msg>prevent double close<commit_after>package nyb

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"sync"
	"time"

	"github.com/hako/durafmt"
	"github.com/ugjka/dumbirc"
)

//Settings for bot
type Settings struct {
	IrcNick    string
	IrcChans   []string
	IrcServer  string
	IrcTrigger string
	IrcUseTLS  bool
	Bot        *dumbirc.Connection
	LogChan    LogChan
	Stopper    chan bool
	Email      string
	Nominatim  string
	extra
}

type extra struct {
	zones           TZS
	last            TZ
	next            TZ
	remaining       int
	nominatimResult NominatimResults
	//We close this when we get WELCOME msg on join in irc
	start chan bool
	//This is used to prevent sending ping before we
	//have response from previous ping (any activity on irc)
	//pingpong(pp) sends a signal to ping timer
	pp chan bool
	sync.Once
	sync.WaitGroup
}

//New creates new bot
func New(nick string, chans []string, trigger string, server string,
	tls bool, email string, nominatim string) *Settings {
	return &Settings{
		nick,
		chans,
		server,
		trigger,
		tls,
		dumbirc.New(nick, "nyebot", server, tls),
		newLogChan(),
		make(chan bool),
		email,
		nominatim,
		extra{
			start: make(chan bool),
			pp:    make(chan bool, 1),
		},
	}
}

var stFinished = "That's it, Year %d is here AoE"

//Start starts the bot
func (s *Settings) Start() {
	log.SetOutput(s.LogChan)
	log.Println("Starting the bot...")
	defer s.Wait()
	//
	//Set up irc
	//
	bot := s.Bot
	s.addCallbacks()
	s.addTriggers()

	s.Add(1)
	go s.ircControl()

	bot.Start()
	select {
	case <-s.start:
		log.Println("Got start...")
	case <-s.Stopper:
		return
	}

	if err := s.decodeZones(Zones); err != nil {
		log.Println("Fatal error:", err)
		s.Stop()
		return
	}
	for {
		s.loopTimeZones()
		select {
		case <-s.Stopper:
			return
		default:
		}
		bot.MsgBulk(s.IrcChans, fmt.Sprintf(stFinished, target.Year()))
		log.Println("All zones finished...")
		target = target.AddDate(1, 0, 0)
		log.Printf("Wrapping the target date around to %d\n", target.Year())
	}
}

func (s *Settings) decodeZones(z []byte) error {
	if err := json.Unmarshal(z, &s.zones); err != nil {
		return err
	}
	sort.Sort(sort.Reverse(s.zones))
	return nil
}

//Stop stops the bot
func (s *Settings) Stop() {
	select {
	case <-s.Stopper:
		return
	default:
		close(s.Stopper)
	}
}

var reconnectInterval = time.Second * 30
var pingInterval = time.Minute * 1

func (s *Settings) ircControl() {
	bot := s.Bot
	defer s.Done()
	for {
		timer := time.NewTimer(pingInterval)
		select {
		case err := <-bot.Errchan:
			log.Println("Error:", err)
			log.Printf("Reconnecting to irc in %s...\n", reconnectInterval)
			time.AfterFunc(reconnectInterval, func() {
				select {
				case <-s.Stopper:
					return
				default:
					bot.Start()
				}
			})
		case <-s.Stopper:
			timer.Stop()
			log.Println("Stopping the bot...")
			log.Println("Disconnecting...")
			bot.Disconnect()
			return
		case <-timer.C:
			timer.Stop()
			//pingpong stuff
			select {
			case <-s.pp:
				log.Println("Sending PING...")
				bot.Ping()
			default:
				log.Println("Got no PONG...")
			}
		}

	}
}

var stNextNewYear = "Next New Year in %s in %s"
var stHappyNewYear = "Happy New Year in %s"

func (s *Settings) loopTimeZones() {
	zones := s.zones
	bot := s.Bot
	for i := 0; i < len(zones); i++ {
		dur := time.Minute * time.Duration(zones[i].Offset*60)
		s.next = zones[i]
		if i == 0 {
			s.last = zones[len(zones)-1]
		} else {
			s.last = zones[i-1]
		}
		s.remaining = len(zones) - i
		if timeNow().UTC().Add(dur).Before(target) {
			time.Sleep(time.Second * 2)
			log.Println("Zone pending:", zones[i].Offset)
			humandur := durafmt.Parse(target.Sub(timeNow().UTC().Add(dur)))
			msg := fmt.Sprintf(stNextNewYear, removeMilliseconds(humandur), zones[i])
			bot.MsgBulk(s.IrcChans, msg)
			//Wait till Target in Timezone
			timer := NewTimer(target.Sub(timeNow().UTC().Add(dur)))
			select {
			case <-timer.C:
				timer.Stop()
				msg = fmt.Sprintf(stHappyNewYear, zones[i])
				bot.MsgBulk(s.IrcChans, msg)
				log.Println("Announcing zone:", zones[i].Offset)
			case <-s.Stopper:
				timer.Stop()
				return
			}
		}
	}
}
<|endoftext|>


<commit_before>// Octopus! Sounds good :)

package main

<commit_msg>Delete octopus.go<commit_after><|endoftext|>


<commit_before>package assert

// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

import (
	"reflect"
	"time"
)

// Operand represent a value
type Operand struct {
	// this reference is used to report a test failure
	a testingA
	// description of the value, typically a variable or field name
	label string
	// actual value of any type
	value interface{}
	// used to operate on two values
	operator RelationalOperator
}

// With returns a copy Operand that will use the RelationalOperator.
func (o Operand) With(r RelationalOperator) Operand {
	return Operand{o.a, o.label, o.value, r}
}

// Equals checks whether the value we have got is equal to the value we want.
func (o Operand) Equals(want interface{}) {
	if !o.operator.Apply(o.value, want) {
		if reflect.DeepEqual(o.value, want) {
			return
		}
		logCall(o.a.t, "Equals")
		Fatalf(o.a.t, "\ngot [%v] (%T) for \"%s\" but want [%v] (%T)",
			o.value, o.value,
			o.label,
			want, want)
	} else {
		Logf(o.a.t, "%s = %v", o.label, o.value)
	}
}

// Before checks whether the value we have got is before a moment.
func (o Operand) Before(moment time.Time) {
	left, ok := o.value.(time.Time)
	if !ok {
		logCall(o.a.t, "Before")
		Fatalf(o.a.t, "got [%v](%T) for \"%s\" but want a Time", o.value, o.value, o.label)
	}
	if left.Before(moment) {
		Logf(o.a.t, "%s is before %v", o.label, moment)
	} else {
		logCall(o.a.t, "Before")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want it before [%v]", o.value, o.label, moment)
	}
}

// After checks whether the value we have got is after a moment.
func (o Operand) After(moment time.Time) {
	left, ok := o.value.(time.Time)
	if !ok {
		logCall(o.a.t, "After")
		Fatalf(o.a.t, "got [%v](%T) for \"%s\" but want a Time", o.value, o.value, o.label)
	}
	if left.After(moment) {
		Logf(o.a.t, "%s is after %v", o.label, moment)
	} else {
		logCall(o.a.t, "After")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want it after [%v]", o.value, o.label, moment)
	}
}

// IsKindOf checks whether the values are of the same type
func (o Operand) IsKindOf(v interface{}) {
	leftType := reflect.TypeOf(o.value)
	rightType := reflect.TypeOf(v)
	if leftType != rightType {
		logCall(o.a.t, "IsKindOf")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [%v]", leftType, o.label, rightType)
	} else {
		Logf(o.a.t, "%s is kind of %v", o.label, rightType)
	}
}

// IsNil checks whether the value is nil
func (o Operand) IsNil() {
	if o.operator.Apply(o.value, nil) {
		return
	} else {
		// from github.com/go-check/check
		switch v := reflect.ValueOf(o.value); v.Kind() {
		case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
			if v.IsNil() {
				return
			}
		}
	}
	logCall(o.a.t, "IsNil")
	Fatalf(o.a.t, "got [%v] for \"%s\" but want [nil]", o.value, o.label)
}

// IsNotNil checks whether the value is nil
func (o Operand) IsNotNil() {
	if o.operator.Apply(o.value, nil) {
		logCall(o.a.t, "IsNotNil")
		Fatalf(o.a.t, "got unexpected [%v] for \"%s\"", o.value, o.label)
	}
}

// IsTrue checks whether the value is true
func (o Operand) IsTrue() {
	if o.operator.Apply(o.value, false) { // i.e fail if !true
		logCall(o.a.t, "IsTrue")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [true]", o.value, o.label)
	} else {
		Logf(o.a.t, "%s = %v", o.label, o.value)
	}
}

// IsFalse checks whether the value is false
func (o Operand) IsFalse() {
	if o.operator.Apply(o.value, true) { // i.e fail if !false
		logCall(o.a.t, "IsFalse")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [false]", o.value, o.label)
	} else {
		Logf(o.a.t, "%s != %v", o.label, o.value)
	}
}

// Not creates a new Operand with a negated version of its comparator.
func (o Operand) Not() Operand {
	return Operand{o.a, o.label, o.value, not{o.operator}}
}

// Len checks that len(value) or value.Len() is equals to the given length.
// It operates on Array, Chan, Map, Slice, or String and objects that implement Len() int.
func (o Operand) Len(want int) {
	// panic catcher
	defer func() {
		if err := recover(); err != nil {
			// try calling Len
			rt := reflect.TypeOf(o.value)
			rf, ok := rt.MethodByName("Len")
			if !ok {
				logCall(o.a.t, "Len")
				Fatalf(o.a.t, "got [%v] for \"%s\" but it does not implement Len() int", o.value, o.label)
				return
			}
			rv := reflect.ValueOf(o.value)
			gotvs := rf.Func.Call([]reflect.Value{rv})
			got := int(gotvs[0].Int())
			if !o.operator.Apply(got, want) {
				logCall(o.a.t, "Len")
				Fatalf(o.a.t, "got [%v] for \"%s\" but want [%d]", got, o.label, want)
			}
		}
	}()
	rv := reflect.ValueOf(o.value)
	got := rv.Len()
	if !o.operator.Apply(got, want) {
		logCall(o.a.t, "Len")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [%d]", got, o.label, want)
	}
}

// IsEmpty checks that len(value) or value.Len() is zero.
// It operates on Array, Chan, Map, Slice, or String and objects that implement Len() int.
func (o Operand) IsEmpty() {
	// panic catcher
	defer func() {
		if err := recover(); err != nil {
			// try calling Len
			rt := reflect.TypeOf(o.value)
			rf, ok := rt.MethodByName("Len")
			if !ok {
				logCall(o.a.t, "IsEmpty")
				Fatalf(o.a.t, "got [%v] for \"%s\" but it does not implement Len() int", o.value, o.label)
				return
			}
			rv := reflect.ValueOf(o.value)
			gotvs := rf.Func.Call([]reflect.Value{rv})
			got := int(gotvs[0].Int())
			if !o.operator.Apply(got, 0) {
				logCall(o.a.t, "IsEmpty")
				Fatalf(o.a.t, "got [%v] for len(\"%s\") but want > 0", got, o.label)
			}
		}
	}()
	rv := reflect.ValueOf(o.value)
	got := rv.Len()
	if !o.operator.Apply(got, 0) {
		logCall(o.a.t, "IsEmpty")
		Fatalf(o.a.t, "got [%v] for len(\"%s\") but want > 0", got, o.label)
	}
}
<commit_msg>added logging for IsNil/IsNotNil and changed IsTrue/IsFalse<commit_after>package assert

// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

import (
	"reflect"
	"time"
)

// Operand represent a value
type Operand struct {
	// this reference is used to report a test failure
	a testingA
	// description of the value, typically a variable or field name
	label string
	// actual value of any type
	value interface{}
	// used to operate on two values
	operator RelationalOperator
}

// With returns a copy Operand that will use the RelationalOperator.
func (o Operand) With(r RelationalOperator) Operand {
	return Operand{o.a, o.label, o.value, r}
}

// Equals checks whether the value we have got is equal to the value we want.
func (o Operand) Equals(want interface{}) {
	if !o.operator.Apply(o.value, want) {
		if reflect.DeepEqual(o.value, want) {
			return
		}
		logCall(o.a.t, "Equals")
		Fatalf(o.a.t, "\ngot [%v] (%T) for \"%s\" but want [%v] (%T)",
			o.value, o.value,
			o.label,
			want, want)
	} else {
		Logf(o.a.t, "%s = %v", o.label, o.value)
	}
}

// Before checks whether the value we have got is before a moment.
func (o Operand) Before(moment time.Time) {
	left, ok := o.value.(time.Time)
	if !ok {
		logCall(o.a.t, "Before")
		Fatalf(o.a.t, "got [%v](%T) for \"%s\" but want a Time", o.value, o.value, o.label)
	}
	if left.Before(moment) {
		Logf(o.a.t, "%s is before %v", o.label, moment)
	} else {
		logCall(o.a.t, "Before")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want it before [%v]", o.value, o.label, moment)
	}
}

// After checks whether the value we have got is after a moment.
func (o Operand) After(moment time.Time) {
	left, ok := o.value.(time.Time)
	if !ok {
		logCall(o.a.t, "After")
		Fatalf(o.a.t, "got [%v](%T) for \"%s\" but want a Time", o.value, o.value, o.label)
	}
	if left.After(moment) {
		Logf(o.a.t, "%s is after %v", o.label, moment)
	} else {
		logCall(o.a.t, "After")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want it after [%v]", o.value, o.label, moment)
	}
}

// IsKindOf checks whether the values are of the same type
func (o Operand) IsKindOf(v interface{}) {
	leftType := reflect.TypeOf(o.value)
	rightType := reflect.TypeOf(v)
	if leftType != rightType {
		logCall(o.a.t, "IsKindOf")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [%v]", leftType, o.label, rightType)
	} else {
		Logf(o.a.t, "%s is kind of %v", o.label, rightType)
	}
}

// IsNil checks whether the value is nil
func (o Operand) IsNil() {
	if o.operator.Apply(o.value, nil) {
		Logf(o.a.t, "%s is nil", o.label)
		return
	} else {
		// from github.com/go-check/check
		switch v := reflect.ValueOf(o.value); v.Kind() {
		case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
			if v.IsNil() {
				return
			}
		}
	}
	logCall(o.a.t, "IsNil")
	Fatalf(o.a.t, "got [%v] for \"%s\" but want [nil]", o.value, o.label)
}

// IsNotNil checks whether the value is nil
func (o Operand) IsNotNil() {
	if o.operator.Apply(o.value, nil) {
		logCall(o.a.t, "IsNotNil")
		Fatalf(o.a.t, "got unexpected [%v] for \"%s\"", o.value, o.label)
	} else {
		Logf(o.a.t, "%s is not nil", o.value)
	}
}

// IsTrue checks whether the value is true
func (o Operand) IsTrue() {
	if o.operator.Apply(o.value, false) { // i.e fail if !true
		logCall(o.a.t, "IsTrue")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [true]", o.value, o.label)
	} else {
		Logf(o.a.t, "%s is true", o.label)
	}
}

// IsFalse checks whether the value is false
func (o Operand) IsFalse() {
	if o.operator.Apply(o.value, true) { // i.e fail if !false
		logCall(o.a.t, "IsFalse")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [false]", o.value, o.label)
	} else {
		Logf(o.a.t, "%s is false", o.label)
	}
}

// Not creates a new Operand with a negated version of its comparator.
func (o Operand) Not() Operand {
	return Operand{o.a, o.label, o.value, not{o.operator}}
}

// Len checks that len(value) or value.Len() is equals to the given length.
// It operates on Array, Chan, Map, Slice, or String and objects that implement Len() int.
func (o Operand) Len(want int) {
	// panic catcher
	defer func() {
		if err := recover(); err != nil {
			// try calling Len
			rt := reflect.TypeOf(o.value)
			rf, ok := rt.MethodByName("Len")
			if !ok {
				logCall(o.a.t, "Len")
				Fatalf(o.a.t, "got [%v] for \"%s\" but it does not implement Len() int", o.value, o.label)
				return
			}
			rv := reflect.ValueOf(o.value)
			gotvs := rf.Func.Call([]reflect.Value{rv})
			got := int(gotvs[0].Int())
			if !o.operator.Apply(got, want) {
				logCall(o.a.t, "Len")
				Fatalf(o.a.t, "got [%v] for \"%s\" but want [%d]", got, o.label, want)
			}
		}
	}()
	rv := reflect.ValueOf(o.value)
	got := rv.Len()
	if !o.operator.Apply(got, want) {
		logCall(o.a.t, "Len")
		Fatalf(o.a.t, "got [%v] for \"%s\" but want [%d]", got, o.label, want)
	}
}

// IsEmpty checks that len(value) or value.Len() is zero.
// It operates on Array, Chan, Map, Slice, or String and objects that implement Len() int.
func (o Operand) IsEmpty() {
	// panic catcher
	defer func() {
		if err := recover(); err != nil {
			// try calling Len
			rt := reflect.TypeOf(o.value)
			rf, ok := rt.MethodByName("Len")
			if !ok {
				logCall(o.a.t, "IsEmpty")
				Fatalf(o.a.t, "got [%v] for \"%s\" but it does not implement Len() int", o.value, o.label)
				return
			}
			rv := reflect.ValueOf(o.value)
			gotvs := rf.Func.Call([]reflect.Value{rv})
			got := int(gotvs[0].Int())
			if !o.operator.Apply(got, 0) {
				logCall(o.a.t, "IsEmpty")
				Fatalf(o.a.t, "got [%v] for len(\"%s\") but want > 0", got, o.label)
			}
		}
	}()
	rv := reflect.ValueOf(o.value)
	got := rv.Len()
	if !o.operator.Apply(got, 0) {
		logCall(o.a.t, "IsEmpty")
		Fatalf(o.a.t, "got [%v] for len(\"%s\") but want > 0", got, o.label)
	}
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package gobuild

import (
	"errors"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// MinWatcherFrequency 监视器更新频率的最小值
const MinWatcherFrequency = 1 * time.Second

// Options 热编译的选项
type Options struct {
	// 为 go build 最后的文件参数，可以为空，表示当前目录。
	MainFiles string

	// 指定可执行文件输出的文件路径，为空表示默认值，
	// 若不带路径信息，会附加在 Dirs 的第一个路径上；
	//
	// windows 系统无须指定 .exe 扩展名，会自行添加。
	//
	// 如果带路径信息，则会使用该文件所在目录作为工作目录。
	OutputName string
	appName    string

	// 传递各个工具的参数，大致有以下几个，具体可参考 go build 的 xxflags 系列参数。
	//  - asm   --> asmflags
	//  - gccgo --> gccgoflags
	//  - gc    --> gcflags
	//  - ld    --> ldflags
	Flags map[string]string

	// 指定监视的文件扩展名，为空表示不监视任何文件，* 表示监视所有文件
	Exts string
	exts []string

	// 传递给编译成功后的程序的参数
	AppArgs string
	appArgs []string

	// 是否监视子目录
	Recursive bool

	// 表示需要监视的目录，至少指定一个目录，
	// 第一个目录被当作主目录，将编译其下的文件作为执行主体。
	//
	// 如果 OutputName 中未指定目录的话，第一个目录会被当作工作目录使用。
	Dirs  []string
	paths []string

	// 监视器的更新频率，只有文件更新的时长超过此值，才会被定义为更新。
	// 防止文件频繁修改导致的频繁编译调用。
	//
	// 此值不能小于 MinWatcherFrequency。
	//
	// 默认值为 MinWatcherFrequency 表示的值。
	WatcherFrequency time.Duration

	// 传递给 go 命令的参数
	goCmdArgs []string
}

func (opt *Options) sanitize() error {
	if len(opt.Dirs) < 1 {
		return errors.New("参数 dir 至少指定一个")
	}
	wd, err := filepath.Abs(opt.Dirs[0])
	if err != nil {
		return err
	}
	opt.Dirs[0] = wd

	if opt.appName, err = getAppName(opt.OutputName, wd); err != nil {
		return err
	}

	opt.exts = getExts(opt.Exts)

	opt.appArgs = splitArgs(opt.AppArgs)

	if opt.paths, err = recursivePaths(opt.Recursive, opt.Dirs); err != nil {
		return err
	}

	if opt.WatcherFrequency == 0 {
		opt.WatcherFrequency = MinWatcherFrequency
	} else if opt.WatcherFrequency < MinWatcherFrequency {
		return errors.New("watcherFrequency 值过小")
	}

	// 初始化 goCmd 的参数
	args := []string{"build", "-o", opt.appName}
	for k, v := range opt.Flags {
		args = append(args, "-"+k+"flags", v)
	}
	args = append(args, "-v")
	if len(opt.MainFiles) > 0 {
		args = append(args, opt.MainFiles)
	}
	opt.goCmdArgs = args

	return nil
}

// 将 extString 分解成数组，并清理掉无用的内容，比如空字符串
func getExts(extString string) []string {
	exts := strings.Split(extString, ",")
	ret := make([]string, 0, len(exts))

	for _, ext := range exts {
		ext = strings.TrimSpace(ext)

		if len(ext) == 0 {
			continue
		}
		if ext[0] != '.' {
			ext = "." + ext
		}
		ret = append(ret, ext)
	}

	return ret
}

func getAppName(outputName, wd string) (string, error) {
	if outputName == "" {
		outputName = filepath.Base(wd)
	}

	goexe := os.Getenv("GOEXE")
	if goexe != "" && !strings.HasSuffix(outputName, goexe) {
		outputName += goexe
	}

	// 没有分隔符，表示仅有一个文件名，需要加上 wd
	if strings.IndexByte(outputName, '/') < 0 || strings.IndexByte(outputName, filepath.Separator) < 0 {
		outputName = filepath.Join(wd, outputName)
	}

	// 转成绝对路径
	outputName, err := filepath.Abs(outputName)
	if err != nil {
		return "", err
	}

	return outputName, nil
}

// 根据 recursive 值确定是否递归查找 paths 每个目录下的子目录。
func recursivePaths(recursive bool, paths []string) ([]string, error) {
	if !recursive {
		return paths, nil
	}

	ret := []string{}

	walk := func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if fi.IsDir() && strings.Index(path, "/.") < 0 {
			ret = append(ret, path)
		}
		return nil
	}

	for _, path := range paths {
		if err := filepath.Walk(path, walk); err != nil {
			return nil, err
		}
	}

	return ret, nil
}

func splitArgs(args string) []string {
	ret := make([]string, 0, 10)
	var state byte
	var start, index int

	for index = 0; index < len(args); index++ {
		b := args[index]
		switch b {
		case ' ':
			if state == '"' {
				break
			}

			if state != ' ' {
				ret = appendArg(ret, args[start:index])
				state = ' '
			}
			start = index + 1
		case '=':
			if state == '"' {
				break
			}

			if state != '=' {
				ret = appendArg(ret, args[start:index])
				state = '='
			}
			start = index + 1
			state = 0
		case '"':
			if state == '"' {
				ret = appendArg(ret, args[start:index])
				state = 0
				start = index + 1
				break
			}

			if start != index {
				ret = appendArg(ret, args[start:index])
			}
			state = '"'
			start = index + 1
		default:
			if state == ' ' {
				state = 0
				start = index
			}
		}
	} // end for

	if start < len(args) {
		ret = appendArg(ret, args[start:])
	}

	return ret
}

func appendArg(args []string, arg string) []string {
	arg = strings.TrimSpace(arg)
	if arg == "" {
		return args
	}

	return append(args, arg)
}
<commit_msg>fix: 修正 windows 下对目录的判断错误<commit_after>// SPDX-License-Identifier: MIT

package gobuild

import (
	"errors"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// MinWatcherFrequency 监视器更新频率的最小值
const MinWatcherFrequency = 1 * time.Second

// Options 热编译的选项
type Options struct {
	// 为 go build 最后的文件参数，可以为空，表示当前目录。
	MainFiles string

	// 指定可执行文件输出的文件路径，为空表示默认值，
	// 若不带路径信息，会附加在 Dirs 的第一个路径上；
	//
	// windows 系统无须指定 .exe 扩展名，会自行添加。
	//
	// 如果带路径信息，则会使用该文件所在目录作为工作目录。
	OutputName string
	appName    string

	// 传递各个工具的参数，大致有以下几个，具体可参考 go build 的 xxflags 系列参数。
	//  - asm   --> asmflags
	//  - gccgo --> gccgoflags
	//  - gc    --> gcflags
	//  - ld    --> ldflags
	Flags map[string]string

	// 指定监视的文件扩展名，为空表示不监视任何文件，* 表示监视所有文件
	Exts string
	exts []string

	// 传递给编译成功后的程序的参数
	AppArgs string
	appArgs []string

	// 是否监视子目录
	Recursive bool

	// 表示需要监视的目录，至少指定一个目录，
	// 第一个目录被当作主目录，将编译其下的文件作为执行主体。
	//
	// 如果 OutputName 中未指定目录的话，第一个目录会被当作工作目录使用。
	Dirs  []string
	paths []string

	// 监视器的更新频率，只有文件更新的时长超过此值，才会被定义为更新。
	// 防止文件频繁修改导致的频繁编译调用。
	//
	// 此值不能小于 MinWatcherFrequency。
	//
	// 默认值为 MinWatcherFrequency 表示的值。
	WatcherFrequency time.Duration

	// 传递给 go 命令的参数
	goCmdArgs []string
}

func (opt *Options) sanitize() error {
	if len(opt.Dirs) < 1 {
		return errors.New("参数 dir 至少指定一个")
	}
	wd, err := filepath.Abs(opt.Dirs[0])
	if err != nil {
		return err
	}
	opt.Dirs[0] = wd

	if opt.appName, err = getAppName(opt.OutputName, wd); err != nil {
		return err
	}

	opt.exts = getExts(opt.Exts)

	opt.appArgs = splitArgs(opt.AppArgs)

	if opt.paths, err = recursivePaths(opt.Recursive, opt.Dirs); err != nil {
		return err
	}

	if opt.WatcherFrequency == 0 {
		opt.WatcherFrequency = MinWatcherFrequency
	} else if opt.WatcherFrequency < MinWatcherFrequency {
		return errors.New("watcherFrequency 值过小")
	}

	// 初始化 goCmd 的参数
	args := []string{"build", "-o", opt.appName}
	for k, v := range opt.Flags {
		args = append(args, "-"+k+"flags", v)
	}
	args = append(args, "-v")
	if len(opt.MainFiles) > 0 {
		args = append(args, opt.MainFiles)
	}
	opt.goCmdArgs = args

	return nil
}

// 将 extString 分解成数组，并清理掉无用的内容，比如空字符串
func getExts(extString string) []string {
	exts := strings.Split(extString, ",")
	ret := make([]string, 0, len(exts))

	for _, ext := range exts {
		ext = strings.TrimSpace(ext)

		if len(ext) == 0 {
			continue
		}
		if ext[0] != '.' {
			ext = "." + ext
		}
		ret = append(ret, ext)
	}

	return ret
}

func getAppName(outputName, wd string) (string, error) {
	if outputName == "" {
		outputName = filepath.Base(wd)
	}

	goexe := os.Getenv("GOEXE")
	if goexe != "" && !strings.HasSuffix(outputName, goexe) {
		outputName += goexe
	}

	// 没有分隔符，表示仅有一个文件名，需要加上 wd
	if strings.IndexByte(outputName, '/') < 0 || strings.IndexByte(outputName, filepath.Separator) < 0 {
		outputName = filepath.Join(wd, outputName)
	}

	// 转成绝对路径
	outputName, err := filepath.Abs(outputName)
	if err != nil {
		return "", err
	}

	return outputName, nil
}

// 根据 recursive 值确定是否递归查找 paths 每个目录下的子目录。
func recursivePaths(recursive bool, paths []string) ([]string, error) {
	if !recursive {
		return paths, nil
	}

	ret := []string{}

	walk := func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if fi.IsDir() && (strings.Index(path, "/.") < 0 || strings.Index(path, "\\.") < 0) { // 非隐藏的目录
			ret = append(ret, path)
		}
		return nil
	}

	for _, path := range paths {
		if err := filepath.Walk(path, walk); err != nil {
			return nil, err
		}
	}

	return ret, nil
}

func splitArgs(args string) []string {
	ret := make([]string, 0, 10)
	var state byte
	var start, index int

	for index = 0; index < len(args); index++ {
		b := args[index]
		switch b {
		case ' ':
			if state == '"' {
				break
			}

			if state != ' ' {
				ret = appendArg(ret, args[start:index])
				state = ' '
			}
			start = index + 1
		case '=':
			if state == '"' {
				break
			}

			if state != '=' {
				ret = appendArg(ret, args[start:index])
				state = '='
			}
			start = index + 1
			state = 0
		case '"':
			if state == '"' {
				ret = appendArg(ret, args[start:index])
				state = 0
				start = index + 1
				break
			}

			if start != index {
				ret = appendArg(ret, args[start:index])
			}
			state = '"'
			start = index + 1
		default:
			if state == ' ' {
				state = 0
				start = index
			}
		}
	} // end for

	if start < len(args) {
		ret = appendArg(ret, args[start:])
	}

	return ret
}

func appendArg(args []string, arg string) []string {
	arg = strings.TrimSpace(arg)
	if arg == "" {
		return args
	}

	return append(args, arg)
}
<|endoftext|>


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

package gohg

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

var errstr = "command %s has no option %s"

// These are the options the Hg commands can take.

type (
	Active        bool   // -a --active
	Closed        bool   // -c --closed
	Debug         bool   //    --debug
	Destpath      string // no equivalent Hg option, used by Init()
	Limit         int    // -l --limit
	Mq            bool   //    --mq
	Profile       bool   //    --profile
	Quiet         bool   // -q --quiet
	Remote        bool   //    --remote
	Rev           string // -r -- rev REV
	ShowBookmarks bool   // -B --bookmarks
	ShowBranch    bool   // -b --branch
	ShowId        bool   // -i --id
	ShowNum       bool   // -n --num
	ShowTags      bool   // -t --tags
	Template      string //    --template
	Traceback     bool   //    --traceback
	Verbose       bool   // -v --verbose
)

type optionAdder interface {
	addOption(interface{}, *[]string) error
}

func (o Debug) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Debug")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--debug")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Debug")
	}
	return nil
}

func (o Destpath) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Destpath")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Destpath")
	}
	return nil
}

func (o Limit) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Limit")
	if f.IsValid() || f.CanSet() {
		f.SetInt(int64(o))
		if int64(o) > 0 {
			*hgcmd = append(*hgcmd, "-l")
			*hgcmd = append(*hgcmd, strconv.Itoa(int(int64(o))))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Limit")
	}
	return nil
}

func (o Mq) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Mq")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--mq")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Mq")
	}
	return nil
}

func (o Profile) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Profile")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--profile")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Profile")
	}
	return nil
}

func (o Quiet) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Quiet")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "-q")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Quiet")
	}
	return nil
}

func (o Remote) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Remote")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--remote")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Remote")
	}
	return nil
}

func (o Rev) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Rev")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, "-r")
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Rev")
	}
	return nil
}

func (o ShowBookmarks) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowBookmarks")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--bookmarks")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowBookmarks")
	}
	return nil
}

func (o ShowBranch) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowBranch")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--branch")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowBranch")
	}
	return nil
}

func (o ShowId) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowId")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		// fmt.Printf("ShowId.transl %v\n", bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--id")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowId")
	}
	return nil
}

func (o ShowNum) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowNum")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		// fmt.Printf("ShowNum.transl %v\n", bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--num")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowNum")
	}
	return nil
}

func (o ShowTags) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowTags")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--tags")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowTags")
	}
	return nil
}

func (o Template) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Template")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, "--template")
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Traceback")
	}
	return nil
}

func (o Traceback) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Traceback")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--traceback")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Traceback")
	}
	return nil
}

func (o Verbose) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Verbose")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "-v")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Verbose")
	}
	return nil
}
<commit_msg>options: added Active, Closed, Cwd, Repository<commit_after>// Copyright 2012, The gohg Authors. All rights reserved.
// Use of this source code is governed by a BSD style license
// that can be found in the LICENSE file.

package gohg

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

var errstr = "command %s has no option %s"

// These are the options the Hg commands can take.

type (
	Active        bool   // -a --active
	Closed        bool   // -c --closed
	Cwd           string //    --cwd
	Debug         bool   //    --debug
	Destpath      string // no equivalent Hg option, used by Init()
	Limit         int    // -l --limit
	Mq            bool   //    --mq
	Profile       bool   //    --profile
	Quiet         bool   // -q --quiet
	Remote        bool   //    --remote
	Repository    string // -R --repository
	Rev           string // -r -- rev REV
	ShowBookmarks bool   // -B --bookmarks
	ShowBranch    bool   // -b --branch
	ShowId        bool   // -i --id
	ShowNum       bool   // -n --num
	ShowTags      bool   // -t --tags
	Template      string //    --template
	Traceback     bool   //    --traceback
	Verbose       bool   // -v --verbose
)

type optionAdder interface {
	addOption(interface{}, *[]string) error
}

func (o Active) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Active")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--active")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Active")
	}
	return nil
}

func (o Closed) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Closed")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--closed")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Closed")
	}
	return nil
}

func (o Cwd) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Cwd")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, "--cwd")
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Cwd")
	}
	return nil
}

func (o Debug) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Debug")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--debug")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Debug")
	}
	return nil
}

func (o Destpath) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Destpath")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Destpath")
	}
	return nil
}

func (o Limit) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Limit")
	if f.IsValid() || f.CanSet() {
		f.SetInt(int64(o))
		if int64(o) > 0 {
			*hgcmd = append(*hgcmd, "-l")
			*hgcmd = append(*hgcmd, strconv.Itoa(int(int64(o))))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Limit")
	}
	return nil
}

func (o Mq) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Mq")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--mq")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Mq")
	}
	return nil
}

func (o Profile) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Profile")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--profile")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Profile")
	}
	return nil
}

func (o Quiet) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Quiet")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "-q")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Quiet")
	}
	return nil
}

func (o Remote) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Remote")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--remote")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Remote")
	}
	return nil
}

func (o Repository) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Repository")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, "-R")
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Repository")
	}
	return nil
}

func (o Rev) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Rev")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, "-r")
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Rev")
	}
	return nil
}

func (o ShowBookmarks) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowBookmarks")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--bookmarks")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowBookmarks")
	}
	return nil
}

func (o ShowBranch) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowBranch")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--branch")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowBranch")
	}
	return nil
}

func (o ShowId) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowId")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		// fmt.Printf("ShowId.transl %v\n", bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--id")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowId")
	}
	return nil
}

func (o ShowNum) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowNum")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		// fmt.Printf("ShowNum.transl %v\n", bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--num")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowNum")
	}
	return nil
}

func (o ShowTags) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("ShowTags")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--tags")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "ShowTags")
	}
	return nil
}

func (o Template) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Template")
	if f.IsValid() || f.CanSet() {
		f.SetString(string(o))
		if string(o) != "" {
			*hgcmd = append(*hgcmd, "--template")
			*hgcmd = append(*hgcmd, string(o))
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Traceback")
	}
	return nil
}

func (o Traceback) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Traceback")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "--traceback")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Traceback")
	}
	return nil
}

func (o Verbose) addOption(i interface{}, hgcmd *[]string) error {
	f := reflect.ValueOf(i).Elem().FieldByName("Verbose")
	if f.IsValid() || f.CanSet() {
		f.SetBool(bool(o))
		if bool(o) {
			*hgcmd = append(*hgcmd, "-v")
		}
	} else {
		return fmt.Errorf(errstr, strings.Title((*hgcmd)[0]), "Verbose")
	}
	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright 2017 Dgraph Labs, Inc. and Contributors
 *
 * 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 badger

import (
	"github.com/dgraph-io/badger/v2/options"
)

// Options are params for creating DB object.
//
// This package provides DefaultOptions which contains options that should
// work for most applications. Consider using that as a starting point before
// customizing it for your own needs.
//
// Each option X is documented on the WithX method.
type Options struct {
	// Required options.

	Dir      string
	ValueDir string

	// Usually modified options.

	SyncWrites          bool
	TableLoadingMode    options.FileLoadingMode
	ValueLogLoadingMode options.FileLoadingMode
	NumVersionsToKeep   int
	ReadOnly            bool
	Truncate            bool
	Logger              Logger

	// Fine tuning options.

	MaxTableSize        int64
	LevelSizeMultiplier int
	MaxLevels           int
	ValueThreshold      int
	NumMemtables        int

	NumLevelZeroTables      int
	NumLevelZeroTablesStall int

	LevelOneSize       int64
	ValueLogFileSize   int64
	ValueLogMaxEntries uint32

	NumCompactors     int
	CompactL0OnClose  bool
	LogRotatesToFlush int32

	// Transaction start and commit timestamps are managed by end-user.
	// This is only useful for databases built on top of Badger (like Dgraph).
	// Not recommended for most users.
	managedTxns bool

	// 4. Flags for testing purposes
	// ------------------------------
	maxBatchCount int64 // max entries in batch
	maxBatchSize  int64 // max batch size in bytes

}

// DefaultOptions sets a list of recommended options for good performance.
// Feel free to modify these to suit your needs with the WithX methods.
func DefaultOptions(path string) Options {
	return Options{
		LevelOneSize:        256 << 20,
		LevelSizeMultiplier: 10,
		TableLoadingMode:    options.MemoryMap,
		ValueLogLoadingMode: options.MemoryMap,
		// table.MemoryMap to mmap() the tables.
		// table.Nothing to not preload the tables.
		MaxLevels:               7,
		MaxTableSize:            64 << 20,
		NumCompactors:           2, // Compactions can be expensive. Only run 2.
		NumLevelZeroTables:      5,
		NumLevelZeroTablesStall: 10,
		NumMemtables:            5,
		SyncWrites:              true,
		NumVersionsToKeep:       1,
		CompactL0OnClose:        true,
		// Nothing to read/write value log using standard File I/O
		// MemoryMap to mmap() the value log files
		// (2^30 - 1)*2 when mmapping < 2^31 - 1, max int32.
		// -1 so 2*ValueLogFileSize won't overflow on 32-bit systems.
		ValueLogFileSize: 1<<30 - 1,

		ValueLogMaxEntries: 1000000,
		ValueThreshold:     32,
		Truncate:           false,
		Logger:             defaultLogger,
		LogRotatesToFlush:  2,
	}
}

// LSMOnlyOptions follows from DefaultOptions, but sets a higher ValueThreshold
// so values would be colocated with the LSM tree, with value log largely acting
// as a write-ahead log only. These options would reduce the disk usage of value
// log, and make Badger act more like a typical LSM tree.
func LSMOnlyOptions(path string) Options {
	// Max value length which fits in uint16.
	// Let's not set any other options, because they can cause issues with the
	// size of key-value a user can pass to Badger. For e.g., if we set
	// ValueLogFileSize to 64MB, a user can't pass a value more than that.
	// Setting it to ValueLogMaxEntries to 1000, can generate too many files.
	// These options are better configured on a usage basis, than broadly here.
	// The ValueThreshold is the most important setting a user needs to do to
	// achieve a heavier usage of LSM tree.
	// NOTE: If a user does not want to set 64KB as the ValueThreshold because
	// of performance reasons, 1KB would be a good option too, allowing
	// values smaller than 1KB to be colocated with the keys in the LSM tree.
	return DefaultOptions(path).WithValueThreshold(65500)
}

// WithDir returns a new Options value with Dir set to the given value.
// The original Options value is never modified.
//
// Dir is the path of the directory where key data will be stored in.
// If it doesn't exist, Badger will try to create it for you.
// This is set automatically to be the path given to `DefaultOptions`.
func (opt Options) WithDir(val string) Options {
	opt.Dir = val
	return opt
}

// WithValueDir returns a new Options value with ValueDir set to the given value.
// The original Options value is never modified.
//
// ValueDir is the path of the directory where value data will be stored in.
// If it doesn't exist, Badger will try to create it for you.
// This is set automatically to be the path given to `DefaultOptions`.
func (opt Options) WithValueDir(val string) Options {
	opt.ValueDir = val
	return opt
}

// WithSyncWrites returns a new Options value with SyncWrites set to the given value.
// The original Options value is never modified.
//
// When SyncWrites is true all writes are synced to disk. Setting this to false would achieve better
// performance, but may cause data loss in case of crash.
//
// The default value of SyncWrites is true.
func (opt Options) WithSyncWrites(val bool) Options {
	opt.SyncWrites = val
	return opt
}

// WithTableLoadingMode returns a new Options value with TableLoadingMode set to the given value.
// The original Options value is never modified.
//
// TableLoadingMode indicates which file loading mode should be used for the LSM tree data files.
//
// The default value of TableLoadingMode is options.MemoryMap.
func (opt Options) WithTableLoadingMode(val options.FileLoadingMode) Options {
	opt.TableLoadingMode = val
	return opt
}

// WithValueLogLoadingMode returns a new Options value with ValueLogLoadingMode set to the given
// value.
// The original Options value is never modified.
//
// ValueLogLoadingMode indicates which file loading mode should be used for the value log data
// files.
//
// The default value of ValueLogLoadingMode is options.MemoryMap.
func (opt Options) WithValueLogLoadingMode(val options.FileLoadingMode) Options {
	opt.ValueLogLoadingMode = val
	return opt
}

// WithNumVersionsToKeep returns a new Options value with NumVersionsToKeep set to the given value.
// The original Options value is never modified.
//
// NumVersionsToKeep sets how many versions to keep per key at most.
//
// The default value of NumVersionsToKeep is 1.
func (opt Options) WithNumVersionsToKeep(val int) Options {
	opt.NumVersionsToKeep = val
	return opt
}

// WithReadOnly returns a new Options value with ReadOnly set to the given value.
// The original Options value is never modified.
//
// When ReadOnly is true the DB will be opened on read-only mode.
// Multiple processes can open the same Badger DB.
// Note: if the DB being opened had crashed before and has vlog data to be replayed,
// ReadOnly will cause Open to fail with an appropriate message.
//
// The default value of ReadOnly is false.
func (opt Options) WithReadOnly(val bool) Options {
	opt.ReadOnly = val
	return opt
}

// WithTruncate returns a new Options value with Truncate set to the given value.
// The original Options value is never modified.
//
// Truncate indicates whether value log files should be truncated to delete corrupt data, if any.
// This option is ignored when ReadOnly is true.
//
// The default value of Truncate is false.
func (opt Options) WithTruncate(val bool) Options {
	opt.Truncate = val
	return opt
}

// WithLogger returns a new Options value with Logger set to the given value.
// The original Options value is never modified.
//
// Logger provides a way to configure what logger each value of badger.DB uses.
//
// The default value of Logger writes to stderr using the log package from the Go standard library.
func (opt Options) WithLogger(val Logger) Options {
	opt.Logger = val
	return opt
}

// WithMaxTableSize returns a new Options value with MaxTableSize set to the given value.
// The original Options value is never modified.
//
// MaxTableSize sets the maximum size in bytes for each LSM table or file.
//
// The default value of MaxTableSize is 64MB.
func (opt Options) WithMaxTableSize(val int64) Options {
	opt.MaxTableSize = val
	return opt
}

// WithLevelSizeMultiplier returns a new Options value with LevelSizeMultiplier set to the given
// value.
// The original Options value is never modified.
//
// LevelSizeMultiplier sets the ratio between the maximum sizes of contiguous levels in the LSM.
// Once a level grows to be larger than this ratio allowed, the compaction process will be
//  triggered.
//
// The default value of LevelSizeMultiplier is 10.
func (opt Options) WithLevelSizeMultiplier(val int) Options {
	opt.LevelSizeMultiplier = val
	return opt
}

// WithMaxLevels returns a new Options value with MaxLevels set to the given value.
// The original Options value is never modified.
//
// Maximum number of levels of compaction allowed in the LSM.
//
// The default value of MaxLevels is 7.
func (opt Options) WithMaxLevels(val int) Options {
	opt.MaxLevels = val
	return opt
}

// WithValueThreshold returns a new Options value with ValueThreshold set to the given value.
// The original Options value is never modified.
//
// ValueThreshold sets the threshold used to decide whether a value is stored directly in the LSM
// tree or separatedly in the log value files.
//
// The default value of ValueThreshold is 32, but LSMOnlyOptions sets it to 65500.
func (opt Options) WithValueThreshold(val int) Options {
	opt.ValueThreshold = val
	return opt
}

// WithNumMemtables returns a new Options value with NumMemtables set to the given value.
// The original Options value is never modified.
//
// NumMemtables sets the maximum number of tables to keep in memory before stalling.
//
// The default value of NumMemtables is 5.
func (opt Options) WithNumMemtables(val int) Options {
	opt.NumMemtables = val
	return opt
}

// WithNumLevelZeroTables returns a new Options value with NumLevelZeroTables set to the given
// value.
// The original Options value is never modified.
//
// NumLevelZeroTables sets the maximum number of Level 0 tables before compaction starts.
//
// The default value of NumLevelZeroTables is 5.
func (opt Options) WithNumLevelZeroTables(val int) Options {
	opt.NumLevelZeroTables = val
	return opt
}

// WithNumLevelZeroTablesStall returns a new Options value with NumLevelZeroTablesStall set to the
// given value.
// The original Options value is never modified.
//
// NumLevelZeroTablesStall sets the number of Level 0 tables that once reached causes the DB to
// stall until compaction succeeds.
//
// The default value of NumLevelZeroTablesStall is 10.
func (opt Options) WithNumLevelZeroTablesStall(val int) Options {
	opt.NumLevelZeroTablesStall = val
	return opt
}

// WithLevelOneSize returns a new Options value with LevelOneSize set to the given value.
// The original Options value is never modified.
//
// LevelOneSize sets the maximum total size for Level 1.
//
// The default value of LevelOneSize is 20MB.
func (opt Options) WithLevelOneSize(val int64) Options {
	opt.LevelOneSize = val
	return opt
}

// WithValueLogFileSize returns a new Options value with ValueLogFileSize set to the given value.
// The original Options value is never modified.
//
// ValueLogFileSize sets the maximum size of a single value log file.
//
// The default value of ValueLogFileSize is 1GB.
func (opt Options) WithValueLogFileSize(val int64) Options {
	opt.ValueLogFileSize = val
	return opt
}

// WithValueLogMaxEntries returns a new Options value with ValueLogMaxEntries set to the given
// value.
// The original Options value is never modified.
//
// ValueLogMaxEntries sets the maximum number of entries a value log file can hold approximately.
// A actual size limit of a value log file is the minimum of ValueLogFileSize and
// ValueLogMaxEntries.
//
// The default value of ValueLogMaxEntries is one million (1000000).
func (opt Options) WithValueLogMaxEntries(val uint32) Options {
	opt.ValueLogMaxEntries = val
	return opt
}

// WithNumCompactors returns a new Options value with NumCompactors set to the given value.
// The original Options value is never modified.
//
// NumCompactors sets the number of compaction workers to run concurrently.
// Setting this to zero stops compactions, which could eventually cause writes to block forever.
//
// The default value of NumCompactors is 2.
func (opt Options) WithNumCompactors(val int) Options {
	opt.NumCompactors = val
	return opt
}

// WithCompactL0OnClose returns a new Options value with CompactL0OnClose set to the given value.
// The original Options value is never modified.
//
// CompactL0OnClose determines whether Level 0 should be compacted before closing the DB.
// This ensures that both reads and writes are efficient when the DB is opened later.
//
// The default value of CompactL0OnClose is true.
func (opt Options) WithCompactL0OnClose(val bool) Options {
	opt.CompactL0OnClose = val
	return opt
}

// WithLogRotatesToFlush returns a new Options value with LogRotatesToFlush set to the given value.
// The original Options value is never modified.
//
// LogRotatesToFlush sets the number of value log file rotates after which the Memtables are
// flushed to disk. This is useful in write loads with fewer keys and larger values. This work load
// would fill up the value logs quickly, while not filling up the Memtables. Thus, on a crash
// and restart, the value log head could cause the replay of a good number of value log files
// which can slow things on start.
//
// The default value of LogRotatesToFlush is 2.
func (opt Options) WithLogRotatesToFlush(val int32) Options {
	opt.LogRotatesToFlush = val
	return opt
}
<commit_msg>Apply @manishrjain's feedback<commit_after>/*
 * Copyright 2017 Dgraph Labs, Inc. and Contributors
 *
 * 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 badger

import (
	"github.com/dgraph-io/badger/v2/options"
)

// Note: If you add a new option X make sure you also add a WithX method on Options.

// Options are params for creating DB object.
//
// This package provides DefaultOptions which contains options that should
// work for most applications. Consider using that as a starting point before
// customizing it for your own needs.
//
// Each option X is documented on the WithX method.
type Options struct {
	// Required options.

	Dir      string
	ValueDir string

	// Usually modified options.

	SyncWrites          bool
	TableLoadingMode    options.FileLoadingMode
	ValueLogLoadingMode options.FileLoadingMode
	NumVersionsToKeep   int
	ReadOnly            bool
	Truncate            bool
	Logger              Logger

	// Fine tuning options.

	MaxTableSize        int64
	LevelSizeMultiplier int
	MaxLevels           int
	ValueThreshold      int
	NumMemtables        int

	NumLevelZeroTables      int
	NumLevelZeroTablesStall int

	LevelOneSize       int64
	ValueLogFileSize   int64
	ValueLogMaxEntries uint32

	NumCompactors     int
	CompactL0OnClose  bool
	LogRotatesToFlush int32

	// Transaction start and commit timestamps are managed by end-user.
	// This is only useful for databases built on top of Badger (like Dgraph).
	// Not recommended for most users.
	managedTxns bool

	// 4. Flags for testing purposes
	// ------------------------------
	maxBatchCount int64 // max entries in batch
	maxBatchSize  int64 // max batch size in bytes

}

// DefaultOptions sets a list of recommended options for good performance.
// Feel free to modify these to suit your needs with the WithX methods.
func DefaultOptions(path string) Options {
	return Options{
		LevelOneSize:        256 << 20,
		LevelSizeMultiplier: 10,
		TableLoadingMode:    options.MemoryMap,
		ValueLogLoadingMode: options.MemoryMap,
		// table.MemoryMap to mmap() the tables.
		// table.Nothing to not preload the tables.
		MaxLevels:               7,
		MaxTableSize:            64 << 20,
		NumCompactors:           2, // Compactions can be expensive. Only run 2.
		NumLevelZeroTables:      5,
		NumLevelZeroTablesStall: 10,
		NumMemtables:            5,
		SyncWrites:              true,
		NumVersionsToKeep:       1,
		CompactL0OnClose:        true,
		// Nothing to read/write value log using standard File I/O
		// MemoryMap to mmap() the value log files
		// (2^30 - 1)*2 when mmapping < 2^31 - 1, max int32.
		// -1 so 2*ValueLogFileSize won't overflow on 32-bit systems.
		ValueLogFileSize: 1<<30 - 1,

		ValueLogMaxEntries: 1000000,
		ValueThreshold:     32,
		Truncate:           false,
		Logger:             defaultLogger,
		LogRotatesToFlush:  2,
	}
}

// LSMOnlyOptions follows from DefaultOptions, but sets a higher ValueThreshold
// so values would be colocated with the LSM tree, with value log largely acting
// as a write-ahead log only. These options would reduce the disk usage of value
// log, and make Badger act more like a typical LSM tree.
func LSMOnlyOptions(path string) Options {
	// Max value length which fits in uint16.
	// Let's not set any other options, because they can cause issues with the
	// size of key-value a user can pass to Badger. For e.g., if we set
	// ValueLogFileSize to 64MB, a user can't pass a value more than that.
	// Setting it to ValueLogMaxEntries to 1000, can generate too many files.
	// These options are better configured on a usage basis, than broadly here.
	// The ValueThreshold is the most important setting a user needs to do to
	// achieve a heavier usage of LSM tree.
	// NOTE: If a user does not want to set 64KB as the ValueThreshold because
	// of performance reasons, 1KB would be a good option too, allowing
	// values smaller than 1KB to be colocated with the keys in the LSM tree.
	return DefaultOptions(path).WithValueThreshold(65500)
}

// WithDir returns a new Options value with Dir set to the given value.
//
// Dir is the path of the directory where key data will be stored in.
// If it doesn't exist, Badger will try to create it for you.
// This is set automatically to be the path given to `DefaultOptions`.
func (opt Options) WithDir(val string) Options {
	opt.Dir = val
	return opt
}

// WithValueDir returns a new Options value with ValueDir set to the given value.
//
// ValueDir is the path of the directory where value data will be stored in.
// If it doesn't exist, Badger will try to create it for you.
// This is set automatically to be the path given to `DefaultOptions`.
func (opt Options) WithValueDir(val string) Options {
	opt.ValueDir = val
	return opt
}

// WithSyncWrites returns a new Options value with SyncWrites set to the given value.
//
// When SyncWrites is true all writes are synced to disk. Setting this to false would achieve better
// performance, but may cause data loss in case of crash.
//
// The default value of SyncWrites is true.
func (opt Options) WithSyncWrites(val bool) Options {
	opt.SyncWrites = val
	return opt
}

// WithTableLoadingMode returns a new Options value with TableLoadingMode set to the given value.
//
// TableLoadingMode indicates which file loading mode should be used for the LSM tree data files.
//
// The default value of TableLoadingMode is options.MemoryMap.
func (opt Options) WithTableLoadingMode(val options.FileLoadingMode) Options {
	opt.TableLoadingMode = val
	return opt
}

// WithValueLogLoadingMode returns a new Options value with ValueLogLoadingMode set to the given
// value.
//
// ValueLogLoadingMode indicates which file loading mode should be used for the value log data
// files.
//
// The default value of ValueLogLoadingMode is options.MemoryMap.
func (opt Options) WithValueLogLoadingMode(val options.FileLoadingMode) Options {
	opt.ValueLogLoadingMode = val
	return opt
}

// WithNumVersionsToKeep returns a new Options value with NumVersionsToKeep set to the given value.
//
// NumVersionsToKeep sets how many versions to keep per key at most.
//
// The default value of NumVersionsToKeep is 1.
func (opt Options) WithNumVersionsToKeep(val int) Options {
	opt.NumVersionsToKeep = val
	return opt
}

// WithReadOnly returns a new Options value with ReadOnly set to the given value.
//
// When ReadOnly is true the DB will be opened on read-only mode.
// Multiple processes can open the same Badger DB.
// Note: if the DB being opened had crashed before and has vlog data to be replayed,
// ReadOnly will cause Open to fail with an appropriate message.
//
// The default value of ReadOnly is false.
func (opt Options) WithReadOnly(val bool) Options {
	opt.ReadOnly = val
	return opt
}

// WithTruncate returns a new Options value with Truncate set to the given value.
//
// Truncate indicates whether value log files should be truncated to delete corrupt data, if any.
// This option is ignored when ReadOnly is true.
//
// The default value of Truncate is false.
func (opt Options) WithTruncate(val bool) Options {
	opt.Truncate = val
	return opt
}

// WithLogger returns a new Options value with Logger set to the given value.
//
// Logger provides a way to configure what logger each value of badger.DB uses.
//
// The default value of Logger writes to stderr using the log package from the Go standard library.
func (opt Options) WithLogger(val Logger) Options {
	opt.Logger = val
	return opt
}

// WithMaxTableSize returns a new Options value with MaxTableSize set to the given value.
//
// MaxTableSize sets the maximum size in bytes for each LSM table or file.
//
// The default value of MaxTableSize is 64MB.
func (opt Options) WithMaxTableSize(val int64) Options {
	opt.MaxTableSize = val
	return opt
}

// WithLevelSizeMultiplier returns a new Options value with LevelSizeMultiplier set to the given
// value.
//
// LevelSizeMultiplier sets the ratio between the maximum sizes of contiguous levels in the LSM.
// Once a level grows to be larger than this ratio allowed, the compaction process will be
//  triggered.
//
// The default value of LevelSizeMultiplier is 10.
func (opt Options) WithLevelSizeMultiplier(val int) Options {
	opt.LevelSizeMultiplier = val
	return opt
}

// WithMaxLevels returns a new Options value with MaxLevels set to the given value.
//
// Maximum number of levels of compaction allowed in the LSM.
//
// The default value of MaxLevels is 7.
func (opt Options) WithMaxLevels(val int) Options {
	opt.MaxLevels = val
	return opt
}

// WithValueThreshold returns a new Options value with ValueThreshold set to the given value.
//
// ValueThreshold sets the threshold used to decide whether a value is stored directly in the LSM
// tree or separatedly in the log value files.
//
// The default value of ValueThreshold is 32, but LSMOnlyOptions sets it to 65500.
func (opt Options) WithValueThreshold(val int) Options {
	opt.ValueThreshold = val
	return opt
}

// WithNumMemtables returns a new Options value with NumMemtables set to the given value.
//
// NumMemtables sets the maximum number of tables to keep in memory before stalling.
//
// The default value of NumMemtables is 5.
func (opt Options) WithNumMemtables(val int) Options {
	opt.NumMemtables = val
	return opt
}

// WithNumLevelZeroTables returns a new Options value with NumLevelZeroTables set to the given
// value.
//
// NumLevelZeroTables sets the maximum number of Level 0 tables before compaction starts.
//
// The default value of NumLevelZeroTables is 5.
func (opt Options) WithNumLevelZeroTables(val int) Options {
	opt.NumLevelZeroTables = val
	return opt
}

// WithNumLevelZeroTablesStall returns a new Options value with NumLevelZeroTablesStall set to the
// given value.
//
// NumLevelZeroTablesStall sets the number of Level 0 tables that once reached causes the DB to
// stall until compaction succeeds.
//
// The default value of NumLevelZeroTablesStall is 10.
func (opt Options) WithNumLevelZeroTablesStall(val int) Options {
	opt.NumLevelZeroTablesStall = val
	return opt
}

// WithLevelOneSize returns a new Options value with LevelOneSize set to the given value.
//
// LevelOneSize sets the maximum total size for Level 1.
//
// The default value of LevelOneSize is 20MB.
func (opt Options) WithLevelOneSize(val int64) Options {
	opt.LevelOneSize = val
	return opt
}

// WithValueLogFileSize returns a new Options value with ValueLogFileSize set to the given value.
//
// ValueLogFileSize sets the maximum size of a single value log file.
//
// The default value of ValueLogFileSize is 1GB.
func (opt Options) WithValueLogFileSize(val int64) Options {
	opt.ValueLogFileSize = val
	return opt
}

// WithValueLogMaxEntries returns a new Options value with ValueLogMaxEntries set to the given
// value.
//
// ValueLogMaxEntries sets the maximum number of entries a value log file can hold approximately.
// A actual size limit of a value log file is the minimum of ValueLogFileSize and
// ValueLogMaxEntries.
//
// The default value of ValueLogMaxEntries is one million (1000000).
func (opt Options) WithValueLogMaxEntries(val uint32) Options {
	opt.ValueLogMaxEntries = val
	return opt
}

// WithNumCompactors returns a new Options value with NumCompactors set to the given value.
//
// NumCompactors sets the number of compaction workers to run concurrently.
// Setting this to zero stops compactions, which could eventually cause writes to block forever.
//
// The default value of NumCompactors is 2.
func (opt Options) WithNumCompactors(val int) Options {
	opt.NumCompactors = val
	return opt
}

// WithCompactL0OnClose returns a new Options value with CompactL0OnClose set to the given value.
//
// CompactL0OnClose determines whether Level 0 should be compacted before closing the DB.
// This ensures that both reads and writes are efficient when the DB is opened later.
//
// The default value of CompactL0OnClose is true.
func (opt Options) WithCompactL0OnClose(val bool) Options {
	opt.CompactL0OnClose = val
	return opt
}

// WithLogRotatesToFlush returns a new Options value with LogRotatesToFlush set to the given value.
//
// LogRotatesToFlush sets the number of value log file rotates after which the Memtables are
// flushed to disk. This is useful in write loads with fewer keys and larger values. This work load
// would fill up the value logs quickly, while not filling up the Memtables. Thus, on a crash
// and restart, the value log head could cause the replay of a good number of value log files
// which can slow things on start.
//
// The default value of LogRotatesToFlush is 2.
func (opt Options) WithLogRotatesToFlush(val int32) Options {
	opt.LogRotatesToFlush = val
	return opt
}
<|endoftext|>


<commit_before>package instana

import (
	"os"
	"strconv"
)

// Options allows the user to configure the to-be-initialized sensor
type Options struct {
	Service                     string
	AgentHost                   string
	AgentPort                   int
	MaxBufferedSpans            int
	ForceTransmissionStartingAt int
	LogLevel                    int
	EnableAutoProfile           bool
	MaxBufferedProfiles         int
	IncludeProfilerFrames       bool
	Tracer                      TracerOptions
}

// DefaultOptions returns the default set of options to configure Instana sensor.
// The service name is set to the name of current executable, the MaxBufferedSpans
// and ForceTransmissionStartingAt are set to instana.DefaultMaxBufferedSpans and
// instana.DefaultForceSpanSendAt correspondigly. The AgentHost and AgentPort are
// taken from the env INSTANA_AGENT_HOST and INSTANA_AGENT_PORT if set, and default
// to localhost and 46999 otherwise.
func DefaultOptions() *Options {
	opts := &Options{
		Tracer: DefaultTracerOptions(),
	}
	opts.setDefaults()

	return opts
}

func (opts *Options) setDefaults() {
	if opts.MaxBufferedSpans == 0 {
		opts.MaxBufferedSpans = DefaultMaxBufferedSpans
	}

	if opts.ForceTransmissionStartingAt == 0 {
		opts.ForceTransmissionStartingAt = DefaultForceSpanSendAt
	}

	if opts.AgentHost == "" {
		opts.AgentHost = agentDefaultHost

		if host := os.Getenv("INSTANA_AGENT_HOST"); host != "" {
			opts.AgentHost = host
		}
	}

	if opts.AgentPort == 0 {
		opts.AgentPort = agentDefaultPort

		if port, err := strconv.Atoi(os.Getenv("INSTANA_AGENT_PORT")); err == nil {
			opts.AgentPort = port
		}
	}

	secretsMatcher, err := parseInstanaSecrets(os.Getenv("INSTANA_SECRETS"))
	if err != nil {
		defaultLogger.Warn("invalid INSTANA_SECRETS= env variable value: ", err, ", ignoring")
		secretsMatcher = opts.Tracer.Secrets
	}

	if secretsMatcher == nil {
		secretsMatcher = DefaultSecretsMatcher()
	}

	opts.Tracer.Secrets = secretsMatcher
}
<commit_msg>Collect HTTP headers specified in INSTANA_EXTRA_HTTP_HEADERS var<commit_after>package instana

import (
	"os"
	"strconv"
)

// Options allows the user to configure the to-be-initialized sensor
type Options struct {
	Service                     string
	AgentHost                   string
	AgentPort                   int
	MaxBufferedSpans            int
	ForceTransmissionStartingAt int
	LogLevel                    int
	EnableAutoProfile           bool
	MaxBufferedProfiles         int
	IncludeProfilerFrames       bool
	Tracer                      TracerOptions
}

// DefaultOptions returns the default set of options to configure Instana sensor.
// The service name is set to the name of current executable, the MaxBufferedSpans
// and ForceTransmissionStartingAt are set to instana.DefaultMaxBufferedSpans and
// instana.DefaultForceSpanSendAt correspondigly. The AgentHost and AgentPort are
// taken from the env INSTANA_AGENT_HOST and INSTANA_AGENT_PORT if set, and default
// to localhost and 46999 otherwise.
func DefaultOptions() *Options {
	opts := &Options{
		Tracer: DefaultTracerOptions(),
	}
	opts.setDefaults()

	return opts
}

func (opts *Options) setDefaults() {
	if opts.MaxBufferedSpans == 0 {
		opts.MaxBufferedSpans = DefaultMaxBufferedSpans
	}

	if opts.ForceTransmissionStartingAt == 0 {
		opts.ForceTransmissionStartingAt = DefaultForceSpanSendAt
	}

	if opts.AgentHost == "" {
		opts.AgentHost = agentDefaultHost

		if host := os.Getenv("INSTANA_AGENT_HOST"); host != "" {
			opts.AgentHost = host
		}
	}

	if opts.AgentPort == 0 {
		opts.AgentPort = agentDefaultPort

		if port, err := strconv.Atoi(os.Getenv("INSTANA_AGENT_PORT")); err == nil {
			opts.AgentPort = port
		}
	}

	secretsMatcher, err := parseInstanaSecrets(os.Getenv("INSTANA_SECRETS"))
	if err != nil {
		defaultLogger.Warn("invalid INSTANA_SECRETS= env variable value: ", err, ", ignoring")
		secretsMatcher = opts.Tracer.Secrets
	}

	if secretsMatcher == nil {
		secretsMatcher = DefaultSecretsMatcher()
	}

	opts.Tracer.Secrets = secretsMatcher

	if collectableHeaders, ok := os.LookupEnv("INSTANA_EXTRA_HTTP_HEADERS"); ok {
		opts.Tracer.CollectableHTTPHeaders = parseInstanaExtraHTTPHeaders(collectableHeaders)
	}
}
<|endoftext|>


<commit_before>package out

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/pkg/errors"
)

//Execute - provides out capability
func Execute(sourceRoot, version string, input []byte) (string, error) {

	logger := log.New(os.Stderr, "", log.LstdFlags)

	if sourceRoot == "" {
		return "", errors.New("expected path to build sources as first argument")
	}

	var indata Input

	err := json.Unmarshal(input, &indata)
	if err != nil {
		return "", errors.Wrap(err, "unmarshalling input")
	}

	err = validateConfiguration(indata)
	if err != nil {
		return "", errors.Wrap(err, "Invalid configuration")
	}

	source := indata.Source
	params := indata.Params
	debug := strings.EqualFold("true", params.Debug)
	smtpConfig := source.SMTP

	if debug {
		logger.Println(fmt.Sprintf("Params: %+v", params))
	}
	if debug {
		logger.Println("Getting subject")
	}
	subject, err := fromTextOrFile(sourceRoot, params.SubjectText, params.Subject)
	if err != nil {
		return "", errors.Wrap(err, "Error getting Subject:")
	}
	subject = strings.Trim(subject, "\n")

	if debug {
		logger.Println("Getting Body")
	}
	body, err := fromTextOrFile(sourceRoot, params.BodyText, params.Body)
	if err != nil {
		return "", errors.Wrap(err, "Error getting Body:")
	}

	toArray, err := sliceFromTextOrFile(sourceRoot, params.ToText, params.To)
	if err != nil {
		return "", errors.Wrap(err, "Error getting to list:")
	}
	source.To = append(source.To, toArray...)

	ccArray, err := sliceFromTextOrFile(sourceRoot, params.CcText, params.Cc)
	if err != nil {
		return "", errors.Wrap(err, "Error getting cc list:")
	}
	source.Cc = append(source.Bcc, ccArray...)

	bccArray, err := sliceFromTextOrFile(sourceRoot, params.BccText, params.Bcc)
	if err != nil {
		return "", errors.Wrap(err, "Error getting cc list:")
	}
	source.Bcc = append(source.Bcc, bccArray...)

	var outdata Output
	outdata.Version.Time = time.Now().UTC()
	outdata.Metadata = []MetadataItem{
		{Name: "smtp_host", Value: smtpConfig.Host},
		{Name: "subject", Value: subject},
		{Name: "version", Value: version},
	}
	outbytes, err := json.Marshal(outdata)
	if err != nil {
		return "", errors.Wrap(err, "Error Marshalling JSON:")
	}

	if params.SendEmptyBody == false && len(body) == 0 {
		logger.Println("Message not sent because the message body is empty and send_empty_body parameter was set to false. Github readme: https://github.com/pivotal-cf/email-resource")
		return string(outbytes), nil
	}

	if debug {
		logger.Println("Building Message Payload")
	}

	mail := NewMailCreator()
	mail.From = source.From
	mail.To = source.To
	mail.CC = source.Cc
	mail.BCC = source.Bcc
	mail.Subject = subject
	mail.Body = body
	if params.Headers != "" {
		if debug {
			logger.Println("Getting headers")
		}
		var headersString string
		headersString, err = readSource(sourceRoot, params.Headers)
		if err != nil {
			return "", errors.Wrap(err, "unable to read source file for headers")
		}
		headersString = strings.Trim(headersString, "\n")
		lines := strings.Split(headersString, "\n")
		for _, line := range lines {
			kv := strings.Split(line, ": ")
			mail.AddHeader(kv[0], kv[1])
		}
	}

	if len(params.AttachmentGlobs) > 0 {
		for _, glob := range params.AttachmentGlobs {
			globPath := filepath.Join(sourceRoot, glob)
			logger.Println(fmt.Sprintf("Looking for files with pattern %s", globPath))
			paths, err := filepath.Glob(globPath)
			if err != nil {
				return "", errors.Wrapf(err, "Error getting files from glob %s", globPath)
			}
			for _, attachmentPath := range paths {
				logger.Println(fmt.Sprintf("Attaching files %s", attachmentPath))
				err = mail.AddAttachment(attachmentPath)
				if err != nil {
					return "", errors.Wrapf(err, "Error adding attachement from path %s", attachmentPath)
				}
			}
		}
	}

	sender := NewSender(smtpConfig.Host, smtpConfig.Port, smtpConfig.Username, smtpConfig.Password, debug, logger)
	sender.HostOrigin = smtpConfig.HostOrigin
	sender.CaCert = smtpConfig.CaCert
	sender.Anonymous = smtpConfig.Anonymous
	sender.LoginAuth = smtpConfig.LoginAuth
	sender.SkipSSLValidation = smtpConfig.SkipSSLValidation
	sender.From = source.From
	sender.To = append(append(source.To, source.Cc...), source.Bcc...)

	msg, err := mail.Compose()
	if err != nil {
		return "", errors.Wrapf(err, "Error composing mail")
	}
	err = sender.Send(msg)
	if err != nil {
		return "", err
	}

	return string(outbytes), nil
}

func validateConfiguration(indata Input) error {
	if indata.Source.SMTP.Host == "" {
		return errors.New(`missing required field "source.smtp.host"`)
	}

	if indata.Source.SMTP.Port == "" {
		return errors.New(`missing required field "source.smtp.port"`)
	}

	if indata.Source.From == "" {
		return errors.New(`missing required field "source.from"`)
	}

	if len(indata.Source.To) == 0 && len(indata.Params.To) == 0 && len(indata.Params.ToText) == 0 {
		return errors.New(`missing required field "source.to" or "params.to" or "params.to_text". Must specify at least one`)
	}

	if indata.Params.Subject == "" && indata.Params.SubjectText == "" {
		return errors.New(`missing required field "params.subject" or "params.subject_text". Must specify at least one`)
	}

	if indata.Source.SMTP.Anonymous == false {
		if indata.Source.SMTP.Username == "" {
			return errors.New(`missing required field "source.smtp.username" if anonymous specify anonymous: true`)
		}

		if indata.Source.SMTP.Password == "" {
			return errors.New(`missing required field "source.smtp.password" if anonymous specify anonymous: true`)
		}
	}
	return nil
}

func replaceTokens(sourceString string) string {
	var buildTokens = map[string]string{
		"${BUILD_ID}":            os.Getenv("BUILD_ID"),
		"${BUILD_NAME}":          os.Getenv("BUILD_NAME"),
		"${BUILD_JOB_NAME}":      os.Getenv("BUILD_JOB_NAME"),
		"${BUILD_PIPELINE_NAME}": os.Getenv("BUILD_PIPELINE_NAME"),
		"${ATC_EXTERNAL_URL}":    os.Getenv("ATC_EXTERNAL_URL"),
		"${BUILD_TEAM_NAME}":     os.Getenv("BUILD_TEAM_NAME"),
	}
	for k, v := range buildTokens {
		sourceString = strings.Replace(sourceString, k, v, -1)
	}
	return sourceString
}

func readSource(sourceRoot, sourcePath string) (string, error) {
	if !filepath.IsAbs(sourcePath) {
		sourcePath = filepath.Join(sourceRoot, sourcePath)
	}
	bytes, err := ioutil.ReadFile(sourcePath)
	return replaceTokens(string(bytes)), err
}

func fromTextOrFile(sourceRoot, text, filePath string) (string, error) {
	if text != "" {
		return replaceTokens(text), nil

	}
	if filePath != "" {
		return readSource(sourceRoot, filePath)
	}
	return "", nil
}

func sliceFromTextOrFile(sourceRoot, text, filePath string) ([]string, error) {
	var returnList []string
	if text != "" {
		listArray := strings.Split(text, ",")
		for _, item := range listArray {
			returnList = append(returnList, strings.TrimSpace(item))
		}
	}
	if filePath != "" {
		fileList, err := readSource(sourceRoot, filePath)
		if err != nil {
			return nil, errors.Wrapf(err, "Error reading file %s", filePath)
		}
		if len(fileList) > 0 {
			listArray := strings.Split(fileList, ",")
			for _, item := range listArray {
				returnList = append(returnList, strings.TrimSpace(item))
			}
		}
	}
	return returnList, nil
}
<commit_msg>Fix CC source parameter<commit_after>package out

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/pkg/errors"
)

//Execute - provides out capability
func Execute(sourceRoot, version string, input []byte) (string, error) {

	logger := log.New(os.Stderr, "", log.LstdFlags)

	if sourceRoot == "" {
		return "", errors.New("expected path to build sources as first argument")
	}

	var indata Input

	err := json.Unmarshal(input, &indata)
	if err != nil {
		return "", errors.Wrap(err, "unmarshalling input")
	}

	err = validateConfiguration(indata)
	if err != nil {
		return "", errors.Wrap(err, "Invalid configuration")
	}

	source := indata.Source
	params := indata.Params
	debug := strings.EqualFold("true", params.Debug)
	smtpConfig := source.SMTP

	if debug {
		logger.Println(fmt.Sprintf("Params: %+v", params))
	}
	if debug {
		logger.Println("Getting subject")
	}
	subject, err := fromTextOrFile(sourceRoot, params.SubjectText, params.Subject)
	if err != nil {
		return "", errors.Wrap(err, "Error getting Subject:")
	}
	subject = strings.Trim(subject, "\n")

	if debug {
		logger.Println("Getting Body")
	}
	body, err := fromTextOrFile(sourceRoot, params.BodyText, params.Body)
	if err != nil {
		return "", errors.Wrap(err, "Error getting Body:")
	}

	toArray, err := sliceFromTextOrFile(sourceRoot, params.ToText, params.To)
	if err != nil {
		return "", errors.Wrap(err, "Error getting to list:")
	}
	source.To = append(source.To, toArray...)

	ccArray, err := sliceFromTextOrFile(sourceRoot, params.CcText, params.Cc)
	if err != nil {
		return "", errors.Wrap(err, "Error getting cc list:")
	}
	source.Cc = append(source.Cc, ccArray...)

	bccArray, err := sliceFromTextOrFile(sourceRoot, params.BccText, params.Bcc)
	if err != nil {
		return "", errors.Wrap(err, "Error getting cc list:")
	}
	source.Bcc = append(source.Bcc, bccArray...)

	var outdata Output
	outdata.Version.Time = time.Now().UTC()
	outdata.Metadata = []MetadataItem{
		{Name: "smtp_host", Value: smtpConfig.Host},
		{Name: "subject", Value: subject},
		{Name: "version", Value: version},
	}
	outbytes, err := json.Marshal(outdata)
	if err != nil {
		return "", errors.Wrap(err, "Error Marshalling JSON:")
	}

	if params.SendEmptyBody == false && len(body) == 0 {
		logger.Println("Message not sent because the message body is empty and send_empty_body parameter was set to false. Github readme: https://github.com/pivotal-cf/email-resource")
		return string(outbytes), nil
	}

	if debug {
		logger.Println("Building Message Payload")
	}

	mail := NewMailCreator()
	mail.From = source.From
	mail.To = source.To
	mail.CC = source.Cc
	mail.BCC = source.Bcc
	mail.Subject = subject
	mail.Body = body
	if params.Headers != "" {
		if debug {
			logger.Println("Getting headers")
		}
		var headersString string
		headersString, err = readSource(sourceRoot, params.Headers)
		if err != nil {
			return "", errors.Wrap(err, "unable to read source file for headers")
		}
		headersString = strings.Trim(headersString, "\n")
		lines := strings.Split(headersString, "\n")
		for _, line := range lines {
			kv := strings.Split(line, ": ")
			mail.AddHeader(kv[0], kv[1])
		}
	}

	if len(params.AttachmentGlobs) > 0 {
		for _, glob := range params.AttachmentGlobs {
			globPath := filepath.Join(sourceRoot, glob)
			logger.Println(fmt.Sprintf("Looking for files with pattern %s", globPath))
			paths, err := filepath.Glob(globPath)
			if err != nil {
				return "", errors.Wrapf(err, "Error getting files from glob %s", globPath)
			}
			for _, attachmentPath := range paths {
				logger.Println(fmt.Sprintf("Attaching files %s", attachmentPath))
				err = mail.AddAttachment(attachmentPath)
				if err != nil {
					return "", errors.Wrapf(err, "Error adding attachement from path %s", attachmentPath)
				}
			}
		}
	}

	sender := NewSender(smtpConfig.Host, smtpConfig.Port, smtpConfig.Username, smtpConfig.Password, debug, logger)
	sender.HostOrigin = smtpConfig.HostOrigin
	sender.CaCert = smtpConfig.CaCert
	sender.Anonymous = smtpConfig.Anonymous
	sender.LoginAuth = smtpConfig.LoginAuth
	sender.SkipSSLValidation = smtpConfig.SkipSSLValidation
	sender.From = source.From
	sender.To = append(append(source.To, source.Cc...), source.Bcc...)

	msg, err := mail.Compose()
	if err != nil {
		return "", errors.Wrapf(err, "Error composing mail")
	}
	err = sender.Send(msg)
	if err != nil {
		return "", err
	}

	return string(outbytes), nil
}

func validateConfiguration(indata Input) error {
	if indata.Source.SMTP.Host == "" {
		return errors.New(`missing required field "source.smtp.host"`)
	}

	if indata.Source.SMTP.Port == "" {
		return errors.New(`missing required field "source.smtp.port"`)
	}

	if indata.Source.From == "" {
		return errors.New(`missing required field "source.from"`)
	}

	if len(indata.Source.To) == 0 && len(indata.Params.To) == 0 && len(indata.Params.ToText) == 0 {
		return errors.New(`missing required field "source.to" or "params.to" or "params.to_text". Must specify at least one`)
	}

	if indata.Params.Subject == "" && indata.Params.SubjectText == "" {
		return errors.New(`missing required field "params.subject" or "params.subject_text". Must specify at least one`)
	}

	if indata.Source.SMTP.Anonymous == false {
		if indata.Source.SMTP.Username == "" {
			return errors.New(`missing required field "source.smtp.username" if anonymous specify anonymous: true`)
		}

		if indata.Source.SMTP.Password == "" {
			return errors.New(`missing required field "source.smtp.password" if anonymous specify anonymous: true`)
		}
	}
	return nil
}

func replaceTokens(sourceString string) string {
	var buildTokens = map[string]string{
		"${BUILD_ID}":            os.Getenv("BUILD_ID"),
		"${BUILD_NAME}":          os.Getenv("BUILD_NAME"),
		"${BUILD_JOB_NAME}":      os.Getenv("BUILD_JOB_NAME"),
		"${BUILD_PIPELINE_NAME}": os.Getenv("BUILD_PIPELINE_NAME"),
		"${ATC_EXTERNAL_URL}":    os.Getenv("ATC_EXTERNAL_URL"),
		"${BUILD_TEAM_NAME}":     os.Getenv("BUILD_TEAM_NAME"),
	}
	for k, v := range buildTokens {
		sourceString = strings.Replace(sourceString, k, v, -1)
	}
	return sourceString
}

func readSource(sourceRoot, sourcePath string) (string, error) {
	if !filepath.IsAbs(sourcePath) {
		sourcePath = filepath.Join(sourceRoot, sourcePath)
	}
	bytes, err := ioutil.ReadFile(sourcePath)
	return replaceTokens(string(bytes)), err
}

func fromTextOrFile(sourceRoot, text, filePath string) (string, error) {
	if text != "" {
		return replaceTokens(text), nil

	}
	if filePath != "" {
		return readSource(sourceRoot, filePath)
	}
	return "", nil
}

func sliceFromTextOrFile(sourceRoot, text, filePath string) ([]string, error) {
	var returnList []string
	if text != "" {
		listArray := strings.Split(text, ",")
		for _, item := range listArray {
			returnList = append(returnList, strings.TrimSpace(item))
		}
	}
	if filePath != "" {
		fileList, err := readSource(sourceRoot, filePath)
		if err != nil {
			return nil, errors.Wrapf(err, "Error reading file %s", filePath)
		}
		if len(fileList) > 0 {
			listArray := strings.Split(fileList, ",")
			for _, item := range listArray {
				returnList = append(returnList, strings.TrimSpace(item))
			}
		}
	}
	return returnList, nil
}
<|endoftext|>