<commit_before>// Make balanced rosters according to weighted criteria

package main

import (
	"fmt"
	"math"
	"math/rand"
	"os"
	"os/signal"
	"runtime"
	"sort"
	"syscall"
	"text/tabwriter"
	"time"

	"github.com/op/go-logging"
	"github.com/pkg/profile"

	"gopkg.in/alecthomas/kingpin.v2"
)

var newLog = logging.MustGetLogger("")

// Genetic algorithm constants
const (
	// Number of teams to break players into
	numTeams = 6
	// Percent of the time we will try to mutate. After each
	// mutation, we have a mutationChance percent chance of
	// mutating again.
	mutationChance = 5
	// We will make numSolutionsPerRun every run, and numParents carry
	// over into the next run to create the next batch of solutions.
	numSolutionsPerRun = 1000
	numParents         = 20
)

type Score float64
type Solution struct {
	players []Player
	score   Score
}

// Implement sort.Interface for []Solution, sorting based on score
type ByScore []Solution

func (a ByScore) Len() int {
	return len(a)
}
func (a ByScore) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}
func (a ByScore) Less(i, j int) bool {
	return a[i].score < a[j].score
}

type Team struct {
	players []Player
}

func splitIntoTeams(players []Player) []Team {
	teams := make([]Team, numTeams)
	for _, player := range players {
		teams[player.team].players = append(teams[player.team].players, player)
	}
	return teams
}

func randomizeTeams(players []Player) {
	for i, _ := range players {
		players[i].team = uint8(rand.Intn(numTeams))
	}
}

func maxNumberOfPlayersPerTeam(teams []Team) int {
	maxPlayers := 0
	for i := 0; i < math.MaxInt16; i++ {
		works := false
		for _, team := range teams {
			if len(team.players) >= maxPlayers {
				works = true
			}
		}
		if !works {
			break
		}
		maxPlayers += 1
	}
	return maxPlayers
}

func PrintTeams(solution Solution) {
	writer := new(tabwriter.Writer)
	writer.Init(os.Stdout, 0, 0, 0, ' ', 0)
	for _, filterFunc := range []PlayerFilter{IsMale, IsFemale} {
		// Print the rating for each team
		filteredPlayers := Filter(solution.players, filterFunc)
		sort.Sort(sort.Reverse(ByRating(filteredPlayers)))
		teams := splitIntoTeams(filteredPlayers)
		string := ""
		for _, team := range teams {
			string += fmt.Sprintf("|Average: %.02f\t", AverageRating(team))
		}
		string += "|"
		fmt.Fprintln(writer, string)

		string = ""
		for _, team := range teams {
			topPlayers := team.players
			if len(topPlayers) > 3 {
				topPlayers = team.players[:3]
			}
			string += fmt.Sprintf("|Top Average: %.02f\t", AverageRating(Team{topPlayers}))
		}
		string += "|"
		fmt.Fprintln(writer, string)

		// Print the players for each team
		numLoops := maxNumberOfPlayersPerTeam(teams)
		for i := 0; i < numLoops; i++ {
			string := ""
			for _, team := range teams {
				if len(team.players) > i {
					string += fmt.Sprintf("|%s\t", team.players[i].String())
				} else {
					string += "|\t"
				}
			}
			string += "|"
			fmt.Fprintln(writer, string)
		}
	}
	writer.Flush()
}

// Mutate the solution by moving random players to random teams, sometimes.
func mutate(players []Player) {
	for {
		// We have mutationChance of mutating. Otherwise, we break out of our loop
		if rand.Intn(100) > mutationChance {
			return
		}
		// Mutation! Move a random player to a random new team
		players[rand.Intn(len(players))].team = uint8(rand.Intn(numTeams))
	}
}

// Breed via combining the two given solutions, then randomly mutating.
func breed(solution1 Solution, solution2 Solution) Solution {
	// Create the new solution by taking crossover from both inputs
	newPlayers := make([]Player, len(solution1.players))
	for i, _ := range newPlayers {
		// Randomly take each player from solution1 or solution2
		if rand.Intn(100) < 50 {
			newPlayers[i] = solution1.players[i]
		} else {
			newPlayers[i] = solution2.players[i]
		}
	}

	// Mutate the new player list
	mutate(newPlayers)

	solutionScore, _ := ScoreSolution(newPlayers)
	return Solution{newPlayers, solutionScore}
}

type workerTask struct {
	parent1, parent2 Solution
}

func worker(tasks <-chan workerTask, results chan<- Solution) {
	for task := range tasks {
		results <- breed(task.parent1, task.parent2)
	}
}

// performRun creates a new solution list by breeding parents.
func performRun(
	parents []Solution, tasks chan<- workerTask, results <-chan Solution) []Solution {
	// Start jobs
	for i := 0; i < numSolutionsPerRun; i++ {
		tasks <- workerTask{
			parents[rand.Intn(len(parents))], parents[rand.Intn(len(parents))]}
	}

	// Retreive the results of our jobs
	solutions := make([]Solution, numSolutionsPerRun)
	for i := 0; i < numSolutionsPerRun; i++ {
		solutions[i] = <-results
	}
	return solutions
}

// parseCommandLine parses the user input
//
// Returns:
//  - a []Player of the players from the input file
//  - a bool which tells us whether or not we should be profiling
//  - the number of CPUs to use for goroutines, which is manipulated by "-d"
func parseCommandLine() ([]Player, bool, int) {
	filenamePointer := kingpin.Arg("players",
		"filename from which to get list of players").
		Required().String()
	baggagesPointer := kingpin.Arg("baggages",
		"filename from which to get list of baggages").
		Required().String()
	deterministicPointer := kingpin.Flag("deterministic",
		"makes our output deterministic by allowing the default rand.Seed").
		Short('d').Bool()
	runProfilingPointer := kingpin.Flag("profiling",
		"output profiling stats when true").Short('p').Bool()
	verbosePointer := kingpin.Flag("verbose",
		"verbose output").Short('v').Bool()
	kingpin.Parse()

	// Set up logging
	logging.SetBackend(logging.NewLogBackend(os.Stdout, "", 0))
	if *verbosePointer {
		logging.SetLevel(logging.DEBUG, "")
	} else {
		logging.SetLevel(logging.INFO, "")
	}

	// To run deterministically, we use the default seed and only one goroutine
	numWorkers := runtime.NumCPU()
	if !*deterministicPointer {
		rand.Seed(time.Now().UTC().UnixNano())
	} else {
		newLog.Info("Seeded deterministically")
		numWorkers = 1
	}

	players := ParsePlayers(*filenamePointer)
	ParseBaggages(*baggagesPointer, players)
	return players, *runProfilingPointer, numWorkers
}

func timeToClose(
	numRunsCompleted int, topScoreRunNumber int, doneSignal <-chan os.Signal) bool {
	// If we receive a done signal, exit
	select {
	case <-doneSignal:
		fmt.Println("Exit signal received")
		return true
	default:
	}
	return numRunsCompleted > topScoreRunNumber+10000
}

func main() {
	players, profilingOn, numWorkers := parseCommandLine()
	startTime := time.Now()
	if len(players) == 0 {
		panic("Could not find players")
	}

	// Start profiler
	if profilingOn {
		newLog.Info("Running profiler")
		defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
	}

	// Create random Parent solutions to start
	parentSolutions := make([]Solution, numParents)
	for i, _ := range parentSolutions {
		ourPlayers := make([]Player, len(players))
		copy(ourPlayers, players)
		randomizeTeams(ourPlayers)
		solutionScore, _ := ScoreSolution(ourPlayers)
		parentSolutions[i] = Solution{ourPlayers, solutionScore}
	}

	// Use the random starting solutions to determine the worst case for each of
	// our criteria
	PopulateWorstCases(parentSolutions)

	// Start our worker goroutines
	tasks := make(chan workerTask, numSolutionsPerRun)
	results := make(chan Solution, numSolutionsPerRun)
	for i := 0; i < numWorkers; i++ {
		go worker(tasks, results)
	}
	defer close(tasks)

	// Allow user to signal exit
	doneSignal := make(chan os.Signal, 1)
	signal.Notify(doneSignal, syscall.SIGINT)

	topScore := parentSolutions[0].score
	numRunsCompleted := 0
	topScoreRunNumber := 0
	for {
		// If we have a new best score, save and print it!
		if topScore != parentSolutions[0].score {
			topScore = parentSolutions[0].score
			topScoreRunNumber = numRunsCompleted
			if newLog.IsEnabledFor(logging.DEBUG) {
				newLog.Info("New top score! Run number %d. Score: %.02f",
					numRunsCompleted, topScore)
				PrintTeams(parentSolutions[0])
				PrintSolutionScoring(parentSolutions[0])
			}
		}

		// Create new solutions, and save the best ones
		newSolutions := performRun(parentSolutions, tasks, results)
		sort.Sort(ByScore(newSolutions))
		for i, _ := range parentSolutions {
			parentSolutions[i] = newSolutions[i]
		}
		numRunsCompleted += 1
		if timeToClose(numRunsCompleted, topScoreRunNumber, doneSignal) {
			break
		}
	}

	// Display our solution to the user
	topSolution := parentSolutions[0]
	fmt.Println()
	PrintTeams(topSolution)
	PrintSolutionScoring(topSolution)
	newLog.Debug("Program runtime: %.02fs", time.Since(startTime).Seconds())
}
<commit_msg>add tournament selection<commit_after>// Make balanced rosters according to weighted criteria

package main

import (
	"fmt"
	"math"
	"math/rand"
	"os"
	"os/signal"
	"runtime"
	"sort"
	"syscall"
	"text/tabwriter"
	"time"

	"github.com/op/go-logging"
	"github.com/pkg/profile"

	"gopkg.in/alecthomas/kingpin.v2"
)

var newLog = logging.MustGetLogger("")

// Genetic algorithm constants
const (
	// Number of teams to break players into
	numTeams = 6
	// Percent of the time we will try to mutate. After each
	// mutation, we have a mutationChance percent chance of
	// mutating again.
	mutationChance = 5
	// We will make numSolutionsPerRun every run, and numParents carry
	// over into the next run to create the next batch of solutions.
	numSolutionsPerRun = 1000
	numParents         = 20
)

type Score float64
type Solution struct {
	players []Player
	score   Score
}

// Implement sort.Interface for []Solution, sorting based on score
type ByScore []Solution

func (a ByScore) Len() int {
	return len(a)
}
func (a ByScore) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}
func (a ByScore) Less(i, j int) bool {
	return a[i].score < a[j].score
}

type Team struct {
	players []Player
}

func splitIntoTeams(players []Player) []Team {
	teams := make([]Team, numTeams)
	for _, player := range players {
		teams[player.team].players = append(teams[player.team].players, player)
	}
	return teams
}

func randomizeTeams(players []Player) {
	for i, _ := range players {
		players[i].team = uint8(rand.Intn(numTeams))
	}
}

func maxNumberOfPlayersPerTeam(teams []Team) int {
	maxPlayers := 0
	for i := 0; i < math.MaxInt16; i++ {
		works := false
		for _, team := range teams {
			if len(team.players) >= maxPlayers {
				works = true
			}
		}
		if !works {
			break
		}
		maxPlayers += 1
	}
	return maxPlayers
}

func PrintTeams(solution Solution) {
	writer := new(tabwriter.Writer)
	writer.Init(os.Stdout, 0, 0, 0, ' ', 0)
	for _, filterFunc := range []PlayerFilter{IsMale, IsFemale} {
		// Print the rating for each team
		filteredPlayers := Filter(solution.players, filterFunc)
		sort.Sort(sort.Reverse(ByRating(filteredPlayers)))
		teams := splitIntoTeams(filteredPlayers)
		string := ""
		for _, team := range teams {
			string += fmt.Sprintf("|Average: %.02f\t", AverageRating(team))
		}
		string += "|"
		fmt.Fprintln(writer, string)

		string = ""
		for _, team := range teams {
			topPlayers := team.players
			if len(topPlayers) > 3 {
				topPlayers = team.players[:3]
			}
			string += fmt.Sprintf("|Top Average: %.02f\t", AverageRating(Team{topPlayers}))
		}
		string += "|"
		fmt.Fprintln(writer, string)

		// Print the players for each team
		numLoops := maxNumberOfPlayersPerTeam(teams)
		for i := 0; i < numLoops; i++ {
			string := ""
			for _, team := range teams {
				if len(team.players) > i {
					string += fmt.Sprintf("|%s\t", team.players[i].String())
				} else {
					string += "|\t"
				}
			}
			string += "|"
			fmt.Fprintln(writer, string)
		}
	}
	writer.Flush()
}

// Mutate the solution by moving random players to random teams, sometimes.
func mutate(players []Player) {
	for {
		// We have mutationChance of mutating. Otherwise, we break out of our loop
		if rand.Intn(100) > mutationChance {
			return
		}
		// Mutation! Move a random player to a random new team
		players[rand.Intn(len(players))].team = uint8(rand.Intn(numTeams))
	}
}

// Breed via combining the two given solutions, then randomly mutating.
func breed(solution1 Solution, solution2 Solution) Solution {
	// Create the new solution by taking crossover from both inputs
	newPlayers := make([]Player, len(solution1.players))
	for i, _ := range newPlayers {
		// Randomly take each player from solution1 or solution2
		if rand.Intn(100) < 50 {
			newPlayers[i] = solution1.players[i]
		} else {
			newPlayers[i] = solution2.players[i]
		}
	}

	// Mutate the new player list
	mutate(newPlayers)

	solutionScore, _ := ScoreSolution(newPlayers)
	return Solution{newPlayers, solutionScore}
}

type workerTask struct {
	parent1, parent2 Solution
}

func worker(tasks <-chan workerTask, results chan<- Solution) {
	for task := range tasks {
		results <- breed(task.parent1, task.parent2)
	}
}

func tournamentSelection(parents []Solution) Solution {
	// Randomly select parents for tournament
	numParentsInTournament := 5
	tournamentParents := make([]Solution, numParentsInTournament)
	for i := range tournamentParents {
		// Random parent
		tournamentParents[i] = parents[rand.Intn(len(parents))]
	}

	// Choose our two parents for breeding from tournament in weighted fashion
	const p = .5
	r := rand.Float64()
	for i := range tournamentParents {
		if p*math.Pow((1.0-p), float64(i+1)) < r {
			return tournamentParents[i]
		}
	}
	return parents[0]
}

// performRun creates a new solution list by breeding parents.
func performRun(
	parents []Solution, tasks chan<- workerTask, results <-chan Solution) []Solution {
	// Start jobs
	for i := 0; i < numSolutionsPerRun; i++ {
		tasks <- workerTask{tournamentSelection(parents), tournamentSelection(parents)}
	}

	// Retreive the results of our jobs
	solutions := make([]Solution, numSolutionsPerRun)
	for i := 0; i < numSolutionsPerRun; i++ {
		solutions[i] = <-results
	}
	return solutions
}

// parseCommandLine parses the user input
//
// Returns:
//  - a []Player of the players from the input file
//  - a bool which tells us whether or not we should be profiling
//  - the number of CPUs to use for goroutines, which is manipulated by "-d"
func parseCommandLine() ([]Player, bool, int) {
	filenamePointer := kingpin.Arg("players",
		"filename from which to get list of players").
		Required().String()
	baggagesPointer := kingpin.Arg("baggages",
		"filename from which to get list of baggages").
		Required().String()
	deterministicPointer := kingpin.Flag("deterministic",
		"makes our output deterministic by allowing the default rand.Seed").
		Short('d').Bool()
	runProfilingPointer := kingpin.Flag("profiling",
		"output profiling stats when true").Short('p').Bool()
	verbosePointer := kingpin.Flag("verbose",
		"verbose output").Short('v').Bool()
	kingpin.Parse()

	// Set up logging
	logging.SetBackend(logging.NewLogBackend(os.Stdout, "", 0))
	if *verbosePointer {
		logging.SetLevel(logging.DEBUG, "")
	} else {
		logging.SetLevel(logging.INFO, "")
	}

	// To run deterministically, we use the default seed and only one goroutine
	numWorkers := runtime.NumCPU()
	if !*deterministicPointer {
		rand.Seed(time.Now().UTC().UnixNano())
	} else {
		newLog.Info("Seeded deterministically")
		numWorkers = 1
	}

	players := ParsePlayers(*filenamePointer)
	ParseBaggages(*baggagesPointer, players)
	return players, *runProfilingPointer, numWorkers
}

func timeToClose(
	numRunsCompleted int, topScoreRunNumber int, doneSignal <-chan os.Signal) bool {
	// If we receive a done signal, exit
	select {
	case <-doneSignal:
		fmt.Println("Exit signal received")
		return true
	default:
	}
	return numRunsCompleted > topScoreRunNumber+10000
}

func main() {
	players, profilingOn, numWorkers := parseCommandLine()
	startTime := time.Now()
	if len(players) == 0 {
		panic("Could not find players")
	}

	// Start profiler
	if profilingOn {
		newLog.Info("Running profiler")
		defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
	}

	// Create random Parent solutions to start
	parentSolutions := make([]Solution, numParents)
	for i, _ := range parentSolutions {
		ourPlayers := make([]Player, len(players))
		copy(ourPlayers, players)
		randomizeTeams(ourPlayers)
		solutionScore, _ := ScoreSolution(ourPlayers)
		parentSolutions[i] = Solution{ourPlayers, solutionScore}
	}

	// Use the random starting solutions to determine the worst case for each of
	// our criteria
	PopulateWorstCases(parentSolutions)

	// Start our worker goroutines
	tasks := make(chan workerTask, numSolutionsPerRun)
	results := make(chan Solution, numSolutionsPerRun)
	for i := 0; i < numWorkers; i++ {
		go worker(tasks, results)
	}
	defer close(tasks)

	// Allow user to signal exit
	doneSignal := make(chan os.Signal, 1)
	signal.Notify(doneSignal, syscall.SIGINT)

	topScore := parentSolutions[0].score
	numRunsCompleted := 0
	topScoreRunNumber := 0
	for {
		// If we have a new best score, save and print it!
		if topScore != parentSolutions[0].score {
			topScore = parentSolutions[0].score
			topScoreRunNumber = numRunsCompleted
			if newLog.IsEnabledFor(logging.DEBUG) {
				newLog.Info("New top score! Run number %d. Score: %.02f",
					numRunsCompleted, topScore)
				PrintTeams(parentSolutions[0])
				PrintSolutionScoring(parentSolutions[0])
			}
		}

		// Create new solutions, and save the best ones
		newSolutions := performRun(parentSolutions, tasks, results)
		sort.Sort(ByScore(newSolutions))
		for i, _ := range parentSolutions {
			parentSolutions[i] = newSolutions[i]
		}
		numRunsCompleted += 1
		if timeToClose(numRunsCompleted, topScoreRunNumber, doneSignal) {
			break
		}
	}

	// Display our solution to the user
	topSolution := parentSolutions[0]
	fmt.Println()
	PrintTeams(topSolution)
	PrintSolutionScoring(topSolution)
	newLog.Debug("Program runtime: %.02fs", time.Since(startTime).Seconds())
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"net/url"
	"strings"
	"time"
)

type Rate struct {
	Id   string  `json:"id"`
	Name string  `json:"Name"`
	Rate float64 `json:",string"`
	Date string  `json:"Date"`
	Time string  `json:"Time"`
	Ask  float64 `json:",string"`
	Bid  float64 `json:",string"`
}

type YahooJSONResponseInfo struct {
	Count   int       `json:"count"`
	Created time.Time `json:"created"`
	Lang    string    `json:"lang"`
}

type YahooJSONResponse struct {
	Query struct {
		YahooJSONResponseInfo
		Results struct {
			Rate []Rate `json:"rate"`
		}
	}
}

const (
	YAHOO_YQL_URL      = "http://query.yahooapis.com/v1/public/yql"
	YAHOO_DATABASE     = "store://datatables.org/alltableswithkeys"
	DEFAULT_CURRENCIES = "USD,AUD,EUR,CNY"
)

var (
	CurrencyStore             YahooJSONResponse
	ErrCurrencyDataNotFetched = errors.New("Yahoo currency data has not been fetched yet.")
	ErrCurrencyNotFound       = errors.New("Unable to find specified currency.")
	ErrQueryingYahoo          = errors.New("Unable to query Yahoo currency values.")
)

func RetrieveConfigCurrencyPairs(config Config) error {
	currencyPairs := ""
	for _, exchange := range config.Exchanges {
		if exchange.Enabled {
			var result []string
			if StringContains(exchange.BaseCurrencies, ",") {
				result = SplitStrings(exchange.BaseCurrencies, ",")
			} else {
				if StringContains(DEFAULT_CURRENCIES, exchange.BaseCurrencies) {
					result = SplitStrings(DEFAULT_CURRENCIES, ",")
				} else {
					result = SplitStrings(exchange.BaseCurrencies+","+DEFAULT_CURRENCIES, ",")
				}
			}
			for _, s := range result {
				if !StringContains(currencyPairs, s) {
					currencyPairs += s + ","
				}
			}
		}
	}
	currencyPairs = currencyPairs[0 : len(currencyPairs)-1]
	err := QueryYahooCurrencyValues(currencyPairs)

	if err != nil {
		return ErrQueryingYahoo
	}

	log.Println("Fetched currency value data.")
	return nil
}

func MakecurrencyPairs(supportedCurrencies string) string {
	currencies := SplitStrings(supportedCurrencies, ",")
	pairs := ""
	count := len(currencies)
	for i := 0; i < count; i++ {
		currency := currencies[i]
		for j := 0; j < count; j++ {
			if currency != currencies[j] {
				pairs += currency + currencies[j] + ","
			}
		}
	}
	return pairs[0 : len(pairs)-1]
}

func ConvertCurrency(amount float64, from, to string) (float64, error) {
	if CurrencyStore.Query.YahooJSONResponseInfo.Count == 0 {
		return 0, ErrCurrencyDataNotFetched
	}

	currency := strings.ToUpper(from + to)
	for i := 0; i < CurrencyStore.Query.YahooJSONResponseInfo.Count; i++ {
		if CurrencyStore.Query.Results.Rate[i].Id == currency {
			return amount * CurrencyStore.Query.Results.Rate[i].Rate, nil
		}
	}
	return 0, ErrCurrencyNotFound
}

func QueryYahooCurrencyValues(currencies string) error {
	currencyPairs := MakecurrencyPairs(currencies)
	log.Printf("Supported currency pairs: %s\n", currencyPairs)

	values := url.Values{}
	values.Set("q", fmt.Sprintf("SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")", currencyPairs))
	values.Set("format", "json")
	values.Set("env", YAHOO_DATABASE)

	headers := make(map[string]string)
	headers["Content-Type"] = "application/x-www-form-urlencoded"

	resp, err := SendHTTPRequest("POST", YAHOO_YQL_URL, headers, strings.NewReader(values.Encode()))

	if err != nil {
		return err
	}

	err = JSONDecode([]byte(resp), &CurrencyStore)

	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Added IsFiatCurrency function.<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"net/url"
	"strings"
	"time"
)

type Rate struct {
	Id   string  `json:"id"`
	Name string  `json:"Name"`
	Rate float64 `json:",string"`
	Date string  `json:"Date"`
	Time string  `json:"Time"`
	Ask  float64 `json:",string"`
	Bid  float64 `json:",string"`
}

type YahooJSONResponseInfo struct {
	Count   int       `json:"count"`
	Created time.Time `json:"created"`
	Lang    string    `json:"lang"`
}

type YahooJSONResponse struct {
	Query struct {
		YahooJSONResponseInfo
		Results struct {
			Rate []Rate `json:"rate"`
		}
	}
}

const (
	YAHOO_YQL_URL      = "http://query.yahooapis.com/v1/public/yql"
	YAHOO_DATABASE     = "store://datatables.org/alltableswithkeys"
	DEFAULT_CURRENCIES = "USD,AUD,EUR,CNY"
)

var (
	CurrencyStore             YahooJSONResponse
	BaseCurrencies            string
	ErrCurrencyDataNotFetched = errors.New("Yahoo currency data has not been fetched yet.")
	ErrCurrencyNotFound       = errors.New("Unable to find specified currency.")
	ErrQueryingYahoo          = errors.New("Unable to query Yahoo currency values.")
)

func IsFiatCurrency(currency string) bool {
	if StringContains(BaseCurrencies, StringToUpper(currency)) {
		return true
	}
	return false
}

func RetrieveConfigCurrencyPairs(config Config) error {
	currencyPairs := ""
	for _, exchange := range config.Exchanges {
		if exchange.Enabled {
			var result []string
			if StringContains(exchange.BaseCurrencies, ",") {
				result = SplitStrings(exchange.BaseCurrencies, ",")
			} else {
				if StringContains(DEFAULT_CURRENCIES, exchange.BaseCurrencies) {
					result = SplitStrings(DEFAULT_CURRENCIES, ",")
				} else {
					result = SplitStrings(exchange.BaseCurrencies+","+DEFAULT_CURRENCIES, ",")
				}
			}
			for _, s := range result {
				if !StringContains(currencyPairs, s) {
					currencyPairs += s + ","
				}
			}
		}
	}
	currencyPairs = currencyPairs[0 : len(currencyPairs)-1]
	BaseCurrencies = currencyPairs
	err := QueryYahooCurrencyValues(currencyPairs)

	if err != nil {
		return ErrQueryingYahoo
	}

	log.Println("Fetched currency value data.")
	return nil
}

func MakecurrencyPairs(supportedCurrencies string) string {
	currencies := SplitStrings(supportedCurrencies, ",")
	pairs := ""
	count := len(currencies)
	for i := 0; i < count; i++ {
		currency := currencies[i]
		for j := 0; j < count; j++ {
			if currency != currencies[j] {
				pairs += currency + currencies[j] + ","
			}
		}
	}
	return pairs[0 : len(pairs)-1]
}

func ConvertCurrency(amount float64, from, to string) (float64, error) {
	if CurrencyStore.Query.YahooJSONResponseInfo.Count == 0 {
		return 0, ErrCurrencyDataNotFetched
	}

	currency := strings.ToUpper(from + to)
	for i := 0; i < CurrencyStore.Query.YahooJSONResponseInfo.Count; i++ {
		if CurrencyStore.Query.Results.Rate[i].Id == currency {
			return amount * CurrencyStore.Query.Results.Rate[i].Rate, nil
		}
	}
	return 0, ErrCurrencyNotFound
}

func QueryYahooCurrencyValues(currencies string) error {
	currencyPairs := MakecurrencyPairs(currencies)
	log.Printf("Supported currency pairs: %s\n", currencyPairs)

	values := url.Values{}
	values.Set("q", fmt.Sprintf("SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")", currencyPairs))
	values.Set("format", "json")
	values.Set("env", YAHOO_DATABASE)

	headers := make(map[string]string)
	headers["Content-Type"] = "application/x-www-form-urlencoded"

	resp, err := SendHTTPRequest("POST", YAHOO_YQL_URL, headers, strings.NewReader(values.Encode()))

	if err != nil {
		return err
	}

	err = JSONDecode([]byte(resp), &CurrencyStore)

	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package tls_test

import (
	"testing"

	"crypto/tls"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"strings"
	"time"

	"github.com/fastly/go-utils/server"
	ttls "github.com/fastly/go-utils/tls"
)

func NewMockServer() *server.Server {
	addr := ":0" // let kernel assign an unused port
	s, err := server.NewSingleServer(&addr)
	if err != nil {
		log.Panic(err)
		return nil
	}
	return s
}

func TestTLSCacheToProxy(t *testing.T) {
	check(t, "test-cache-client", "test-proxy-server", true)
}

func TestTLSProxyToSyslogd(t *testing.T) {
	check(t, "test-proxy-client", "test-syslogd-server", true)
}

// reject connections from unknown certs
func TestTLSUnknownToProxy(t *testing.T) {
	check(t, "test-unknown-client", "test-proxy-server", false)
}

func TestTLSProxyToUnknown(t *testing.T) {
	check(t, "test-proxy-client", "test-unknown-server", false)
}

var badCipherSuites = map[uint16]bool{
	tls.TLS_RSA_WITH_RC4_128_SHA:            true,
	tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA:       true,
	tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:    true,
	tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA:      true,
	tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: true,
}

func check(t *testing.T, clientName, serverName string, shouldPass bool) {
	clientConfig, err := ttls.ConfigureClient(clientName, "test-tls-ca")
	if err != nil {
		t.Errorf("Bad client key '%s': %s", clientName, err)
		return
	}
	serverConfig, err := ttls.ConfigureServer(serverName, "test-tls-ca")
	if err != nil {
		t.Errorf("Bad server key '%s': %s", serverName, err)
		return
	}

	if clientConfig.MinVersion != tls.VersionTLS12 {
		t.Errorf("expected TLS minimum version of %v, got %v", clientConfig.MinVersion, tls.VersionTLS12)
	}
	if serverConfig.MinVersion != tls.VersionTLS12 {
		t.Errorf("expected TLS minimum version of %v, got %v", serverConfig.MinVersion, tls.VersionTLS12)
	}

	for _, suite := range serverConfig.CipherSuites {
		if badCipherSuites[suite] {
			t.Errorf("server allows blacklisted cipher suite %v", suite)
		}
	}

	server := NewMockServer()
	server.SetListener(tls.NewListener(server.Listener(), serverConfig))
	listener := server.Listener()

	testData := strings.Repeat("x", 1<<16)

	go func() {
		server.SignalReady()

		conn, err := listener.Accept()
		if err == nil {
			defer conn.Close()
			conn.SetDeadline(time.Now().Add(time.Second))
			data, err := ioutil.ReadAll(conn)
			if err == nil && string(data) != testData {
				err = fmt.Errorf("Server read incorrect data; got '%s', expected '%s'", string(data), testData)
			}

			if err != nil && shouldPass {
				t.Errorf("Server read error: %v", err)
			} else if err == nil && !shouldPass {
				t.Errorf("Expected server read error: %v", err)
			}
		} else {
			t.Errorf("Listener error: %v", err)
		}

		server.WaitForShutdown()
		server.SignalFinish()
	}()

	server.WaitForReady()

	addr := listener.Addr().String()
	conn, err := tls.Dial("tcp", addr, clientConfig)
	if err != nil && shouldPass {
		t.Errorf("Client connection error: %v", err)
		return
	} else if err == nil && !shouldPass {
		t.Errorf("Expected client connection error: %v", err)
		return
	} else if err != nil {
		return
	} // else err == nil && shouldPass
	conn.SetDeadline(time.Now().Add(time.Second))

	n, err := io.WriteString(conn, testData)
	if err == nil && n < len(testData) {
		err = fmt.Errorf("Client incomplete write: expected %d bytes, got %d", len(testData), n)
	}

	if err != nil && shouldPass {
		t.Errorf("Client write error: %v", err)
	} else if err == nil && !shouldPass {
		t.Errorf("Expected client write error: %v", err)
	}

	conn.Close()
	server.Shutdown()
}
<commit_msg>explicitly listen on an IPv4 localhost address<commit_after>package tls_test

import (
	"testing"

	"crypto/tls"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"strings"
	"time"

	"github.com/fastly/go-utils/server"
	ttls "github.com/fastly/go-utils/tls"
)

func NewMockServer() *server.Server {
	addr := "127.0.0.1:0" // let kernel assign an unused port
	s, err := server.NewSingleServer(&addr)
	if err != nil {
		log.Panic(err)
		return nil
	}
	return s
}

func TestTLSCacheToProxy(t *testing.T) {
	check(t, "test-cache-client", "test-proxy-server", true)
}

func TestTLSProxyToSyslogd(t *testing.T) {
	check(t, "test-proxy-client", "test-syslogd-server", true)
}

// reject connections from unknown certs
func TestTLSUnknownToProxy(t *testing.T) {
	check(t, "test-unknown-client", "test-proxy-server", false)
}

func TestTLSProxyToUnknown(t *testing.T) {
	check(t, "test-proxy-client", "test-unknown-server", false)
}

var badCipherSuites = map[uint16]bool{
	tls.TLS_RSA_WITH_RC4_128_SHA:            true,
	tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA:       true,
	tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:    true,
	tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA:      true,
	tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: true,
}

func check(t *testing.T, clientName, serverName string, shouldPass bool) {
	clientConfig, err := ttls.ConfigureClient(clientName, "test-tls-ca")
	if err != nil {
		t.Errorf("Bad client key '%s': %s", clientName, err)
		return
	}
	serverConfig, err := ttls.ConfigureServer(serverName, "test-tls-ca")
	if err != nil {
		t.Errorf("Bad server key '%s': %s", serverName, err)
		return
	}

	if clientConfig.MinVersion != tls.VersionTLS12 {
		t.Errorf("expected TLS minimum version of %v, got %v", clientConfig.MinVersion, tls.VersionTLS12)
	}
	if serverConfig.MinVersion != tls.VersionTLS12 {
		t.Errorf("expected TLS minimum version of %v, got %v", serverConfig.MinVersion, tls.VersionTLS12)
	}

	for _, suite := range serverConfig.CipherSuites {
		if badCipherSuites[suite] {
			t.Errorf("server allows blacklisted cipher suite %v", suite)
		}
	}

	server := NewMockServer()
	server.SetListener(tls.NewListener(server.Listener(), serverConfig))
	listener := server.Listener()

	testData := strings.Repeat("x", 1<<16)

	go func() {
		server.SignalReady()

		conn, err := listener.Accept()
		if err == nil {
			defer conn.Close()
			conn.SetDeadline(time.Now().Add(time.Second))
			data, err := ioutil.ReadAll(conn)
			if err == nil && string(data) != testData {
				err = fmt.Errorf("Server read incorrect data; got '%s', expected '%s'", string(data), testData)
			}

			if err != nil && shouldPass {
				t.Errorf("Server read error: %v", err)
			} else if err == nil && !shouldPass {
				t.Errorf("Expected server read error: %v", err)
			}
		} else {
			t.Errorf("Listener error: %v", err)
		}

		server.WaitForShutdown()
		server.SignalFinish()
	}()

	server.WaitForReady()

	addr := listener.Addr().String()
	conn, err := tls.Dial("tcp", addr, clientConfig)
	if err != nil && shouldPass {
		t.Errorf("Client connection error: %v", err)
		return
	} else if err == nil && !shouldPass {
		t.Errorf("Expected client connection error: %v", err)
		return
	} else if err != nil {
		return
	} // else err == nil && shouldPass
	conn.SetDeadline(time.Now().Add(time.Second))

	n, err := io.WriteString(conn, testData)
	if err == nil && n < len(testData) {
		err = fmt.Errorf("Client incomplete write: expected %d bytes, got %d", len(testData), n)
	}

	if err != nil && shouldPass {
		t.Errorf("Client write error: %v", err)
	} else if err == nil && !shouldPass {
		t.Errorf("Expected client write error: %v", err)
	}

	conn.Close()
	server.Shutdown()
}
<|endoftext|>


<commit_before>// Copyright 2015 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 ratelimit

import (
	"fmt"
	"math"
	"time"
)

// A measurement of the amount of real time since some fixed epoch.
//
// TokenBucket doesn't care about calendar time, time of day, etc.
// Unfortunately time.Time takes these things into account, and in particular
// time.Now() is not monotonic -- it may jump arbitrarily far into the future
// or past when the system's wall time is changed.
//
// Instead we reckon in terms of a monotonic measurement of time elapsed since
// the bucket was initialized, and leave it up to the user to provide this. See
// SystemTimeTokenBucket for a convenience in doing so.
type MonotonicTime time.Duration

// A bucket of tokens that refills at a specific rate up to a particular
// capacity. Users can remove tokens in sizes up to that capacity, can are told
// how long they should wait before proceeding.
//
// If users cooperate by waiting to take whatever action they are rate limiting
// as told by the token bucket, the overall action rate will be limited to the
// token bucket's fill rate.
//
// Not safe for concurrent access; requires external synchronization.
//
// Cf. http://en.wikipedia.org/wiki/Token_bucket
type TokenBucket interface {
	CheckInvariants()

	// Return the maximum number of tokens that the bucket can hold.
	Capacity() (c uint64)

	// Remove the specified number of tokens from the token bucket at the given
	// time. The user should wait until sleepUntil before proceeding in order to
	// obey the rate limit.
	//
	// REQUIRES: tokens <= Capacity()
	Remove(
		now MonotonicTime,
		tokens uint64) (sleepUntil MonotonicTime)
}

// Choose a token bucket capacity that ensures that the action gated by the
// token bucket will be limited to within a few percent of `rateHz * window`
// for any window of the given size.
//
// This is not be possible for all rates and windows. In that case, an error
// will be returned.
func ChooseTokenBucketCapacity(
	rateHz float64,
	window time.Duration) (capacity uint64, err error) {
	// Check that the input is reasonable.
	if rateHz <= 0 || math.IsInf(rateHz, 0) {
		err = fmt.Errorf("Illegal rate: %f", rateHz)
		return
	}

	if window <= 0 {
		err = fmt.Errorf("Illegal window: %v", window)
		return
	}

	// We cannot help but allow the rate to exceed the configured maximum by some
	// factor in an arbitrary window, no matter how small we scale the max
	// accumulated credit -- the bucket may be full at the start of the window,
	// be immediately exhausted, then be repeatedly exhausted just before filling
	// throughout the window.
	//
	// For example: let the window W = 10 seconds, and the bandwidth B = 20 MiB/s.
	// Set the max accumulated credit C = W*B/2 = 100 MiB. Then this
	// sequence of events is allowed:
	//
	//  *  T=0:        Allow through 100 MiB.
	//  *  T=4.999999: Allow through nearly 100 MiB.
	//  *  T=9.999999: Allow through nearly 100 MiB.
	//
	// Above we allow through nearly 300 MiB, exceeding the allowed bytes for the
	// window by nearly 50%. Note however that this trend cannot continue into
	// the next window, so this must be a transient spike.
	//
	// In general if we set C <= W*B/N, then we're off by no more than a factor
	// of (N+1)/N within any window of size W.
	//
	// Choose a reasonable N.
	const N = 50 // At most 2% error

	w := float64(window) / float64(time.Second)
	capacityFloat := math.Floor(w * rateHz / N)
	if !(capacityFloat > 0 && capacityFloat < float64(math.MaxUint64)) {
		err = fmt.Errorf(
			"Can't use a token bucket to limit to %f Hz over a window of %v "+
				"(result is a capacity of %f)",
			rateHz,
			window,
			capacityFloat)

		return
	}

	capacity = uint64(capacityFloat)
	if capacity == 0 {
		panic(fmt.Sprintf(
			"Calculated a zero capacity for inputs %f, %v. Float version: %f",
			rateHz,
			window,
			capacityFloat))
	}

	return
}

// Create a token bucket that fills at the given rate in tokens per second, up
// to the given capacity. ChooseTokenBucketCapacity may help you decide on a
// capacity.
//
// REQUIRES: rateHz > 0
// REQUIRES: capacity > 0
func NewTokenBucket(
	rateHz float64,
	capacity uint64) (tb TokenBucket) {
	tb = &tokenBucket{
		rateHz:   rateHz,
		capacity: capacity,
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////

type tokenBucket struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	rateHz   float64
	capacity uint64

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The time that we last updated the bucket's credit. Only moves forward.
	creditTime MonotonicTime

	// The number of credits that were available at creditTime.
	//
	// INVARIANT: credit <= float64(capacity)
	credit float64
}

func (tb *tokenBucket) CheckInvariants() {
	// INVARIANT: credit <= float64(capacity)
	if !(tb.credit <= float64(tb.capacity)) {
		panic(fmt.Sprintf(
			"Illegal credit: %f, capacity: %d",
			tb.credit,
			tb.capacity))
	}
}

func (tb *tokenBucket) Capacity() (c uint64) {
	c = tb.capacity
	return
}

func (tb *tokenBucket) Remove(
	now MonotonicTime,
	tokens uint64) (sleepUntil MonotonicTime) {
	if tokens > tb.capacity {
		panic(fmt.Sprintf(
			"Token count %d out of range; capacity is %d",
			tokens,
			tb.capacity))
	}

	// First play the clock forward until now, crediting any tokens that have
	// accumulated in the meantime, up to the bucket's capacity.
	if tb.creditTime < now {
		diff := now - tb.creditTime

		// Don't forget to cap at the capacity.
		tb.credit += tb.rateHz * float64(diff) / float64(time.Second)
		if !(tb.credit <= float64(tb.capacity)) {
			tb.credit = float64(tb.capacity)
		}

		tb.creditTime = now
	}

	// Deduct the requested tokens. The user will need to wait until the credit
	// makes it back to zero, which is when it would have otherwise made it to
	// `tokens`.
	tb.credit -= float64(tokens)

	sleepUntil = tb.creditTime
	if tb.credit < 0 {
		seconds := -tb.credit / tb.rateHz
		sleepUntil = tb.creditTime + MonotonicTime(seconds*float64(time.Second))
	}

	return
}
<commit_msg>Fixed a potential panic in ChooseTokenBucketCapacity.<commit_after>// Copyright 2015 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 ratelimit

import (
	"fmt"
	"math"
	"time"
)

// A measurement of the amount of real time since some fixed epoch.
//
// TokenBucket doesn't care about calendar time, time of day, etc.
// Unfortunately time.Time takes these things into account, and in particular
// time.Now() is not monotonic -- it may jump arbitrarily far into the future
// or past when the system's wall time is changed.
//
// Instead we reckon in terms of a monotonic measurement of time elapsed since
// the bucket was initialized, and leave it up to the user to provide this. See
// SystemTimeTokenBucket for a convenience in doing so.
type MonotonicTime time.Duration

// A bucket of tokens that refills at a specific rate up to a particular
// capacity. Users can remove tokens in sizes up to that capacity, can are told
// how long they should wait before proceeding.
//
// If users cooperate by waiting to take whatever action they are rate limiting
// as told by the token bucket, the overall action rate will be limited to the
// token bucket's fill rate.
//
// Not safe for concurrent access; requires external synchronization.
//
// Cf. http://en.wikipedia.org/wiki/Token_bucket
type TokenBucket interface {
	CheckInvariants()

	// Return the maximum number of tokens that the bucket can hold.
	Capacity() (c uint64)

	// Remove the specified number of tokens from the token bucket at the given
	// time. The user should wait until sleepUntil before proceeding in order to
	// obey the rate limit.
	//
	// REQUIRES: tokens <= Capacity()
	Remove(
		now MonotonicTime,
		tokens uint64) (sleepUntil MonotonicTime)
}

// Choose a token bucket capacity that ensures that the action gated by the
// token bucket will be limited to within a few percent of `rateHz * window`
// for any window of the given size.
//
// This is not be possible for all rates and windows. In that case, an error
// will be returned.
func ChooseTokenBucketCapacity(
	rateHz float64,
	window time.Duration) (capacity uint64, err error) {
	// Check that the input is reasonable.
	if rateHz <= 0 || math.IsInf(rateHz, 0) {
		err = fmt.Errorf("Illegal rate: %f", rateHz)
		return
	}

	if window <= 0 {
		err = fmt.Errorf("Illegal window: %v", window)
		return
	}

	// We cannot help but allow the rate to exceed the configured maximum by some
	// factor in an arbitrary window, no matter how small we scale the max
	// accumulated credit -- the bucket may be full at the start of the window,
	// be immediately exhausted, then be repeatedly exhausted just before filling
	// throughout the window.
	//
	// For example: let the window W = 10 seconds, and the bandwidth B = 20 MiB/s.
	// Set the max accumulated credit C = W*B/2 = 100 MiB. Then this
	// sequence of events is allowed:
	//
	//  *  T=0:        Allow through 100 MiB.
	//  *  T=4.999999: Allow through nearly 100 MiB.
	//  *  T=9.999999: Allow through nearly 100 MiB.
	//
	// Above we allow through nearly 300 MiB, exceeding the allowed bytes for the
	// window by nearly 50%. Note however that this trend cannot continue into
	// the next window, so this must be a transient spike.
	//
	// In general if we set C <= W*B/N, then we're off by no more than a factor
	// of (N+1)/N within any window of size W.
	//
	// Choose a reasonable N.
	const N = 50 // At most 2% error

	w := float64(window) / float64(time.Second)
	capacityFloat := math.Floor(w * rateHz / N)
	if !(capacityFloat >= 1 && capacityFloat < float64(math.MaxUint64)) {
		err = fmt.Errorf(
			"Can't use a token bucket to limit to %f Hz over a window of %v "+
				"(result is a capacity of %f)",
			rateHz,
			window,
			capacityFloat)

		return
	}

	capacity = uint64(capacityFloat)
	if capacity == 0 {
		panic(fmt.Sprintf(
			"Calculated a zero capacity for inputs %f, %v. Float version: %f",
			rateHz,
			window,
			capacityFloat))
	}

	return
}

// Create a token bucket that fills at the given rate in tokens per second, up
// to the given capacity. ChooseTokenBucketCapacity may help you decide on a
// capacity.
//
// REQUIRES: rateHz > 0
// REQUIRES: capacity > 0
func NewTokenBucket(
	rateHz float64,
	capacity uint64) (tb TokenBucket) {
	tb = &tokenBucket{
		rateHz:   rateHz,
		capacity: capacity,
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////

type tokenBucket struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	rateHz   float64
	capacity uint64

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The time that we last updated the bucket's credit. Only moves forward.
	creditTime MonotonicTime

	// The number of credits that were available at creditTime.
	//
	// INVARIANT: credit <= float64(capacity)
	credit float64
}

func (tb *tokenBucket) CheckInvariants() {
	// INVARIANT: credit <= float64(capacity)
	if !(tb.credit <= float64(tb.capacity)) {
		panic(fmt.Sprintf(
			"Illegal credit: %f, capacity: %d",
			tb.credit,
			tb.capacity))
	}
}

func (tb *tokenBucket) Capacity() (c uint64) {
	c = tb.capacity
	return
}

func (tb *tokenBucket) Remove(
	now MonotonicTime,
	tokens uint64) (sleepUntil MonotonicTime) {
	if tokens > tb.capacity {
		panic(fmt.Sprintf(
			"Token count %d out of range; capacity is %d",
			tokens,
			tb.capacity))
	}

	// First play the clock forward until now, crediting any tokens that have
	// accumulated in the meantime, up to the bucket's capacity.
	if tb.creditTime < now {
		diff := now - tb.creditTime

		// Don't forget to cap at the capacity.
		tb.credit += tb.rateHz * float64(diff) / float64(time.Second)
		if !(tb.credit <= float64(tb.capacity)) {
			tb.credit = float64(tb.capacity)
		}

		tb.creditTime = now
	}

	// Deduct the requested tokens. The user will need to wait until the credit
	// makes it back to zero, which is when it would have otherwise made it to
	// `tokens`.
	tb.credit -= float64(tokens)

	sleepUntil = tb.creditTime
	if tb.credit < 0 {
		seconds := -tb.credit / tb.rateHz
		sleepUntil = tb.creditTime + MonotonicTime(seconds*float64(time.Second))
	}

	return
}
<|endoftext|>


<commit_before>// Copyright © 2015 Erik Brady <brady@dvln.org>
//
// 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 lib

import (
	"encoding/json"
	"testing"

	globs "github.com/dvln/viper"
	"github.com/dvln/testify/assert"
)

func TestTextDvlnverOutput(t *testing.T) {
	// Try "standard" verbosity with text output, see if right items set
	output := DvlnVerStr()
	assert.Contains(t, output, "Version:")
	assert.Contains(t, output, "API Rev:")
	assert.Contains(t, output, "Build Date:")
	assert.NotContains(t, output, "Exec Name:")

	// Try "verbose" output
	globs.Set("verbose", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "Version:")
	assert.Contains(t, output, "API Rev:")
	assert.Contains(t, output, "Build Date:")
	assert.Contains(t, output, "Exec Name:")

	// Try "terse" output
	globs.Set("verbose", false)
	globs.Set("terse", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "Version:")
	assert.NotContains(t, output, "API Rev:")
	assert.NotContains(t, output, "Build Date:")
	assert.NotContains(t, output, "Exec Name:")
	globs.Set("terse", false)
}

func TestJSONDvlnverOutput(t *testing.T) {
	// Try "standard" verbosity with JSON output, see if right items set
	globs.Set("look", "json")
	output := DvlnVerStr()
	assert.Contains(t, output, "  \"context\": \"dvlnVersion\"")
	assert.Contains(t, output, "    \"items\":")
	assert.Contains(t, output, "        \"toolVersion\":")
	assert.Contains(t, output, "        \"apiVersion\":")
	assert.Contains(t, output, "        \"buildDate\":")
	assert.NotContains(t, output, "        \"execName\":")
	// Verify the JSON is parsable
	var result interface{}
	err := json.Unmarshal([]byte(output), &result)
	if err != nil {
		t.Fatalf("Unable to marshal regular JSON output: %s", err)
	}
	// FEATURE: could get more results out of returned structure... below also

	// Try "verbose" output
	globs.Set("verbose", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "  \"context\": \"dvlnVersion\"")
	assert.Contains(t, output, "    \"items\":")
	assert.Contains(t, output, "        \"toolVersion\":")
	assert.Contains(t, output, "        \"apiVersion\":")
	assert.Contains(t, output, "        \"buildDate\":")
	assert.Contains(t, output, "        \"execName\":")
	err = json.Unmarshal([]byte(output), &result)
	if err != nil {
		t.Fatalf("Unable to unmarshal verbose JSON output: %s", err)
	}

	// Try "terse" output
	globs.Set("verbose", false)
	globs.Set("terse", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "  \"context\": \"dvlnVersion\"")
	assert.Contains(t, output, "    \"items\":")
	assert.Contains(t, output, "        \"toolVersion\":")
	assert.NotContains(t, output, "        \"apiVersion\":")
	assert.NotContains(t, output, "        \"buildDate\":")
	assert.NotContains(t, output, "        \"execName\":")
	err = json.Unmarshal([]byte(output), &result)
	if err != nil {
		t.Fatalf("Unable to marshal terse JSON output: %s", err)
	}

	// Reset settings in case later tests
	globs.Set("terse", false)
	globs.Set("look", "text")
}
<commit_msg>Updated with go fmt related adjustments<commit_after>// Copyright © 2015 Erik Brady <brady@dvln.org>
//
// 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 lib

import (
	"encoding/json"
	"testing"

	"github.com/dvln/testify/assert"
	globs "github.com/dvln/viper"
)

func TestTextDvlnverOutput(t *testing.T) {
	// Try "standard" verbosity with text output, see if right items set
	output := DvlnVerStr()
	assert.Contains(t, output, "Version:")
	assert.Contains(t, output, "API Rev:")
	assert.Contains(t, output, "Build Date:")
	assert.NotContains(t, output, "Exec Name:")

	// Try "verbose" output
	globs.Set("verbose", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "Version:")
	assert.Contains(t, output, "API Rev:")
	assert.Contains(t, output, "Build Date:")
	assert.Contains(t, output, "Exec Name:")

	// Try "terse" output
	globs.Set("verbose", false)
	globs.Set("terse", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "Version:")
	assert.NotContains(t, output, "API Rev:")
	assert.NotContains(t, output, "Build Date:")
	assert.NotContains(t, output, "Exec Name:")
	globs.Set("terse", false)
}

func TestJSONDvlnverOutput(t *testing.T) {
	// Try "standard" verbosity with JSON output, see if right items set
	globs.Set("look", "json")
	output := DvlnVerStr()
	assert.Contains(t, output, "  \"context\": \"dvlnVersion\"")
	assert.Contains(t, output, "    \"items\":")
	assert.Contains(t, output, "        \"toolVersion\":")
	assert.Contains(t, output, "        \"apiVersion\":")
	assert.Contains(t, output, "        \"buildDate\":")
	assert.NotContains(t, output, "        \"execName\":")
	// Verify the JSON is parsable
	var result interface{}
	err := json.Unmarshal([]byte(output), &result)
	if err != nil {
		t.Fatalf("Unable to marshal regular JSON output: %s", err)
	}
	// FEATURE: could get more results out of returned structure... below also

	// Try "verbose" output
	globs.Set("verbose", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "  \"context\": \"dvlnVersion\"")
	assert.Contains(t, output, "    \"items\":")
	assert.Contains(t, output, "        \"toolVersion\":")
	assert.Contains(t, output, "        \"apiVersion\":")
	assert.Contains(t, output, "        \"buildDate\":")
	assert.Contains(t, output, "        \"execName\":")
	err = json.Unmarshal([]byte(output), &result)
	if err != nil {
		t.Fatalf("Unable to unmarshal verbose JSON output: %s", err)
	}

	// Try "terse" output
	globs.Set("verbose", false)
	globs.Set("terse", true)
	output = DvlnVerStr()
	assert.Contains(t, output, "  \"context\": \"dvlnVersion\"")
	assert.Contains(t, output, "    \"items\":")
	assert.Contains(t, output, "        \"toolVersion\":")
	assert.NotContains(t, output, "        \"apiVersion\":")
	assert.NotContains(t, output, "        \"buildDate\":")
	assert.NotContains(t, output, "        \"execName\":")
	err = json.Unmarshal([]byte(output), &result)
	if err != nil {
		t.Fatalf("Unable to marshal terse JSON output: %s", err)
	}

	// Reset settings in case later tests
	globs.Set("terse", false)
	globs.Set("look", "text")
}
<|endoftext|>


<commit_before>package tour

import "fmt"

// sections within.
//
// It's designed to make it easy to re-order chapters with minimal fuss.
//
// eg.
// 		Intro := Chapter(1)
// 		ID("1.1") == Intro(1)
func Chapter(number int) func(topic int) ID {
	return func(topic int) ID {
		return ID(fmt.Sprintf("%d.%d", number, topic))
	}
}
<commit_msg>docs(tour) chapter<commit_after>package tour

import "fmt"

// returns a partially applied function.
//
// It's designed to make it easy to re-order chapters with minimal fuss.
//
// eg.
// 		Intro := Chapter(1)
// 		ID("1.1") == Intro(1) == Chapter(1)(1)
func Chapter(number int) func(topic int) ID {
	return func(topic int) ID {
		return ID(fmt.Sprintf("%d.%d", number, topic))
	}
}
<|endoftext|>


<commit_before>package sacloud

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

// MobileGateway モバイルゲートウェイ
type MobileGateway struct {
	*Appliance // アプライアンス共通属性

	Remark   *MobileGatewayRemark   `json:",omitempty"` // リマーク
	Settings *MobileGatewaySettings `json:",omitempty"` // モバイルゲートウェイ設定
}

// MobileGatewayRemark リマーク
type MobileGatewayRemark struct {
	*ApplianceRemarkBase
	// TODO Zone
	//Zone *Resource
}

// MobileGatewaySettings モバイルゲートウェイ設定
type MobileGatewaySettings struct {
	MobileGateway *MobileGatewaySetting `json:",omitempty"` // モバイルゲートウェイ設定リスト
}

// MobileGatewaySetting モバイルゲートウェイ設定
type MobileGatewaySetting struct {
	InternetConnection *MGWInternetConnection `json:",omitempty"` // インターネット接続
	Interfaces         []*MGWInterface        `json:",omitempty"` // インターフェース
	StaticRoutes       []*MGWStaticRoute      `json:",omitempty"` // スタティックルート
}

// HasStaticRoutes スタティックルートを保持しているか
func (m *MobileGatewaySetting) HasStaticRoutes() bool {
	return m.StaticRoutes != nil && len(m.StaticRoutes) > 0
}

// AddStaticRoute スタティックルート設定 追加
func (m *MobileGatewaySetting) AddStaticRoute(prefix string, nextHop string) (int, *MGWStaticRoute) {
	if m.StaticRoutes == nil {
		m.StaticRoutes = []*MGWStaticRoute{}
	}

	s := &MGWStaticRoute{
		Prefix:  prefix,
		NextHop: nextHop,
	}
	m.StaticRoutes = append(m.StaticRoutes, s)
	return len(m.StaticRoutes) - 1, s
}

// RemoveStaticRoute スタティックルート設定 削除
func (m *MobileGatewaySetting) RemoveStaticRoute(prefix string, nextHop string) {
	if m.StaticRoutes == nil {
		return
	}

	dest := []*MGWStaticRoute{}
	for _, s := range m.StaticRoutes {
		if s.Prefix != prefix || s.NextHop != nextHop {
			dest = append(dest, s)
		}
	}
	m.StaticRoutes = dest
}

// RemoveStaticRouteAt スタティックルート設定 削除
func (m *MobileGatewaySetting) RemoveStaticRouteAt(index int) {
	if m.StaticRoutes == nil {
		return
	}

	if index < len(m.StaticRoutes) {
		s := m.StaticRoutes[index]
		m.RemoveStaticRoute(s.Prefix, s.NextHop)
	}
}

// FindStaticRoute スタティックルート設定 検索
func (m *MobileGatewaySetting) FindStaticRoute(prefix string, nextHop string) (int, *MGWStaticRoute) {
	for i, s := range m.StaticRoutes {
		if s.Prefix == prefix && s.NextHop == nextHop {
			return i, s
		}
	}
	return -1, nil
}

// MGWInternetConnection インターネット接続
type MGWInternetConnection struct {
	Enabled string `json:",omitempty"`
}

// MGWInterface インターフェース
type MGWInterface struct {
	IPAddress      []string `json:",omitempty"`
	NetworkMaskLen int      `json:",omitempty"`
}

// MGWStaticRoute スタティックルート
type MGWStaticRoute struct {
	Prefix  string `json:",omitempty"`
	NextHop string `json:",omitempty"`
}

// MobileGatewayPlan モバイルゲートウェイプラン
type MobileGatewayPlan int

var (
	// MobileGatewayPlanStandard スタンダードプラン // TODO 正式名称不明なため暫定の名前
	MobileGatewayPlanStandard = MobileGatewayPlan(1)
)

// CreateMobileGatewayValue モバイルゲートウェイ作成用パラメーター
type CreateMobileGatewayValue struct {
	Name        string   // 名称
	Description string   // 説明
	Tags        []string // タグ
	IconID      int64    // アイコン
}

// CreateNewMobileGateway モバイルゲートウェイ作成
func CreateNewMobileGateway(values *CreateMobileGatewayValue, setting *MobileGatewaySetting) (*MobileGateway, error) {

	lb := &MobileGateway{
		Appliance: &Appliance{
			Class:           "mobilegateway",
			propName:        propName{Name: values.Name},
			propDescription: propDescription{Description: values.Description},
			propTags:        propTags{Tags: values.Tags},
			propPlanID:      propPlanID{Plan: &Resource{ID: int64(MobileGatewayPlanStandard)}},
			propIcon: propIcon{
				&Icon{
					Resource: NewResource(values.IconID),
				},
			},
		},
		Remark: &MobileGatewayRemark{
			ApplianceRemarkBase: &ApplianceRemarkBase{
				Switch: &ApplianceRemarkSwitch{
					propScope: propScope{
						Scope: "shared",
					},
				},
				Servers: []interface{}{
					nil,
				},
			},
		},
		Settings: &MobileGatewaySettings{
			MobileGateway: setting,
		},
	}

	return lb, nil
}

// SetPrivateInterface プライベート側NICの接続
func (m *MobileGateway) SetPrivateInterface(ip string, nwMaskLen int) {
	if len(m.Settings.MobileGateway.Interfaces) > 1 {
		m.Settings.MobileGateway.Interfaces[1].IPAddress = []string{ip}
		m.Settings.MobileGateway.Interfaces[1].NetworkMaskLen = nwMaskLen
	} else {
		nic := &MGWInterface{
			IPAddress:      []string{ip},
			NetworkMaskLen: nwMaskLen,
		}
		m.Settings.MobileGateway.Interfaces = append(m.Settings.MobileGateway.Interfaces, nic)
	}
}

// ClearPrivateInterface プライベート側NICの切断
func (m *MobileGateway) ClearPrivateInterface() {
	m.Settings.MobileGateway.Interfaces = []*MGWInterface{nil}
}

// HasSetting モバイルゲートウェイ設定を保持しているか
func (m *MobileGateway) HasSetting() bool {
	return m.Settings != nil && m.Settings.MobileGateway != nil
}

// HasStaticRoutes スタティックルートを保持しているか
func (m *MobileGateway) asStaticRoutes() bool {
	return m.HasSetting() && m.Settings.MobileGateway.HasStaticRoutes()
}

// NewMobileGatewayResolver DNS登録用パラメータ作成
func NewMobileGatewayResolver(dns1, dns2 string) *MobileGatewayResolver {
	return &MobileGatewayResolver{
		SimGroup: &MobileGatewaySIMGroup{
			DNS1: dns1,
			DNS2: dns2,
		},
	}
}

// MobileGatewayResolver DNS登録用パラメータ
type MobileGatewayResolver struct {
	SimGroup *MobileGatewaySIMGroup `json:"sim_group,omitempty"`
}

// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
func (m *MobileGatewaySIMGroup) UnmarshalJSON(data []byte) error {
	targetData := strings.Replace(strings.Replace(string(data), " ", "", -1), "\n", "", -1)
	if targetData == `[]` {
		return nil
	}

	tmp := &struct {
		DNS1 string `json:"dns_1,omitempty"`
		DNS2 string `json:"dns_2,omitempty"`
	}{}
	if err := json.Unmarshal(data, &tmp); err != nil {
		return err
	}

	m.DNS1 = tmp.DNS1
	m.DNS2 = tmp.DNS2
	return nil
}

// MobileGatewaySIMGroup DNS登録用SIMグループ値
type MobileGatewaySIMGroup struct {
	DNS1 string `json:"dns_1,omitempty"`
	DNS2 string `json:"dns_2,omitempty"`
}

// MobileGatewaySIMRoute SIルート
type MobileGatewaySIMRoute struct {
	ICCID      string `json:"iccid,omitempty"`
	Prefix     string `json:"prefix,omitempty"`
	ResourceID string `json:"resource_id,omitempty"`
}

// MobileGatewaySIMRoutes SIMルート一覧
type MobileGatewaySIMRoutes struct {
	SIMRoutes []*MobileGatewaySIMRoute `json:"sim_routes"`
}

// AddSIMRoute SIMルート追加
func (m *MobileGatewaySIMRoutes) AddSIMRoute(simID int64, prefix string) (int, *MobileGatewaySIMRoute) {
	var exists bool
	for _, route := range m.SIMRoutes {
		if route.ResourceID == fmt.Sprintf("%d", simID) && route.Prefix == prefix {
			exists = true
			break
		}
	}
	if !exists {
		r := &MobileGatewaySIMRoute{
			ResourceID: fmt.Sprintf("%d", simID),
			Prefix:     prefix,
		}
		m.SIMRoutes = append(m.SIMRoutes, r)
		return len(m.SIMRoutes) - 1, r
	}
	return -1, nil
}

// DeleteSIMRoute SIMルート削除
func (m *MobileGatewaySIMRoutes) DeleteSIMRoute(simID int64, prefix string) bool {
	routes := []*MobileGatewaySIMRoute{} // nolint (JSONヘのMarshal時に要素が0の場合にNULLではなく[]とするため)
	var exists bool

	for _, route := range m.SIMRoutes {
		if route.ResourceID == fmt.Sprintf("%d", simID) && route.Prefix == prefix {
			exists = true
		} else {
			routes = append(routes, route)
		}
	}
	m.SIMRoutes = routes
	return exists
}

// DeleteSIMRouteAt SIMルート削除
func (m *MobileGatewaySIMRoutes) DeleteSIMRouteAt(index int) bool {
	if m.SIMRoutes == nil {
		return false
	}

	if index < len(m.SIMRoutes) {
		s := m.SIMRoutes[index]
		if simID, err := strconv.ParseInt(s.ResourceID, 10, 64); err != nil {
			m.DeleteSIMRoute(simID, s.Prefix)
		}
	}
	return false
}

// FindSIMRoute SIMルート設定 検索
func (m *MobileGatewaySIMRoutes) FindSIMRoute(simID int64, prefix string) (int, *MobileGatewaySIMRoute) {
	for i, r := range m.SIMRoutes {
		if r.Prefix == prefix && r.ResourceID == fmt.Sprintf("%d", simID) {
			return i, r
		}
	}
	return -1, nil
}
<commit_msg>Fix func name typo<commit_after>package sacloud

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

// MobileGateway モバイルゲートウェイ
type MobileGateway struct {
	*Appliance // アプライアンス共通属性

	Remark   *MobileGatewayRemark   `json:",omitempty"` // リマーク
	Settings *MobileGatewaySettings `json:",omitempty"` // モバイルゲートウェイ設定
}

// MobileGatewayRemark リマーク
type MobileGatewayRemark struct {
	*ApplianceRemarkBase
	// TODO Zone
	//Zone *Resource
}

// MobileGatewaySettings モバイルゲートウェイ設定
type MobileGatewaySettings struct {
	MobileGateway *MobileGatewaySetting `json:",omitempty"` // モバイルゲートウェイ設定リスト
}

// MobileGatewaySetting モバイルゲートウェイ設定
type MobileGatewaySetting struct {
	InternetConnection *MGWInternetConnection `json:",omitempty"` // インターネット接続
	Interfaces         []*MGWInterface        `json:",omitempty"` // インターフェース
	StaticRoutes       []*MGWStaticRoute      `json:",omitempty"` // スタティックルート
}

// HasStaticRoutes スタティックルートを保持しているか
func (m *MobileGatewaySetting) HasStaticRoutes() bool {
	return m.StaticRoutes != nil && len(m.StaticRoutes) > 0
}

// AddStaticRoute スタティックルート設定 追加
func (m *MobileGatewaySetting) AddStaticRoute(prefix string, nextHop string) (int, *MGWStaticRoute) {
	if m.StaticRoutes == nil {
		m.StaticRoutes = []*MGWStaticRoute{}
	}

	s := &MGWStaticRoute{
		Prefix:  prefix,
		NextHop: nextHop,
	}
	m.StaticRoutes = append(m.StaticRoutes, s)
	return len(m.StaticRoutes) - 1, s
}

// RemoveStaticRoute スタティックルート設定 削除
func (m *MobileGatewaySetting) RemoveStaticRoute(prefix string, nextHop string) {
	if m.StaticRoutes == nil {
		return
	}

	dest := []*MGWStaticRoute{}
	for _, s := range m.StaticRoutes {
		if s.Prefix != prefix || s.NextHop != nextHop {
			dest = append(dest, s)
		}
	}
	m.StaticRoutes = dest
}

// RemoveStaticRouteAt スタティックルート設定 削除
func (m *MobileGatewaySetting) RemoveStaticRouteAt(index int) {
	if m.StaticRoutes == nil {
		return
	}

	if index < len(m.StaticRoutes) {
		s := m.StaticRoutes[index]
		m.RemoveStaticRoute(s.Prefix, s.NextHop)
	}
}

// FindStaticRoute スタティックルート設定 検索
func (m *MobileGatewaySetting) FindStaticRoute(prefix string, nextHop string) (int, *MGWStaticRoute) {
	for i, s := range m.StaticRoutes {
		if s.Prefix == prefix && s.NextHop == nextHop {
			return i, s
		}
	}
	return -1, nil
}

// MGWInternetConnection インターネット接続
type MGWInternetConnection struct {
	Enabled string `json:",omitempty"`
}

// MGWInterface インターフェース
type MGWInterface struct {
	IPAddress      []string `json:",omitempty"`
	NetworkMaskLen int      `json:",omitempty"`
}

// MGWStaticRoute スタティックルート
type MGWStaticRoute struct {
	Prefix  string `json:",omitempty"`
	NextHop string `json:",omitempty"`
}

// MobileGatewayPlan モバイルゲートウェイプラン
type MobileGatewayPlan int

var (
	// MobileGatewayPlanStandard スタンダードプラン // TODO 正式名称不明なため暫定の名前
	MobileGatewayPlanStandard = MobileGatewayPlan(1)
)

// CreateMobileGatewayValue モバイルゲートウェイ作成用パラメーター
type CreateMobileGatewayValue struct {
	Name        string   // 名称
	Description string   // 説明
	Tags        []string // タグ
	IconID      int64    // アイコン
}

// CreateNewMobileGateway モバイルゲートウェイ作成
func CreateNewMobileGateway(values *CreateMobileGatewayValue, setting *MobileGatewaySetting) (*MobileGateway, error) {

	lb := &MobileGateway{
		Appliance: &Appliance{
			Class:           "mobilegateway",
			propName:        propName{Name: values.Name},
			propDescription: propDescription{Description: values.Description},
			propTags:        propTags{Tags: values.Tags},
			propPlanID:      propPlanID{Plan: &Resource{ID: int64(MobileGatewayPlanStandard)}},
			propIcon: propIcon{
				&Icon{
					Resource: NewResource(values.IconID),
				},
			},
		},
		Remark: &MobileGatewayRemark{
			ApplianceRemarkBase: &ApplianceRemarkBase{
				Switch: &ApplianceRemarkSwitch{
					propScope: propScope{
						Scope: "shared",
					},
				},
				Servers: []interface{}{
					nil,
				},
			},
		},
		Settings: &MobileGatewaySettings{
			MobileGateway: setting,
		},
	}

	return lb, nil
}

// SetPrivateInterface プライベート側NICの接続
func (m *MobileGateway) SetPrivateInterface(ip string, nwMaskLen int) {
	if len(m.Settings.MobileGateway.Interfaces) > 1 {
		m.Settings.MobileGateway.Interfaces[1].IPAddress = []string{ip}
		m.Settings.MobileGateway.Interfaces[1].NetworkMaskLen = nwMaskLen
	} else {
		nic := &MGWInterface{
			IPAddress:      []string{ip},
			NetworkMaskLen: nwMaskLen,
		}
		m.Settings.MobileGateway.Interfaces = append(m.Settings.MobileGateway.Interfaces, nic)
	}
}

// ClearPrivateInterface プライベート側NICの切断
func (m *MobileGateway) ClearPrivateInterface() {
	m.Settings.MobileGateway.Interfaces = []*MGWInterface{nil}
}

// HasSetting モバイルゲートウェイ設定を保持しているか
func (m *MobileGateway) HasSetting() bool {
	return m.Settings != nil && m.Settings.MobileGateway != nil
}

// HasStaticRoutes スタティックルートを保持しているか
func (m *MobileGateway) HasStaticRoutes() bool {
	return m.HasSetting() && m.Settings.MobileGateway.HasStaticRoutes()
}

// NewMobileGatewayResolver DNS登録用パラメータ作成
func NewMobileGatewayResolver(dns1, dns2 string) *MobileGatewayResolver {
	return &MobileGatewayResolver{
		SimGroup: &MobileGatewaySIMGroup{
			DNS1: dns1,
			DNS2: dns2,
		},
	}
}

// MobileGatewayResolver DNS登録用パラメータ
type MobileGatewayResolver struct {
	SimGroup *MobileGatewaySIMGroup `json:"sim_group,omitempty"`
}

// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
func (m *MobileGatewaySIMGroup) UnmarshalJSON(data []byte) error {
	targetData := strings.Replace(strings.Replace(string(data), " ", "", -1), "\n", "", -1)
	if targetData == `[]` {
		return nil
	}

	tmp := &struct {
		DNS1 string `json:"dns_1,omitempty"`
		DNS2 string `json:"dns_2,omitempty"`
	}{}
	if err := json.Unmarshal(data, &tmp); err != nil {
		return err
	}

	m.DNS1 = tmp.DNS1
	m.DNS2 = tmp.DNS2
	return nil
}

// MobileGatewaySIMGroup DNS登録用SIMグループ値
type MobileGatewaySIMGroup struct {
	DNS1 string `json:"dns_1,omitempty"`
	DNS2 string `json:"dns_2,omitempty"`
}

// MobileGatewaySIMRoute SIルート
type MobileGatewaySIMRoute struct {
	ICCID      string `json:"iccid,omitempty"`
	Prefix     string `json:"prefix,omitempty"`
	ResourceID string `json:"resource_id,omitempty"`
}

// MobileGatewaySIMRoutes SIMルート一覧
type MobileGatewaySIMRoutes struct {
	SIMRoutes []*MobileGatewaySIMRoute `json:"sim_routes"`
}

// AddSIMRoute SIMルート追加
func (m *MobileGatewaySIMRoutes) AddSIMRoute(simID int64, prefix string) (int, *MobileGatewaySIMRoute) {
	var exists bool
	for _, route := range m.SIMRoutes {
		if route.ResourceID == fmt.Sprintf("%d", simID) && route.Prefix == prefix {
			exists = true
			break
		}
	}
	if !exists {
		r := &MobileGatewaySIMRoute{
			ResourceID: fmt.Sprintf("%d", simID),
			Prefix:     prefix,
		}
		m.SIMRoutes = append(m.SIMRoutes, r)
		return len(m.SIMRoutes) - 1, r
	}
	return -1, nil
}

// DeleteSIMRoute SIMルート削除
func (m *MobileGatewaySIMRoutes) DeleteSIMRoute(simID int64, prefix string) bool {
	routes := []*MobileGatewaySIMRoute{} // nolint (JSONヘのMarshal時に要素が0の場合にNULLではなく[]とするため)
	var exists bool

	for _, route := range m.SIMRoutes {
		if route.ResourceID == fmt.Sprintf("%d", simID) && route.Prefix == prefix {
			exists = true
		} else {
			routes = append(routes, route)
		}
	}
	m.SIMRoutes = routes
	return exists
}

// DeleteSIMRouteAt SIMルート削除
func (m *MobileGatewaySIMRoutes) DeleteSIMRouteAt(index int) bool {
	if m.SIMRoutes == nil {
		return false
	}

	if index < len(m.SIMRoutes) {
		s := m.SIMRoutes[index]
		if simID, err := strconv.ParseInt(s.ResourceID, 10, 64); err != nil {
			m.DeleteSIMRoute(simID, s.Prefix)
		}
	}
	return false
}

// FindSIMRoute SIMルート設定 検索
func (m *MobileGatewaySIMRoutes) FindSIMRoute(simID int64, prefix string) (int, *MobileGatewaySIMRoute) {
	for i, r := range m.SIMRoutes {
		if r.Prefix == prefix && r.ResourceID == fmt.Sprintf("%d", simID) {
			return i, r
		}
	}
	return -1, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"time"

	"github.com/rainforestapp/rainforest-cli/rainforest"
	"github.com/urfave/cli"
)

const (
	// Version of the app in SemVer
	version = "2.0.1"
	// This is the default spec folder for RFML tests
	defaultSpecFolder = "./spec/rainforest"
)

var (
	// Build info to be set while building using:
	// go build -ldflags "-X main.build=build"
	build string

	// Release channel to be set while building using:
	// go build -ldflags "-X main.releaseChannel=channel"
	releaseChannel string

	// Rainforest API client
	api *rainforest.Client

	// default output for printing resource tables
	tablesOut io.Writer = os.Stdout

	// Run status polling interval
	runStatusPollInterval = time.Second * 5

	// Batch size (number of rows) for tabular var upload
	tabularBatchSize = 50
	// Concurrent connections when uploading CSV rows
	tabularConcurrency = 1
	// Maximum concurrent connections with Rainforest server
	rfmlDownloadConcurrency = 4
	// Concurrent connections when uploading RFML files
	rfmlUploadConcurrency = 4
)

// cliContext is an interface providing context of running application
// i.e. command line options and flags. One of the types that provides the interface is
// cli.Context, the other is fakeCLIContext which is used for testing.
type cliContext interface {
	String(flag string) (val string)
	StringSlice(flag string) (vals []string)
	Bool(flag string) (val bool)
	Int(flag string) (val int)

	Args() (args cli.Args)
}

// Create custom writer which will use timestamps
type logWriter struct{}

func (l *logWriter) Write(p []byte) (int, error) {
	log.Printf("%s", p)
	return len(p), nil
}

// main is an entry point of the app. It sets up the new cli app, and defines the API.
func main() {
	updateFinishedChan := make(chan struct{})
	app := cli.NewApp()
	app.Usage = "Rainforest QA CLI - https://www.rainforestqa.com/"
	app.Version = version
	if releaseChannel != "" {
		app.Version = fmt.Sprintf("%v - %v channel", app.Version, releaseChannel)
	}
	if build != "" {
		app.Version = fmt.Sprintf("%v - build: %v", app.Version, build)
	}

	// Use our custom writer to print our errors with timestamps
	cli.ErrWriter = &logWriter{}

	// Before running any of the commands we init the API Client & update
	app.Before = func(c *cli.Context) error {
		go autoUpdate(c, updateFinishedChan)

		api = rainforest.NewClient(c.String("token"))

		// Set the User-Agent that will be used for api calls
		if build != "" {
			api.UserAgent = "rainforest-cli/" + version + " build: " + build
		} else {
			api.UserAgent = "rainforest-cli/" + version
		}

		return nil
	}

	// Wait for the update to finish if it's still going on
	app.After = func(c *cli.Context) error {
		<-updateFinishedChan
		return nil
	}

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "token, t",
			Usage:  "API token. You can find it at https://app.rainforestqa.com/settings/integrations",
			EnvVar: "RAINFOREST_API_TOKEN",
		},
		cli.BoolFlag{
			Name:  "skip-update",
			Usage: "Used to disable auto-updating of the cli",
		},
	}

	app.Commands = []cli.Command{
		{
			Name:    "run",
			Aliases: []string{"r"},
			Usage:   "Run your tests on Rainforest",
			Action:  startRun,
			Description: "Runs your tests on Rainforest platform. " +
				"You need to specify list of test IDs to run or use keyword 'all'. " +
				"Alternatively you can use one of the filtering options.",
			ArgsUsage: "[test IDs]",
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "tag",
					Usage: "filter tests by `TAG`. Can be used multiple times for filtering by multiple tags.",
				},
				cli.StringFlag{
					Name:  "site, site-id",
					Usage: "filter tests by a specific site. You can see a list of your `SITE-ID`s with sites command.",
				},
				cli.StringFlag{
					Name:  "folder",
					Usage: "filter tests by a specific folder. You can see a list of your `FOLDER-ID`s with folders command.",
				},
				cli.StringSliceFlag{
					Name: "browser, browsers",
					Usage: "specify the `BROWSER` you wish to run against. This overrides test level settings." +
						"Can be used multiple times to run against multiple browsers.",
				},
				cli.StringFlag{
					Name:  "environment-id",
					Usage: "run your tests using specified `ENVIRONMENT`. Otherwise it will use your default one.",
				},
				cli.StringFlag{
					Name:  "crowd",
					Value: "default",
					Usage: "run your tests using specified `CROWD`. Available choices are: default or on_premise_crowd. " +
						"Contact your CSM for more details.",
				},
				cli.StringFlag{
					Name: "conflict",
					Usage: "use the abort option to abort any runs in the same environment or " +
						"use the abort-all option to abort all runs in progress.",
				},
				cli.BoolFlag{
					Name: "bg, background",
					Usage: "run in the background. This option makes cli return after succesfully starting a run, " +
						"without waiting for the run results.",
				},
				cli.BoolFlag{
					Name: "fail-fast, ff",
					Usage: "fail the build as soon as the first failed result comes in. " +
						"If you don't pass this it will wait until 100% of the run is done. Use with --fg.",
				},
				cli.StringFlag{
					Name: "custom-url",
					Usage: "use a custom `URL` for this run. Example use case: an ad-hoc QA environment with Fourchette. " +
						"You will need to specify a site_id too for this to work.",
				},
				cli.BoolFlag{
					Name: "git-trigger",
					Usage: "only trigger a run when the last commit (for a git repo in the current working directory) " +
						"contains @rainforest and a list of one or more tags. rainforest-cli exits with 0 otherwise.",
				},
				cli.StringFlag{
					Name:  "description",
					Usage: "add arbitrary `DESCRIPTION` to the run.",
				},
				cli.StringFlag{
					Name:  "junit-file",
					Usage: "Create a JUnit XML report `FILE` with the specified name. Must be run in foreground mode.",
				},
				cli.StringFlag{
					Name:  "import-variable-name",
					Usage: "`NAME` of the tabular variable to be created or updated.",
				},
				cli.StringFlag{
					Name:  "import-variable-csv-file",
					Usage: "`PATH` to the CSV file to be uploaded.",
				},
				cli.BoolFlag{
					Name:  "overwrite-variable",
					Usage: "If the flag is set, named variable will be updated.",
				},
				cli.BoolFlag{
					Name:  "single-use",
					Usage: "This option marks uploaded variable as single-use",
				},
				cli.StringFlag{
					Name:  "wait, reattach",
					Usage: "monitor existing run with `RUN_ID` instead of starting a new one.",
				},
			},
		},
		{
			Name:      "new",
			Usage:     "Create a new RFML test",
			ArgsUsage: "[name]",
			Description: "Create new Rainforest test in RFML format (Rainforest Markup Language). " +
				"You may also specify a custom test title or file name.",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:   "test-folder",
					Value:  defaultSpecFolder,
					Usage:  "`PATH` at which to create new test.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
			},
			Action: newRFMLTest,
		},
		{
			Name:      "validate",
			Usage:     "Validate your RFML tests",
			ArgsUsage: "[path to RFML file]",
			Description: "Validate your test for syntax. " +
				"If no filepath is given it validates all RFML tests and performs additional checks for RFML ID validity and more. " +
				"If API token is set it'll validate your tests against server data as well.",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:   "test-folder",
					Value:  "./spec/rainforest/",
					Usage:  "`PATH` where to look for a tests to validate.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
			},
			Action: validateRFML,
		},
		{
			Name:      "upload",
			Usage:     "Upload your RFML tests",
			ArgsUsage: "[path to RFML file]",
			Description: "Uploads specified test to Rainforest. " +
				"If no filepath is given it uploads all RFML tests.",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:   "test-folder",
					Value:  "./spec/rainforest/",
					Usage:  "`PATH` where to look for a tests to upload.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
				cli.BoolFlag{
					Name:  "synchronous-upload",
					Usage: "uploads your test in a synchronous manner i.e. not using concurrency.",
				},
			},
			Action: uploadRFML,
		},
		{
			Name:        "rm",
			Usage:       "Remove an RFML test locally and remotely",
			ArgsUsage:   "[path to RFML file]",
			Description: "Remove RFML file and remove test from Rainforest test suite.",
			Action:      deleteRFML,
		},
		{
			Name: "download",
			// Left for legacy reason, should nuke?
			Aliases:   []string{"export"},
			Usage:     "Download your remote Rainforest tests to RFML",
			ArgsUsage: "[test IDs]",
			Description: "Download your remote tests from Rainforest to RFML. " +
				"You need to specify list of test IDs to download or use keyword 'all'. " +
				"Alternatively you can use one of the filtering options.",
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "tag",
					Usage: "filter tests by `TAG`. Can be used multiple times for filtering by multiple tags.",
				},
				cli.IntFlag{
					Name:  "site, site-id",
					Usage: "filter tests by a specific site. You can see a list of your `SITE-ID`s with sites command.",
				},
				cli.IntFlag{
					Name:  "folder, folder-id",
					Usage: "filter tests by a specific folder. You can see a list of your `FOLDER-ID`s with folders command.",
				},
				cli.StringFlag{
					Name:   "test-folder",
					Value:  "./spec/rainforest/",
					Usage:  "`PATH` at which to save all the downloaded tests.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
				cli.BoolFlag{
					Name:  "embed-tests",
					Usage: "download your tests without extracting the steps of an embedded test.",
				},
			},
			Action: func(c *cli.Context) error {
				return downloadRFML(c, api)
			},
		},
		{
			Name:        "csv-upload",
			Usage:       "Create or update tabular var from CSV.",
			Description: "Upload a CSV file to create or update tabular variables.",
			ArgsUsage:   "[path to CSV file]",
			Flags: []cli.Flag{
				cli.StringFlag{
					// Alternative name left for legacy reason.
					Name:  "name, import-variable-name",
					Usage: "`NAME` of the tabular variable to be created or updated.",
				},
				cli.BoolFlag{
					Name:  "overwrite-variable, overwrite",
					Usage: "If the flag is set, named variable will be updated.",
				},
				cli.BoolFlag{
					Name:  "single-use",
					Usage: "This option marks uploaded variable as single-use",
				},
				// Left here for legacy reason, but imho we should move that to args
				cli.StringFlag{
					Name:  "csv-file, import-variable-csv-file",
					Usage: "DEPRECATED: `PATH` to the CSV file to be uploaded. Since v2 please provide the path as an argument.",
				},
			},
			Action: func(c *cli.Context) error {
				return csvUpload(c, api)
			},
		},
		{
			Name:  "report",
			Usage: "Create a report from your run results",
			Description: "Creates a report from your specified run." +
				"You can specify output file using options, otherwise report will be generated to STDOUT",
			ArgsUsage: "[run ID]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "junit-file",
					Usage: "`PATH` of file to which write a JUnit report for the specified run.",
				},
				cli.StringFlag{
					Name:  "run-id",
					Usage: "DEPRECATED: ID of a run for which to generate results. Since v2 please provide the run ID as an argument.",
				},
			},
			Action: createReport,
		},
		{
			Name:  "sites",
			Usage: "Lists available sites",
			Action: func(c *cli.Context) error {
				return printSites(api)
			},
		},
		{
			Name:  "folders",
			Usage: "Lists available folders",
			Action: func(c *cli.Context) error {
				return printFolders(api)
			},
		},
		{
			Name:  "browsers",
			Usage: "Lists available browsers",
			Action: func(c *cli.Context) error {
				return printBrowsers(api)
			},
		},
		{
			Name:      "update",
			Usage:     "Updates application to the latest version on specified release channel (stable/beta)",
			ArgsUsage: "[CHANNEL]",
			Action:    updateCmd,
		},
	}

	app.Run(shuffleFlags(os.Args))
}

// shuffleFlags moves global flags to the beginning of args array (where they are supposed to be),
// so they are picked up by the cli package, even though they are supplied as a command argument.
// might not be needed if upstream makes this change as well
func shuffleFlags(originalArgs []string) []string {
	globalOptions := []string{}
	rest := []string{}

	// We need to skip the filename as its arg[0] that's why iteration starts at 1
	// then filter out global flags and put them into separate array than the rest of arg
	for i := 1; i < len(originalArgs); i++ {
		option := originalArgs[i]
		if option == "--token" || option == "-t" {
			if i+1 < len(originalArgs) && originalArgs[i+1][:1] != "-" {
				globalOptions = append(globalOptions, originalArgs[i:i+2]...)
				i++
			} else {
				log.Fatalln("No token specified with --token flag")
			}
		} else if option == "--skip-update" {
			globalOptions = append(globalOptions, option)
		} else {
			rest = append(rest, option)
		}
	}

	shuffledFlags := []string{originalArgs[0]}
	shuffledFlags = append(shuffledFlags, globalOptions...)
	shuffledFlags = append(shuffledFlags, rest...)

	return shuffledFlags
}
<commit_msg>RF-1491 fix download documentation<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"time"

	"github.com/rainforestapp/rainforest-cli/rainforest"
	"github.com/urfave/cli"
)

const (
	// Version of the app in SemVer
	version = "2.0.1"
	// This is the default spec folder for RFML tests
	defaultSpecFolder = "./spec/rainforest"
)

var (
	// Build info to be set while building using:
	// go build -ldflags "-X main.build=build"
	build string

	// Release channel to be set while building using:
	// go build -ldflags "-X main.releaseChannel=channel"
	releaseChannel string

	// Rainforest API client
	api *rainforest.Client

	// default output for printing resource tables
	tablesOut io.Writer = os.Stdout

	// Run status polling interval
	runStatusPollInterval = time.Second * 5

	// Batch size (number of rows) for tabular var upload
	tabularBatchSize = 50
	// Concurrent connections when uploading CSV rows
	tabularConcurrency = 1
	// Maximum concurrent connections with Rainforest server
	rfmlDownloadConcurrency = 4
	// Concurrent connections when uploading RFML files
	rfmlUploadConcurrency = 4
)

// cliContext is an interface providing context of running application
// i.e. command line options and flags. One of the types that provides the interface is
// cli.Context, the other is fakeCLIContext which is used for testing.
type cliContext interface {
	String(flag string) (val string)
	StringSlice(flag string) (vals []string)
	Bool(flag string) (val bool)
	Int(flag string) (val int)

	Args() (args cli.Args)
}

// Create custom writer which will use timestamps
type logWriter struct{}

func (l *logWriter) Write(p []byte) (int, error) {
	log.Printf("%s", p)
	return len(p), nil
}

// main is an entry point of the app. It sets up the new cli app, and defines the API.
func main() {
	updateFinishedChan := make(chan struct{})
	app := cli.NewApp()
	app.Usage = "Rainforest QA CLI - https://www.rainforestqa.com/"
	app.Version = version
	if releaseChannel != "" {
		app.Version = fmt.Sprintf("%v - %v channel", app.Version, releaseChannel)
	}
	if build != "" {
		app.Version = fmt.Sprintf("%v - build: %v", app.Version, build)
	}

	// Use our custom writer to print our errors with timestamps
	cli.ErrWriter = &logWriter{}

	// Before running any of the commands we init the API Client & update
	app.Before = func(c *cli.Context) error {
		go autoUpdate(c, updateFinishedChan)

		api = rainforest.NewClient(c.String("token"))

		// Set the User-Agent that will be used for api calls
		if build != "" {
			api.UserAgent = "rainforest-cli/" + version + " build: " + build
		} else {
			api.UserAgent = "rainforest-cli/" + version
		}

		return nil
	}

	// Wait for the update to finish if it's still going on
	app.After = func(c *cli.Context) error {
		<-updateFinishedChan
		return nil
	}

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "token, t",
			Usage:  "API token. You can find it at https://app.rainforestqa.com/settings/integrations",
			EnvVar: "RAINFOREST_API_TOKEN",
		},
		cli.BoolFlag{
			Name:  "skip-update",
			Usage: "Used to disable auto-updating of the cli",
		},
	}

	app.Commands = []cli.Command{
		{
			Name:    "run",
			Aliases: []string{"r"},
			Usage:   "Run your tests on Rainforest",
			Action:  startRun,
			Description: "Runs your tests on Rainforest platform. " +
				"You need to specify list of test IDs to run or use keyword 'all'. " +
				"Alternatively you can use one of the filtering options.",
			ArgsUsage: "[test IDs]",
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "tag",
					Usage: "filter tests by `TAG`. Can be used multiple times for filtering by multiple tags.",
				},
				cli.StringFlag{
					Name:  "site, site-id",
					Usage: "filter tests by a specific site. You can see a list of your `SITE-ID`s with sites command.",
				},
				cli.StringFlag{
					Name:  "folder",
					Usage: "filter tests by a specific folder. You can see a list of your `FOLDER-ID`s with folders command.",
				},
				cli.StringSliceFlag{
					Name: "browser, browsers",
					Usage: "specify the `BROWSER` you wish to run against. This overrides test level settings." +
						"Can be used multiple times to run against multiple browsers.",
				},
				cli.StringFlag{
					Name:  "environment-id",
					Usage: "run your tests using specified `ENVIRONMENT`. Otherwise it will use your default one.",
				},
				cli.StringFlag{
					Name:  "crowd",
					Value: "default",
					Usage: "run your tests using specified `CROWD`. Available choices are: default or on_premise_crowd. " +
						"Contact your CSM for more details.",
				},
				cli.StringFlag{
					Name: "conflict",
					Usage: "use the abort option to abort any runs in the same environment or " +
						"use the abort-all option to abort all runs in progress.",
				},
				cli.BoolFlag{
					Name: "bg, background",
					Usage: "run in the background. This option makes cli return after succesfully starting a run, " +
						"without waiting for the run results.",
				},
				cli.BoolFlag{
					Name: "fail-fast, ff",
					Usage: "fail the build as soon as the first failed result comes in. " +
						"If you don't pass this it will wait until 100% of the run is done. Use with --fg.",
				},
				cli.StringFlag{
					Name: "custom-url",
					Usage: "use a custom `URL` for this run. Example use case: an ad-hoc QA environment with Fourchette. " +
						"You will need to specify a site_id too for this to work.",
				},
				cli.BoolFlag{
					Name: "git-trigger",
					Usage: "only trigger a run when the last commit (for a git repo in the current working directory) " +
						"contains @rainforest and a list of one or more tags. rainforest-cli exits with 0 otherwise.",
				},
				cli.StringFlag{
					Name:  "description",
					Usage: "add arbitrary `DESCRIPTION` to the run.",
				},
				cli.StringFlag{
					Name:  "junit-file",
					Usage: "Create a JUnit XML report `FILE` with the specified name. Must be run in foreground mode.",
				},
				cli.StringFlag{
					Name:  "import-variable-name",
					Usage: "`NAME` of the tabular variable to be created or updated.",
				},
				cli.StringFlag{
					Name:  "import-variable-csv-file",
					Usage: "`PATH` to the CSV file to be uploaded.",
				},
				cli.BoolFlag{
					Name:  "overwrite-variable",
					Usage: "If the flag is set, named variable will be updated.",
				},
				cli.BoolFlag{
					Name:  "single-use",
					Usage: "This option marks uploaded variable as single-use",
				},
				cli.StringFlag{
					Name:  "wait, reattach",
					Usage: "monitor existing run with `RUN_ID` instead of starting a new one.",
				},
			},
		},
		{
			Name:      "new",
			Usage:     "Create a new RFML test",
			ArgsUsage: "[name]",
			Description: "Create new Rainforest test in RFML format (Rainforest Markup Language). " +
				"You may also specify a custom test title or file name.",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:   "test-folder",
					Value:  defaultSpecFolder,
					Usage:  "`PATH` at which to create new test.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
			},
			Action: newRFMLTest,
		},
		{
			Name:      "validate",
			Usage:     "Validate your RFML tests",
			ArgsUsage: "[path to RFML file]",
			Description: "Validate your test for syntax. " +
				"If no filepath is given it validates all RFML tests and performs additional checks for RFML ID validity and more. " +
				"If API token is set it'll validate your tests against server data as well.",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:   "test-folder",
					Value:  "./spec/rainforest/",
					Usage:  "`PATH` where to look for a tests to validate.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
			},
			Action: validateRFML,
		},
		{
			Name:      "upload",
			Usage:     "Upload your RFML tests",
			ArgsUsage: "[path to RFML file]",
			Description: "Uploads specified test to Rainforest. " +
				"If no filepath is given it uploads all RFML tests.",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:   "test-folder",
					Value:  "./spec/rainforest/",
					Usage:  "`PATH` where to look for a tests to upload.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
				cli.BoolFlag{
					Name:  "synchronous-upload",
					Usage: "uploads your test in a synchronous manner i.e. not using concurrency.",
				},
			},
			Action: uploadRFML,
		},
		{
			Name:        "rm",
			Usage:       "Remove an RFML test locally and remotely",
			ArgsUsage:   "[path to RFML file]",
			Description: "Remove RFML file and remove test from Rainforest test suite.",
			Action:      deleteRFML,
		},
		{
			Name: "download",
			// Left for legacy reason, should nuke?
			Aliases:   []string{"export"},
			Usage:     "Download your remote Rainforest tests to RFML",
			ArgsUsage: "[test IDs]",
			Description: "Download your remote tests from Rainforest to RFML. " +
				"You need to specify list of test IDs or download all tests by default. " +
				"Alternatively you can use one of the filtering options.",
			Flags: []cli.Flag{
				cli.StringSliceFlag{
					Name:  "tag",
					Usage: "filter tests by `TAG`. Can be used multiple times for filtering by multiple tags.",
				},
				cli.IntFlag{
					Name:  "site, site-id",
					Usage: "filter tests by a specific site. You can see a list of your `SITE-ID`s with sites command.",
				},
				cli.IntFlag{
					Name:  "folder, folder-id",
					Usage: "filter tests by a specific folder. You can see a list of your `FOLDER-ID`s with folders command.",
				},
				cli.StringFlag{
					Name:   "test-folder",
					Value:  "./spec/rainforest/",
					Usage:  "`PATH` at which to save all the downloaded tests.",
					EnvVar: "RAINFOREST_TEST_FOLDER",
				},
				cli.BoolFlag{
					Name:  "embed-tests",
					Usage: "download your tests without extracting the steps of an embedded test.",
				},
			},
			Action: func(c *cli.Context) error {
				return downloadRFML(c, api)
			},
		},
		{
			Name:        "csv-upload",
			Usage:       "Create or update tabular var from CSV.",
			Description: "Upload a CSV file to create or update tabular variables.",
			ArgsUsage:   "[path to CSV file]",
			Flags: []cli.Flag{
				cli.StringFlag{
					// Alternative name left for legacy reason.
					Name:  "name, import-variable-name",
					Usage: "`NAME` of the tabular variable to be created or updated.",
				},
				cli.BoolFlag{
					Name:  "overwrite-variable, overwrite",
					Usage: "If the flag is set, named variable will be updated.",
				},
				cli.BoolFlag{
					Name:  "single-use",
					Usage: "This option marks uploaded variable as single-use",
				},
				// Left here for legacy reason, but imho we should move that to args
				cli.StringFlag{
					Name:  "csv-file, import-variable-csv-file",
					Usage: "DEPRECATED: `PATH` to the CSV file to be uploaded. Since v2 please provide the path as an argument.",
				},
			},
			Action: func(c *cli.Context) error {
				return csvUpload(c, api)
			},
		},
		{
			Name:  "report",
			Usage: "Create a report from your run results",
			Description: "Creates a report from your specified run." +
				"You can specify output file using options, otherwise report will be generated to STDOUT",
			ArgsUsage: "[run ID]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "junit-file",
					Usage: "`PATH` of file to which write a JUnit report for the specified run.",
				},
				cli.StringFlag{
					Name:  "run-id",
					Usage: "DEPRECATED: ID of a run for which to generate results. Since v2 please provide the run ID as an argument.",
				},
			},
			Action: createReport,
		},
		{
			Name:  "sites",
			Usage: "Lists available sites",
			Action: func(c *cli.Context) error {
				return printSites(api)
			},
		},
		{
			Name:  "folders",
			Usage: "Lists available folders",
			Action: func(c *cli.Context) error {
				return printFolders(api)
			},
		},
		{
			Name:  "browsers",
			Usage: "Lists available browsers",
			Action: func(c *cli.Context) error {
				return printBrowsers(api)
			},
		},
		{
			Name:      "update",
			Usage:     "Updates application to the latest version on specified release channel (stable/beta)",
			ArgsUsage: "[CHANNEL]",
			Action:    updateCmd,
		},
	}

	app.Run(shuffleFlags(os.Args))
}

// shuffleFlags moves global flags to the beginning of args array (where they are supposed to be),
// so they are picked up by the cli package, even though they are supplied as a command argument.
// might not be needed if upstream makes this change as well
func shuffleFlags(originalArgs []string) []string {
	globalOptions := []string{}
	rest := []string{}

	// We need to skip the filename as its arg[0] that's why iteration starts at 1
	// then filter out global flags and put them into separate array than the rest of arg
	for i := 1; i < len(originalArgs); i++ {
		option := originalArgs[i]
		if option == "--token" || option == "-t" {
			if i+1 < len(originalArgs) && originalArgs[i+1][:1] != "-" {
				globalOptions = append(globalOptions, originalArgs[i:i+2]...)
				i++
			} else {
				log.Fatalln("No token specified with --token flag")
			}
		} else if option == "--skip-update" {
			globalOptions = append(globalOptions, option)
		} else {
			rest = append(rest, option)
		}
	}

	shuffledFlags := []string{originalArgs[0]}
	shuffledFlags = append(shuffledFlags, globalOptions...)
	shuffledFlags = append(shuffledFlags, rest...)

	return shuffledFlags
}
<|endoftext|>


<commit_before>package tutum
<commit_msg>Add Action endpoints<commit_after>package tutum

import "encoding/json"

type ActionListResponse struct {
	Objects []Action `json:"objects"`
}

type Action struct {
	Action       string `json:"action"`
	Body         string `json:"body"`
	End_date     string `json:"end_date"`
	Ip           string `json:"ip"`
	Location     string `json:"location"`
	Logs         string `json:"logs"`
	Method       string `json:"method"`
	Object       string `json:"object"`
	Path         string `json:"path"`
	Resource_uri string `json:"resource_uri"`
	Start_date   string `json:"start_date"`
	State        string `json:"state"`
	Uuid         string `json:"uuid"`
}

func ListActions() ([]Action, error) {
	url := "action/"
	request := "GET"
	//Empty Body Request
	body := []byte(`{}`)
	var response ActionListResponse

	data, err := TutumCall(url, request, body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(data, &response)
	if err != nil {
		return nil, err
	}

	return response.Objects, nil
}

func GetAction(uuid string) (Action, error) {
	url := "action/" + uuid + "/"
	request := "GET"
	//Empty Body Request
	body := []byte(`{}`)
	var response Action

	data, err := TutumCall(url, request, body)
	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(data, &response)
	if err != nil {
		panic(err)
	}

	return response, nil
}
<|endoftext|>