<commit_before>package main

import (
	"fmt"
	"math"
)

// An interface defines behaviour of an object. In go an interface is a type
// that specifies one or more method signatures collectively called method set.
// Interfaces are wholly abstract, so it is not possible to instantiate an interface.
// A variable of interface type can store a value of any type with a method set
// that is any superset of the interface. Such a type is said to implement the
// interface. The value of an uninitialized variable of interface type is nil.

// The interface{} type is the interface that specifies the empty set of methods.
// Every value satisfies the interface{} type whether the value has methods or
// not, if a value does have methods, its set of methods includes the empty set
// of methods as well as the methods it actually has. This is why the interface{}
// type can be used for anyvalue.

// A type doesn’t have to state explicitly that it implements an interface:
// interfaces are satisfied implicitly.
// Multiple types can implement the same interface.
// A type that implements an interface can also have other functions. A type can
// implement many interfaces.
// An interface type can contain a reference to an instance of any of the types
// that implement the interface (an interface has what is called a dynamic type)

type shaper interface {
	area() float64
	perimeter() float64
}

type enlarger interface {
	enlarge(float64)
}

type circle struct {
	radius float64
}

type rectangle struct {
	length, width float64
}

type square struct {
	rectangle //anonymous field (embedding)
}

// The methods area and perimeter on types circle, and rectangle are defined
// with a value reciever since these methods do not mutate (modify) the state of
// their respective types.
func (c circle) area() float64 {
	return math.Pi * c.radius * c.radius
}

func (c circle) perimeter() float64 {
	return 2 * math.Pi * c.radius
}

// enlarge method is defined with a pointer reciever since it mutates (modifies)
// the passed values state. If the reciver was defined as value then the chnage
// to the state of the value will not visible.
func (c *circle) enlarge(by float64) {
	c.radius += by
}

// notice that we have not defined the area and perimeter methods for the square type
// If an embedded field has methods we can call them on the containing struct,
// and only the embedded field will be passed as the methods’ receiver.
func (r rectangle) area() float64 {
	return r.length * r.width
}

func (r rectangle) perimeter() float64 {
	return 2 * (r.length + r.width)
}

func (r *rectangle) enlarge(by float64) {
	r.length += by
	r.width += by
}

// geometry takes any values that satisfy the shaper interface and is able
// dynamically dispatch the call to correct area and perimeter methods
func geometry(s shaper) {
	fmt.Printf("Area of %#v is %.2f\n", s, s.area())
	fmt.Printf("Perimeter of %#v is %.2f\n", s, s.perimeter())
}

func main() {
	c := circle{2.12}
	r := rectangle{4.3, 3.5}
	s := square{rectangle{4.6, 4.6}}

	geometry(c)
	geometry(r)
	geometry(s)

	fmt.Printf("Before enlarge %#v\n", c)
	// Since the circle type is addressable Go would automatically modify the
	// call to enlarge method by passing a pointer to c i.e. it would modify
	// the method call to read (&c).enlarge(1.1)
	c.enlarge(1.1)
	fmt.Printf("After enlarge %#v\n", c)

}
<commit_msg>complete the sample<commit_after>package main

import (
	"fmt"
	"math"
)

// An interface defines behaviour of an object. In go an interface is a type
// that specifies one or more method signatures collectively called method set.
// Interfaces are wholly abstract, so it is not possible to instantiate an interface.
// A variable of interface type can store a value of any type with a method set
// that is any superset of the interface. Such a type is said to implement the
// interface. The value of an uninitialized variable of interface type is nil.

// The interface{} type is the interface that specifies the empty set of methods.
// Every value satisfies the interface{} type whether the value has methods or
// not, if a value does have methods, its set of methods includes the empty set
// of methods as well as the methods it actually has. This is why the interface{}
// type can be used for anyvalue.

// A type doesn’t have to state explicitly that it implements an interface:
// interfaces are satisfied implicitly.
// Multiple types can implement the same interface.
// A type that implements an interface can also have other functions. A type can
// implement many interfaces.
// An interface type can contain a reference to an instance of any of the types
// that implement the interface (an interface has what is called a dynamic type)

type shaper interface {
	area() float64
	perimeter() float64
}

type enlarger interface {
	enlarge(float64)
}

type circle struct {
	radius float64
}

type rectangle struct {
	length, width float64
}

type square struct {
	rectangle //anonymous field (embedding)
}

type triangle struct {
	a, b, c float64 //side of a triangle
}

// The methods area and perimeter on types circle, and rectangle are defined
// with a value reciever since these methods do not mutate (modify) the state of
// their respective types.
func (c circle) area() float64 {
	return math.Pi * c.radius * c.radius
}

func (c circle) perimeter() float64 {
	return 2 * math.Pi * c.radius
}

// enlarge method is defined with a pointer reciever since it mutates (modifies)
// the passed values state. If the reciver was defined as value then the chnage
// to the state of the value will not visible.
func (c *circle) enlarge(by float64) {
	c.radius += by
}

// notice that we have not defined the area and perimeter methods for the square type
// If an embedded field has methods we can call them on the containing struct,
// and only the embedded field will be passed as the methods’ receiver.
func (r rectangle) area() float64 {
	return r.length * r.width
}

func (r rectangle) perimeter() float64 {
	return 2 * (r.length + r.width)
}

func (r *rectangle) enlarge(by float64) {
	r.length += by
	r.width += by
}

// geometry takes any values that satisfy the shaper interface and is able
// dynamically dispatch the call to correct area and perimeter methods
func geometry(s shaper) {
	fmt.Printf("Area of %#v is %.2f\n", s, s.area())
	fmt.Printf("Perimeter of %#v is %.2f\n", s, s.perimeter())
}

func main() {
	c := circle{2.12}
	r := rectangle{4.3, 3.5}
	s := square{rectangle{4.6, 4.6}}

	geometry(c)
	geometry(r)
	geometry(s)

	fmt.Printf("Before enlarge %#v\n", c)
	// Since the circle type is addressable Go would automatically modify the
	// call to enlarge method by passing a pointer to c i.e. it would modify
	// the method call to read (&c).enlarge(1.1)
	c.enlarge(1.1)
	fmt.Printf("After enlarge %#v\n", c)

	// declare t if of type interface{}
	var t interface{}
	t = triangle{1.0, 2.0, 3.0}

	// we can use type assertion to check at runtime if a type satifies an interface
	_, ok := t.(shaper)
	if !ok {
		fmt.Printf("%#v does not statisfy shaper interface\n", t)
	}
}
<|endoftext|>


<commit_before>package sqldb

import "github.com/pivotal-golang/lager"

const EncryptionKeyID = "encryption_key_label"

func (db *SQLDB) SetEncryptionKeyLabel(logger lager.Logger, label string) error {
	return db.setConfigurationValue(logger, EncryptionKeyID, label)
}

func (db *SQLDB) EncryptionKeyLabel(logger lager.Logger) (string, error) {
	return db.getConfigurationValue(logger, EncryptionKeyID)
}
<commit_msg>Standardize logging in encryption_db<commit_after>package sqldb

import "github.com/pivotal-golang/lager"

const EncryptionKeyID = "encryption_key_label"

func (db *SQLDB) SetEncryptionKeyLabel(logger lager.Logger, label string) error {
	logger = logger.Session("set-encrption-key-label", lager.Data{"label": label})
	logger.Debug("starting")
	defer logger.Debug("complete")

	return db.setConfigurationValue(logger, EncryptionKeyID, label)
}

func (db *SQLDB) EncryptionKeyLabel(logger lager.Logger) (string, error) {
	logger = logger.Session("encrption-key-label")
	logger.Debug("starting")
	defer logger.Debug("complete")

	return db.getConfigurationValue(logger, EncryptionKeyID)
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/howeyc/ledger"
	"github.com/howeyc/ledger/internal/decimal"
	"github.com/spf13/cobra"
)

const (
	transactionDateFormat = "2006/01/02"
)

var startString, endString string
var columnWidth, transactionDepth int
var showEmptyAccounts bool
var columnWide bool
var period string
var payeeFilter string

func cliTransactions() ([]*ledger.Transaction, error) {
	if columnWidth == 80 && columnWide {
		columnWidth = 132
	}

	parsedStartDate, tstartErr := time.Parse(transactionDateFormat, startString)
	parsedEndDate, tendErr := time.Parse(transactionDateFormat, endString)

	if tstartErr != nil || tendErr != nil {
		return nil, errors.New("unable to parse start or end date string argument")
	}

	// include end dates' transactions too
	parsedEndDate = parsedEndDate.Add(time.Second)

	var generalLedger []*ledger.Transaction
	var parseError error
	if ledgerFilePath == "-" {
		generalLedger, parseError = ledger.ParseLedger(os.Stdin)
	} else {
		generalLedger, parseError = ledger.ParseLedgerFile(ledgerFilePath)
	}
	if parseError != nil {
		return nil, parseError
	}

	sort.SliceStable(generalLedger, func(i, j int) bool {
		return generalLedger[i].Date.Before(generalLedger[j].Date)
	})

	generalLedger = ledger.TransactionsInDateRange(generalLedger, parsedStartDate, parsedEndDate)

	origLedger := generalLedger
	generalLedger = make([]*ledger.Transaction, 0)
	for _, trans := range origLedger {
		if strings.Contains(trans.Payee, payeeFilter) {
			generalLedger = append(generalLedger, trans)
		}
	}

	return generalLedger, nil
}

// printCmd represents the print command
var printCmd = &cobra.Command{
	Use:   "print [account-substring-filter]...",
	Short: "Print transactions in ledger file format",
	Run: func(cmd *cobra.Command, args []string) {
		generalLedger, err := cliTransactions()
		if err != nil {
			log.Fatalln(err)
		}

		PrintLedger(generalLedger, args, columnWidth)
	},
}

func init() {
	rootCmd.AddCommand(printCmd)

	var startDate, endDate time.Time
	startDate = time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local)
	endDate = time.Now().Add(time.Hour * 24)
	printCmd.Flags().StringVarP(&startString, "begin-date", "b", startDate.Format(transactionDateFormat), "Begin date of transaction processing.")
	printCmd.Flags().StringVarP(&endString, "end-date", "e", endDate.Format(transactionDateFormat), "End date of transaction processing.")
	printCmd.Flags().StringVar(&payeeFilter, "payee", "", "Filter output to payees that contain this string.")
	printCmd.Flags().IntVar(&columnWidth, "columns", 80, "Set a column width for output.")
	printCmd.Flags().BoolVar(&columnWide, "wide", false, "Wide output (same as --columns=132).")
}

// PrintBalances prints out account balances formatted to a window set to a width of columns.
// Only shows accounts with names less than or equal to the given depth.
func PrintBalances(accountList []*ledger.Account, printZeroBalances bool, depth, columns int) {
	overallBalance := decimal.Zero
	for _, account := range accountList {
		accDepth := len(strings.Split(account.Name, ":"))
		if accDepth == 1 {
			overallBalance = overallBalance.Add(account.Balance)
		}
		if (printZeroBalances || account.Balance.Sign() != 0) && (depth < 0 || accDepth <= depth) {
			outBalanceString := account.Balance.StringFixedBank()
			spaceCount := columns - utf8.RuneCountInString(account.Name) - utf8.RuneCountInString(outBalanceString)
			fmt.Printf("%s%s%s\n", account.Name, strings.Repeat(" ", spaceCount), outBalanceString)
		}
	}
	fmt.Println(strings.Repeat("-", columns))
	outBalanceString := overallBalance.StringFixedBank()
	spaceCount := columns - utf8.RuneCountInString(outBalanceString)
	fmt.Printf("%s%s\n", strings.Repeat(" ", spaceCount), outBalanceString)
}

// PrintTransaction prints a transaction formatted to fit in specified column width.
func PrintTransaction(trans *ledger.Transaction, columns int) {
	WriteTransaction(os.Stdout, trans, columns)
}

// WriteTransaction writes a transaction formatted to fit in specified column width.
func WriteTransaction(w io.Writer, trans *ledger.Transaction, columns int) {
	for _, c := range trans.Comments {
		fmt.Fprintln(w, c)
	}

	// Print accounts sorted by name
	sort.Slice(trans.AccountChanges, func(i, j int) bool {
		return trans.AccountChanges[i].Name < trans.AccountChanges[j].Name
	})

	fmt.Fprintf(w, "%s %s", trans.Date.Format(transactionDateFormat), trans.Payee)
	if len(trans.PayeeComment) > 0 {
		spaceCount := columns - 10 - utf8.RuneCountInString(trans.Payee)
		if spaceCount < 1 {
			spaceCount = 1
		}
		fmt.Fprintf(w, "%s%s", strings.Repeat(" ", spaceCount), trans.PayeeComment)
	}
	fmt.Fprintln(w, "")
	for _, accChange := range trans.AccountChanges {
		outBalanceString := accChange.Balance.StringFixedBank()
		spaceCount := columns - 4 - utf8.RuneCountInString(accChange.Name) - utf8.RuneCountInString(outBalanceString)
		if spaceCount < 1 {
			spaceCount = 1
		}
		fmt.Fprintf(w, "    %s%s%s", accChange.Name, strings.Repeat(" ", spaceCount), outBalanceString)
		if len(accChange.Comment) > 0 {
			fmt.Fprintf(w, " %s", accChange.Comment)
		}
		fmt.Fprintln(w, "")
	}
	fmt.Fprintln(w, "")
}

// PrintLedger prints all transactions as a formatted ledger file.
func PrintLedger(generalLedger []*ledger.Transaction, filterArr []string, columns int) {
	// Print transactions by date
	if len(generalLedger) > 1 {
		sort.SliceStable(generalLedger, func(i, j int) bool {
			return generalLedger[i].Date.Before(generalLedger[j].Date)
		})
	}

	for _, trans := range generalLedger {
		inFilter := len(filterArr) == 0
		for _, accChange := range trans.AccountChanges {
			for _, filter := range filterArr {
				if strings.Contains(accChange.Name, filter) {
					inFilter = true
				}
			}
		}
		if inFilter {
			PrintTransaction(trans, columns)
		}
	}
}

// PrintRegister prints each transaction that matches the given filters.
func PrintRegister(generalLedger []*ledger.Transaction, filterArr []string, columns int) {
	// Calculate widths for variable-length part of output
	// 3 10-width columns (date, account-change, running-total)
	// 4 spaces
	remainingWidth := columns - (10 * 3) - (4 * 1)
	col1width := remainingWidth / 3
	col2width := remainingWidth - col1width

	formatString := fmt.Sprintf("%%-10.10s %%-%[1]d.%[1]ds %%-%[2]d.%[2]ds %%10.10s %%10.10s\n", col1width, col2width)

	runningBalance := decimal.Zero
	for _, trans := range generalLedger {
		for _, accChange := range trans.AccountChanges {
			inFilter := len(filterArr) == 0
			for _, filter := range filterArr {
				if strings.Contains(accChange.Name, filter) {
					inFilter = true
				}
			}
			if inFilter {
				runningBalance = runningBalance.Add(accChange.Balance)
				outBalanceString := accChange.Balance.StringFixedBank()
				outRunningBalanceString := runningBalance.StringFixedBank()
				fmt.Printf(formatString,
					trans.Date.Format(transactionDateFormat),
					trans.Payee,
					accChange.Name,
					outBalanceString,
					outRunningBalanceString)
			}
		}
	}
}
<commit_msg>fix: columns too small<commit_after>package cmd

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/howeyc/ledger"
	"github.com/howeyc/ledger/internal/decimal"
	"github.com/spf13/cobra"
)

const (
	transactionDateFormat = "2006/01/02"
)

var startString, endString string
var columnWidth, transactionDepth int
var showEmptyAccounts bool
var columnWide bool
var period string
var payeeFilter string

func cliTransactions() ([]*ledger.Transaction, error) {
	if columnWidth == 80 && columnWide {
		columnWidth = 132
	}

	parsedStartDate, tstartErr := time.Parse(transactionDateFormat, startString)
	parsedEndDate, tendErr := time.Parse(transactionDateFormat, endString)

	if tstartErr != nil || tendErr != nil {
		return nil, errors.New("unable to parse start or end date string argument")
	}

	// include end dates' transactions too
	parsedEndDate = parsedEndDate.Add(time.Second)

	var generalLedger []*ledger.Transaction
	var parseError error
	if ledgerFilePath == "-" {
		generalLedger, parseError = ledger.ParseLedger(os.Stdin)
	} else {
		generalLedger, parseError = ledger.ParseLedgerFile(ledgerFilePath)
	}
	if parseError != nil {
		return nil, parseError
	}

	sort.SliceStable(generalLedger, func(i, j int) bool {
		return generalLedger[i].Date.Before(generalLedger[j].Date)
	})

	generalLedger = ledger.TransactionsInDateRange(generalLedger, parsedStartDate, parsedEndDate)

	origLedger := generalLedger
	generalLedger = make([]*ledger.Transaction, 0)
	for _, trans := range origLedger {
		if strings.Contains(trans.Payee, payeeFilter) {
			generalLedger = append(generalLedger, trans)
		}
	}

	return generalLedger, nil
}

// printCmd represents the print command
var printCmd = &cobra.Command{
	Use:   "print [account-substring-filter]...",
	Short: "Print transactions in ledger file format",
	Run: func(cmd *cobra.Command, args []string) {
		generalLedger, err := cliTransactions()
		if err != nil {
			log.Fatalln(err)
		}

		PrintLedger(generalLedger, args, columnWidth)
	},
}

func init() {
	rootCmd.AddCommand(printCmd)

	var startDate, endDate time.Time
	startDate = time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local)
	endDate = time.Now().Add(time.Hour * 24)
	printCmd.Flags().StringVarP(&startString, "begin-date", "b", startDate.Format(transactionDateFormat), "Begin date of transaction processing.")
	printCmd.Flags().StringVarP(&endString, "end-date", "e", endDate.Format(transactionDateFormat), "End date of transaction processing.")
	printCmd.Flags().StringVar(&payeeFilter, "payee", "", "Filter output to payees that contain this string.")
	printCmd.Flags().IntVar(&columnWidth, "columns", 80, "Set a column width for output.")
	printCmd.Flags().BoolVar(&columnWide, "wide", false, "Wide output (same as --columns=132).")
}

// PrintBalances prints out account balances formatted to a window set to a width of columns.
// Only shows accounts with names less than or equal to the given depth.
func PrintBalances(accountList []*ledger.Account, printZeroBalances bool, depth, columns int) {
	// Calculate widths: 10 columns for balance, rest for accountname
	if columns < 12 {
		columns = 12
		fmt.Fprintf(os.Stderr, "warning: `columns` too small, setting to %d\n", columns)
	}
	accWidth := columns - 11
	formatString := fmt.Sprintf("%%-%[1]d.%[1]ds %%10.10s\n", accWidth)

	overallBalance := decimal.Zero
	for _, account := range accountList {
		accDepth := len(strings.Split(account.Name, ":"))
		if accDepth == 1 {
			overallBalance = overallBalance.Add(account.Balance)
		}
		if (printZeroBalances || account.Balance.Sign() != 0) && (depth < 0 || accDepth <= depth) {
			outBalanceString := account.Balance.StringFixedBank()
			fmt.Printf(formatString, account.Name, outBalanceString)
		}
	}
	fmt.Println(strings.Repeat("-", columns))
	outBalanceString := overallBalance.StringFixedBank()
	fmt.Printf(formatString, "", outBalanceString)
}

// PrintTransaction prints a transaction formatted to fit in specified column width.
func PrintTransaction(trans *ledger.Transaction, columns int) {
	WriteTransaction(os.Stdout, trans, columns)
}

// WriteTransaction writes a transaction formatted to fit in specified column width.
func WriteTransaction(w io.Writer, trans *ledger.Transaction, columns int) {
	for _, c := range trans.Comments {
		fmt.Fprintln(w, c)
	}

	// Print accounts sorted by name
	sort.Slice(trans.AccountChanges, func(i, j int) bool {
		return trans.AccountChanges[i].Name < trans.AccountChanges[j].Name
	})

	fmt.Fprintf(w, "%s %s", trans.Date.Format(transactionDateFormat), trans.Payee)
	if len(trans.PayeeComment) > 0 {
		spaceCount := columns - 10 - utf8.RuneCountInString(trans.Payee)
		if spaceCount < 1 {
			spaceCount = 1
		}
		fmt.Fprintf(w, "%s%s", strings.Repeat(" ", spaceCount), trans.PayeeComment)
	}
	fmt.Fprintln(w, "")
	for _, accChange := range trans.AccountChanges {
		outBalanceString := accChange.Balance.StringFixedBank()
		spaceCount := columns - 4 - utf8.RuneCountInString(accChange.Name) - utf8.RuneCountInString(outBalanceString)
		if spaceCount < 1 {
			spaceCount = 1
		}
		fmt.Fprintf(w, "    %s%s%s", accChange.Name, strings.Repeat(" ", spaceCount), outBalanceString)
		if len(accChange.Comment) > 0 {
			fmt.Fprintf(w, " %s", accChange.Comment)
		}
		fmt.Fprintln(w, "")
	}
	fmt.Fprintln(w, "")
}

// PrintLedger prints all transactions as a formatted ledger file.
func PrintLedger(generalLedger []*ledger.Transaction, filterArr []string, columns int) {
	// Print transactions by date
	if len(generalLedger) > 1 {
		sort.SliceStable(generalLedger, func(i, j int) bool {
			return generalLedger[i].Date.Before(generalLedger[j].Date)
		})
	}

	for _, trans := range generalLedger {
		inFilter := len(filterArr) == 0
		for _, accChange := range trans.AccountChanges {
			for _, filter := range filterArr {
				if strings.Contains(accChange.Name, filter) {
					inFilter = true
				}
			}
		}
		if inFilter {
			PrintTransaction(trans, columns)
		}
	}
}

// PrintRegister prints each transaction that matches the given filters.
func PrintRegister(generalLedger []*ledger.Transaction, filterArr []string, columns int) {
	// Calculate widths for variable-length part of output
	// 3 10-width columns (date, account-change, running-total)
	// 4 spaces
	if columns < 35 {
		columns = 35
		fmt.Fprintf(os.Stderr, "warning: `columns` too small, setting to %d\n", columns)
	}
	remainingWidth := columns - (10 * 3) - (4 * 1)
	col1width := remainingWidth / 3
	col2width := remainingWidth - col1width
	formatString := fmt.Sprintf("%%-10.10s %%-%[1]d.%[1]ds %%-%[2]d.%[2]ds %%10.10s %%10.10s\n", col1width, col2width)

	runningBalance := decimal.Zero
	for _, trans := range generalLedger {
		for _, accChange := range trans.AccountChanges {
			inFilter := len(filterArr) == 0
			for _, filter := range filterArr {
				if strings.Contains(accChange.Name, filter) {
					inFilter = true
				}
			}
			if inFilter {
				runningBalance = runningBalance.Add(accChange.Balance)
				outBalanceString := accChange.Balance.StringFixedBank()
				outRunningBalanceString := runningBalance.StringFixedBank()
				fmt.Printf(formatString,
					trans.Date.Format(transactionDateFormat),
					trans.Payee,
					accChange.Name,
					outBalanceString,
					outRunningBalanceString)
			}
		}
	}
}
<|endoftext|>


<commit_before>package data

type VolumeInfo struct {
	Title    string   `json:"title"`
	Subtitle string   `json:"subtitle,omitempty"`
	Series   string   `json:"series,omitempty"`
	Authors  []string `json:"authors"`
}

type BookMetaData struct {
	Volume      VolumeInfo `json:"volumeInfo" datastore:",noindex"`
	Publisher   string     `json:"publisher,omitempty" datastore:",noindex"`
	Description string     `json:"description,omitempty" datastore:",noindex"`
	Comment     string     `json:"comment,omitempty" datastore:",noindex"`
	Images      ImageLinks `json:"imageLink,omitempty" datastore:",noindex"`
	PageCount   int        `json:"pageCount,omitempty" datastore:",noindex"`
	ISBN        string     `json:"isbn" datastore:"noindex"`
}

type ImageLinks struct {
	ThumbnailSmall string `json:"smallThumbnail,omitempty" datastore:",noindex"`
	Thumbnail      string `json:"thumbnail,omitempty" datastore:",noindex"`
}

type LookupReply struct {
	Count     int            `json:"totalItems"`
	BookInfos []BookMetaData `json:"items"`
}

type Bookshelf struct {
	Books []BookMetaData
}
<commit_msg>Fixed datastore annotation<commit_after>package data

type VolumeInfo struct {
	Title    string   `json:"title"`
	Subtitle string   `json:"subtitle,omitempty"`
	Series   string   `json:"series,omitempty"`
	Authors  []string `json:"authors"`
}

type BookMetaData struct {
	Volume      VolumeInfo `json:"volumeInfo" datastore:",noindex"`
	Publisher   string     `json:"publisher,omitempty" datastore:",noindex"`
	Description string     `json:"description,omitempty" datastore:",noindex"`
	Comment     string     `json:"comment,omitempty" datastore:",noindex"`
	Images      ImageLinks `json:"imageLink,omitempty" datastore:",noindex"`
	PageCount   int        `json:"pageCount,omitempty" datastore:",noindex"`
	ISBN        string     `json:"isbn" datastore:",noindex"`
}

type ImageLinks struct {
	ThumbnailSmall string `json:"smallThumbnail,omitempty" datastore:",noindex"`
	Thumbnail      string `json:"thumbnail,omitempty" datastore:",noindex"`
}

type LookupReply struct {
	Count     int            `json:"totalItems"`
	BookInfos []BookMetaData `json:"items"`
}

type Bookshelf struct {
	Books []BookMetaData
}
<|endoftext|>


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

package mail

import (
	"bytes"
	"io/ioutil"
	"reflect"
	"testing"
	"time"
)

var parseTests = []struct {
	in     string
	header Header
	body   string
}{
	{
		// RFC 5322, Appendix A.1.1
		in: `From: John Doe <jdoe@machine.example>
To: Mary Smith <mary@example.net>
Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example>

This is a message just to say hello.
So, "Hello".
`,
		header: Header{
			"From":       []string{"John Doe <jdoe@machine.example>"},
			"To":         []string{"Mary Smith <mary@example.net>"},
			"Subject":    []string{"Saying Hello"},
			"Date":       []string{"Fri, 21 Nov 1997 09:55:06 -0600"},
			"Message-Id": []string{"<1234@local.machine.example>"},
		},
		body: "This is a message just to say hello.\nSo, \"Hello\".\n",
	},
}

func TestParsing(t *testing.T) {
	for i, test := range parseTests {
		msg, err := ReadMessage(bytes.NewBuffer([]byte(test.in)))
		if err != nil {
			t.Errorf("test #%d: Failed parsing message: %v", i, err)
			continue
		}
		if !headerEq(msg.Header, test.header) {
			t.Errorf("test #%d: Incorrectly parsed message header.\nGot:\n%+v\nWant:\n%+v",
				i, msg.Header, test.header)
		}
		body, err := ioutil.ReadAll(msg.Body)
		if err != nil {
			t.Errorf("test #%d: Failed reading body: %v", i, err)
			continue
		}
		bodyStr := string(body)
		if bodyStr != test.body {
			t.Errorf("test #%d: Incorrectly parsed message body.\nGot:\n%+v\nWant:\n%+v",
				i, bodyStr, test.body)
		}
	}
}

func headerEq(a, b Header) bool {
	if len(a) != len(b) {
		return false
	}
	for k, as := range a {
		bs, ok := b[k]
		if !ok {
			return false
		}
		if !reflect.DeepEqual(as, bs) {
			return false
		}
	}
	return true
}

func TestDateParsing(t *testing.T) {
	tests := []struct {
		dateStr string
		exp     time.Time
	}{
		// RFC 5322, Appendix A.1.1
		{
			"Fri, 21 Nov 1997 09:55:06 -0600",
			time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
		},
		// RFC5322, Appendix A.6.2
		// Obsolete date.
		{
			"21 Nov 97 09:55:06 GMT",
			time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("GMT", 0)),
		},
	}
	for _, test := range tests {
		hdr := Header{
			"Date": []string{test.dateStr},
		}
		date, err := hdr.Date()
		if err != nil {
			t.Errorf("Failed parsing %q: %v", test.dateStr, err)
			continue
		}
		if !reflect.DeepEqual(date, test.exp) {
			t.Errorf("Parse of %q: got %+v, want %+v", test.dateStr, date, test.exp)
		}
	}
}

func TestAddressParsing(t *testing.T) {
	tests := []struct {
		addrsStr string
		exp      []*Address
	}{
		// Bare address
		{
			`jdoe@machine.example`,
			[]*Address{{
				Address: "jdoe@machine.example",
			}},
		},
		// RFC 5322, Appendix A.1.1
		{
			`John Doe <jdoe@machine.example>`,
			[]*Address{{
				Name:    "John Doe",
				Address: "jdoe@machine.example",
			}},
		},
		// RFC 5322, Appendix A.1.2
		{
			`"Joe Q. Public" <john.q.public@example.com>`,
			[]*Address{{
				Name:    "Joe Q. Public",
				Address: "john.q.public@example.com",
			}},
		},
		{
			`Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
			[]*Address{
				{
					Name:    "Mary Smith",
					Address: "mary@x.test",
				},
				{
					Address: "jdoe@example.org",
				},
				{
					Name:    "Who?",
					Address: "one@y.test",
				},
			},
		},
		{
			`<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
			[]*Address{
				{
					Address: "boss@nil.test",
				},
				{
					Name:    `Giant; "Big" Box`,
					Address: "sysservices@example.net",
				},
			},
		},
		// RFC 5322, Appendix A.1.3
		// TODO(dsymonds): Group addresses.

		// RFC 2047 "Q"-encoded ISO-8859-1 address.
		{
			`=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg Doe`,
					Address: "joerg@example.com",
				},
			},
		},
		// RFC 2047 "Q"-encoded UTF-8 address.
		{
			`=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg Doe`,
					Address: "joerg@example.com",
				},
			},
		},
		// RFC 2047, Section 8.
		{
			`=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
			[]*Address{
				{
					Name:    `André Pirard`,
					Address: "PIRARD@vm1.ulg.ac.be",
				},
			},
		},
		// Custom example of RFC 2047 "B"-encoded ISO-8859-1 address.
		{
			`=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg`,
					Address: "joerg@example.com",
				},
			},
		},
		// Custom example of RFC 2047 "B"-encoded UTF-8 address.
		{
			`=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg`,
					Address: "joerg@example.com",
				},
			},
		},
	}
	for _, test := range tests {
		addrs, err := newAddrParser(test.addrsStr).parseAddressList()
		if err != nil {
			t.Errorf("Failed parsing %q: %v", test.addrsStr, err)
			continue
		}
		if !reflect.DeepEqual(addrs, test.exp) {
			t.Errorf("Parse of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
		}
	}
}

func TestAddressFormatting(t *testing.T) {
	tests := []struct {
		addr *Address
		exp  string
	}{
		{
			&Address{Address: "bob@example.com"},
			"<bob@example.com>",
		},
		{
			&Address{Name: "Bob", Address: "bob@example.com"},
			`"Bob" <bob@example.com>`,
		},
		{
			// note the ö (o with an umlaut)
			&Address{Name: "Böb", Address: "bob@example.com"},
			`=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
		},
	}
	for _, test := range tests {
		s := test.addr.String()
		if s != test.exp {
			t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp)
		}
	}
}
<commit_msg>net/mail: correctly compare parsed times in the test.<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mail

import (
	"bytes"
	"io/ioutil"
	"reflect"
	"testing"
	"time"
)

var parseTests = []struct {
	in     string
	header Header
	body   string
}{
	{
		// RFC 5322, Appendix A.1.1
		in: `From: John Doe <jdoe@machine.example>
To: Mary Smith <mary@example.net>
Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example>

This is a message just to say hello.
So, "Hello".
`,
		header: Header{
			"From":       []string{"John Doe <jdoe@machine.example>"},
			"To":         []string{"Mary Smith <mary@example.net>"},
			"Subject":    []string{"Saying Hello"},
			"Date":       []string{"Fri, 21 Nov 1997 09:55:06 -0600"},
			"Message-Id": []string{"<1234@local.machine.example>"},
		},
		body: "This is a message just to say hello.\nSo, \"Hello\".\n",
	},
}

func TestParsing(t *testing.T) {
	for i, test := range parseTests {
		msg, err := ReadMessage(bytes.NewBuffer([]byte(test.in)))
		if err != nil {
			t.Errorf("test #%d: Failed parsing message: %v", i, err)
			continue
		}
		if !headerEq(msg.Header, test.header) {
			t.Errorf("test #%d: Incorrectly parsed message header.\nGot:\n%+v\nWant:\n%+v",
				i, msg.Header, test.header)
		}
		body, err := ioutil.ReadAll(msg.Body)
		if err != nil {
			t.Errorf("test #%d: Failed reading body: %v", i, err)
			continue
		}
		bodyStr := string(body)
		if bodyStr != test.body {
			t.Errorf("test #%d: Incorrectly parsed message body.\nGot:\n%+v\nWant:\n%+v",
				i, bodyStr, test.body)
		}
	}
}

func headerEq(a, b Header) bool {
	if len(a) != len(b) {
		return false
	}
	for k, as := range a {
		bs, ok := b[k]
		if !ok {
			return false
		}
		if !reflect.DeepEqual(as, bs) {
			return false
		}
	}
	return true
}

func TestDateParsing(t *testing.T) {
	tests := []struct {
		dateStr string
		exp     time.Time
	}{
		// RFC 5322, Appendix A.1.1
		{
			"Fri, 21 Nov 1997 09:55:06 -0600",
			time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
		},
		// RFC5322, Appendix A.6.2
		// Obsolete date.
		{
			"21 Nov 97 09:55:06 GMT",
			time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("GMT", 0)),
		},
	}
	for _, test := range tests {
		hdr := Header{
			"Date": []string{test.dateStr},
		}
		date, err := hdr.Date()
		if err != nil {
			t.Errorf("Failed parsing %q: %v", test.dateStr, err)
			continue
		}
		if !date.Equal(test.exp) {
			t.Errorf("Parse of %q: got %+v, want %+v", test.dateStr, date, test.exp)
		}
	}
}

func TestAddressParsing(t *testing.T) {
	tests := []struct {
		addrsStr string
		exp      []*Address
	}{
		// Bare address
		{
			`jdoe@machine.example`,
			[]*Address{{
				Address: "jdoe@machine.example",
			}},
		},
		// RFC 5322, Appendix A.1.1
		{
			`John Doe <jdoe@machine.example>`,
			[]*Address{{
				Name:    "John Doe",
				Address: "jdoe@machine.example",
			}},
		},
		// RFC 5322, Appendix A.1.2
		{
			`"Joe Q. Public" <john.q.public@example.com>`,
			[]*Address{{
				Name:    "Joe Q. Public",
				Address: "john.q.public@example.com",
			}},
		},
		{
			`Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
			[]*Address{
				{
					Name:    "Mary Smith",
					Address: "mary@x.test",
				},
				{
					Address: "jdoe@example.org",
				},
				{
					Name:    "Who?",
					Address: "one@y.test",
				},
			},
		},
		{
			`<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
			[]*Address{
				{
					Address: "boss@nil.test",
				},
				{
					Name:    `Giant; "Big" Box`,
					Address: "sysservices@example.net",
				},
			},
		},
		// RFC 5322, Appendix A.1.3
		// TODO(dsymonds): Group addresses.

		// RFC 2047 "Q"-encoded ISO-8859-1 address.
		{
			`=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg Doe`,
					Address: "joerg@example.com",
				},
			},
		},
		// RFC 2047 "Q"-encoded UTF-8 address.
		{
			`=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg Doe`,
					Address: "joerg@example.com",
				},
			},
		},
		// RFC 2047, Section 8.
		{
			`=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
			[]*Address{
				{
					Name:    `André Pirard`,
					Address: "PIRARD@vm1.ulg.ac.be",
				},
			},
		},
		// Custom example of RFC 2047 "B"-encoded ISO-8859-1 address.
		{
			`=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg`,
					Address: "joerg@example.com",
				},
			},
		},
		// Custom example of RFC 2047 "B"-encoded UTF-8 address.
		{
			`=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
			[]*Address{
				{
					Name:    `Jörg`,
					Address: "joerg@example.com",
				},
			},
		},
	}
	for _, test := range tests {
		addrs, err := newAddrParser(test.addrsStr).parseAddressList()
		if err != nil {
			t.Errorf("Failed parsing %q: %v", test.addrsStr, err)
			continue
		}
		if !reflect.DeepEqual(addrs, test.exp) {
			t.Errorf("Parse of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
		}
	}
}

func TestAddressFormatting(t *testing.T) {
	tests := []struct {
		addr *Address
		exp  string
	}{
		{
			&Address{Address: "bob@example.com"},
			"<bob@example.com>",
		},
		{
			&Address{Name: "Bob", Address: "bob@example.com"},
			`"Bob" <bob@example.com>`,
		},
		{
			// note the ö (o with an umlaut)
			&Address{Name: "Böb", Address: "bob@example.com"},
			`=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
		},
	}
	for _, test := range tests {
		s := test.addr.String()
		if s != test.exp {
			t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"os"

	ct "github.com/flynn/flynn-controller/types"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-flynn/cluster"
	"github.com/flynn/go-flynn/postgres"
	"github.com/flynn/go-flynn/resource"
	"github.com/flynn/go-sql"
	"github.com/flynn/rpcplus"
	strowgerc "github.com/flynn/strowger/client"
	"github.com/flynn/strowger/types"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/binding"
	"github.com/martini-contrib/render"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "3000"
	}
	addr := ":" + port

	db, err := postgres.Open("", "")
	if err != nil {
		log.Fatal(err)
	}

	if err := migrateDB(db.DB); err != nil {
		log.Fatal(err)
	}

	cc, err := cluster.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	sc, err := strowgerc.New()
	if err != nil {
		log.Fatal(err)
	}

	if err := discoverd.Register("flynn-controller", addr); err != nil {
		log.Fatal(err)
	}

	handler, _ := appHandler(handlerConfig{db: db, cc: cc, sc: sc, dc: discoverd.DefaultClient})
	log.Fatal(http.ListenAndServe(addr, handler))
}

type dbWrapper interface {
	Database() *sql.DB
	DSN() string
	Close() error
}

type handlerConfig struct {
	db dbWrapper
	cc clusterClient
	sc strowgerc.Client
	dc *discoverd.Client
}

func appHandler(c handlerConfig) (http.Handler, *martini.Martini) {
	r := martini.NewRouter()
	m := martini.New()
	m.Use(martini.Logger())
	m.Use(martini.Recovery())
	m.Use(render.Renderer())
	m.Action(r.Handle)

	d := NewDB(c.db)

	providerRepo := NewProviderRepo(d)
	keyRepo := NewKeyRepo(d)
	resourceRepo := NewResourceRepo(d)
	appRepo := NewAppRepo(d)
	artifactRepo := NewArtifactRepo(d)
	releaseRepo := NewReleaseRepo(d)
	formationRepo := NewFormationRepo(d, appRepo, releaseRepo, artifactRepo)
	m.Map(resourceRepo)
	m.Map(appRepo)
	m.Map(artifactRepo)
	m.Map(releaseRepo)
	m.Map(formationRepo)
	m.Map(c.dc)
	m.MapTo(c.cc, (*clusterClient)(nil))
	m.MapTo(c.sc, (*strowgerc.Client)(nil))
	m.MapTo(c.dc, (*resource.DiscoverdClient)(nil))

	getAppMiddleware := crud("apps", ct.App{}, appRepo, r)
	getReleaseMiddleware := crud("releases", ct.Release{}, releaseRepo, r)
	getProviderMiddleware := crud("providers", ct.Provider{}, providerRepo, r)
	crud("artifacts", ct.Artifact{}, artifactRepo, r)
	crud("keys", ct.Key{}, keyRepo, r)

	r.Put("/apps/:apps_id/formations/:releases_id", getAppMiddleware, getReleaseMiddleware, binding.Bind(ct.Formation{}), putFormation)
	r.Get("/apps/:apps_id/formations/:releases_id", getAppMiddleware, getFormationMiddleware, getFormation)
	r.Delete("/apps/:apps_id/formations/:releases_id", getAppMiddleware, getFormationMiddleware, deleteFormation)
	r.Get("/apps/:apps_id/formations", getAppMiddleware, listFormations)

	r.Post("/apps/:apps_id/jobs", getAppMiddleware, binding.Bind(ct.NewJob{}), runJob)
	r.Get("/apps/:apps_id/jobs", getAppMiddleware, jobList)
	r.Delete("/apps/:apps_id/jobs/:jobs_id", getAppMiddleware, connectHostMiddleware, killJob)
	r.Get("/apps/:apps_id/jobs/:jobs_id/log", getAppMiddleware, connectHostMiddleware, jobLog)

	r.Put("/apps/:apps_id/release", getAppMiddleware, binding.Bind(releaseID{}), setAppRelease)
	r.Get("/apps/:apps_id/release", getAppMiddleware, getAppRelease)

	r.Post("/providers/:providers_id/resources", getProviderMiddleware, binding.Bind(ct.ResourceReq{}), resourceServerMiddleware, provisionResource)
	r.Get("/providers/:providers_id/resources", getProviderMiddleware, getProviderResources)
	r.Get("/providers/:providers_id/resources/:resources_id", getProviderMiddleware, getResourceMiddleware, getResource)
	r.Put("/providers/:providers_id/resources/:resources_id", getProviderMiddleware, binding.Bind(ct.Resource{}), putResource)
	r.Get("/apps/:apps_id/resources", getAppMiddleware, getAppResources)

	r.Post("/apps/:apps_id/routes", getAppMiddleware, binding.Bind(strowger.Route{}), createRoute)
	r.Get("/apps/:apps_id/routes", getAppMiddleware, getRouteList)
	r.Get("/apps/:apps_id/routes/:routes_type/:routes_id", getAppMiddleware, getRouteMiddleware, getRoute)
	r.Delete("/apps/:apps_id/routes/:routes_type/:routes_id", getAppMiddleware, getRouteMiddleware, deleteRoute)

	return rpcMuxHandler(m, rpcHandler(formationRepo)), m
}

func rpcMuxHandler(main http.Handler, rpch http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == rpcplus.DefaultRPCPath {
			rpch.ServeHTTP(w, r)
		} else {
			main.ServeHTTP(w, r)
		}
	})
}

func putFormation(formation ct.Formation, app *ct.App, release *ct.Release, repo *FormationRepo, r render.Render) {
	formation.AppID = app.ID
	formation.ReleaseID = release.ID
	err := repo.Add(&formation)
	if app.Protected {
		for typ := range release.Processes {
			if formation.Processes[typ] == 0 {
				r.JSON(400, struct{}{})
				return
			}
		}
	}
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, &formation)
}

func getFormationMiddleware(c martini.Context, app *ct.App, params martini.Params, repo *FormationRepo, w http.ResponseWriter) {
	formation, err := repo.Get(app.ID, params["releases_id"])
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(404)
			return
		}
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	c.Map(formation)
}

func getFormation(formation *ct.Formation, r render.Render) {
	r.JSON(200, formation)
}

func deleteFormation(formation *ct.Formation, repo *FormationRepo, w http.ResponseWriter) {
	err := repo.Remove(formation.AppID, formation.ReleaseID)
	if err != nil {
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	w.WriteHeader(200)
}

func listFormations(app *ct.App, repo *FormationRepo, r render.Render) {
	list, err := repo.List(app.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, list)
}

type releaseID struct {
	ID string `json:"id"`
}

func setAppRelease(app *ct.App, rid releaseID, apps *AppRepo, releases *ReleaseRepo, formations *FormationRepo, r render.Render) {
	rel, err := releases.Get(rid.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	release := rel.(*ct.Release)
	apps.SetRelease(app.ID, release.ID)

	// TODO: use transaction/lock
	fs, err := formations.List(app.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	if len(fs) == 1 && fs[0].ReleaseID != release.ID {
		if err := formations.Add(&ct.Formation{
			AppID:     app.ID,
			ReleaseID: release.ID,
			Processes: fs[0].Processes,
		}); err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
		if err := formations.Remove(app.ID, fs[0].ReleaseID); err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
	}

	r.JSON(200, release)
}

func getAppRelease(app *ct.App, apps *AppRepo, r render.Render, w http.ResponseWriter) {
	release, err := apps.GetRelease(app.ID)
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(404)
			return
		}
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	r.JSON(200, release)
}

func resourceServerMiddleware(c martini.Context, p *ct.Provider, dc resource.DiscoverdClient, w http.ResponseWriter) {
	server, err := resource.NewServerWithDiscoverd(p.URL, dc)
	if err != nil {
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	c.Map(server)
	c.Next()
	server.Close()
}

func putResource(p *ct.Provider, params martini.Params, resource ct.Resource, repo *ResourceRepo, r render.Render) {
	resource.ID = params["resources_id"]
	resource.ProviderID = p.ID
	if err := repo.Add(&resource); err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, &resource)
}

func provisionResource(rs *resource.Server, p *ct.Provider, req ct.ResourceReq, repo *ResourceRepo, r render.Render) {
	var config []byte
	if req.Config != nil {
		config = *req.Config
	} else {
		config = []byte(`{}`)
	}
	data, err := rs.Provision(config)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}

	res := &ct.Resource{
		ProviderID: p.ID,
		ExternalID: data.ID,
		Env:        data.Env,
		Apps:       req.Apps,
	}
	if err := repo.Add(res); err != nil {
		// TODO: attempt to "rollback" provisioning
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, res)
}

func getResourceMiddleware(c martini.Context, params martini.Params, repo *ResourceRepo, w http.ResponseWriter) {
	resource, err := repo.Get(params["resources_id"])
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(404)
			return
		}
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	c.Map(resource)
}

func getResource(resource *ct.Resource, r render.Render) {
	r.JSON(200, resource)
}

func getProviderResources(p *ct.Provider, repo *ResourceRepo, r render.Render) {
	res, err := repo.ProviderList(p.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, res)
}

func getAppResources(app *ct.App, repo *ResourceRepo, r render.Render) {
	res, err := repo.AppList(app.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, res)
}
<commit_msg>Add ping endpoint<commit_after>package main

import (
	"log"
	"net/http"
	"os"

	ct "github.com/flynn/flynn-controller/types"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-flynn/cluster"
	"github.com/flynn/go-flynn/postgres"
	"github.com/flynn/go-flynn/resource"
	"github.com/flynn/go-sql"
	"github.com/flynn/rpcplus"
	strowgerc "github.com/flynn/strowger/client"
	"github.com/flynn/strowger/types"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/binding"
	"github.com/martini-contrib/render"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "3000"
	}
	addr := ":" + port

	db, err := postgres.Open("", "")
	if err != nil {
		log.Fatal(err)
	}

	if err := migrateDB(db.DB); err != nil {
		log.Fatal(err)
	}

	cc, err := cluster.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	sc, err := strowgerc.New()
	if err != nil {
		log.Fatal(err)
	}

	if err := discoverd.Register("flynn-controller", addr); err != nil {
		log.Fatal(err)
	}

	handler, _ := appHandler(handlerConfig{db: db, cc: cc, sc: sc, dc: discoverd.DefaultClient})
	log.Fatal(http.ListenAndServe(addr, handler))
}

type dbWrapper interface {
	Database() *sql.DB
	DSN() string
	Close() error
}

type handlerConfig struct {
	db dbWrapper
	cc clusterClient
	sc strowgerc.Client
	dc *discoverd.Client
}

func appHandler(c handlerConfig) (http.Handler, *martini.Martini) {
	r := martini.NewRouter()
	m := martini.New()
	m.Use(martini.Logger())
	m.Use(martini.Recovery())
	m.Use(render.Renderer())
	m.Action(r.Handle)

	d := NewDB(c.db)

	providerRepo := NewProviderRepo(d)
	keyRepo := NewKeyRepo(d)
	resourceRepo := NewResourceRepo(d)
	appRepo := NewAppRepo(d)
	artifactRepo := NewArtifactRepo(d)
	releaseRepo := NewReleaseRepo(d)
	formationRepo := NewFormationRepo(d, appRepo, releaseRepo, artifactRepo)
	m.Map(resourceRepo)
	m.Map(appRepo)
	m.Map(artifactRepo)
	m.Map(releaseRepo)
	m.Map(formationRepo)
	m.Map(c.dc)
	m.MapTo(c.cc, (*clusterClient)(nil))
	m.MapTo(c.sc, (*strowgerc.Client)(nil))
	m.MapTo(c.dc, (*resource.DiscoverdClient)(nil))

	getAppMiddleware := crud("apps", ct.App{}, appRepo, r)
	getReleaseMiddleware := crud("releases", ct.Release{}, releaseRepo, r)
	getProviderMiddleware := crud("providers", ct.Provider{}, providerRepo, r)
	crud("artifacts", ct.Artifact{}, artifactRepo, r)
	crud("keys", ct.Key{}, keyRepo, r)

	r.Get("/ping", func(w http.ResponseWriter) {
		w.WriteHeader(200)
	})

	r.Put("/apps/:apps_id/formations/:releases_id", getAppMiddleware, getReleaseMiddleware, binding.Bind(ct.Formation{}), putFormation)
	r.Get("/apps/:apps_id/formations/:releases_id", getAppMiddleware, getFormationMiddleware, getFormation)
	r.Delete("/apps/:apps_id/formations/:releases_id", getAppMiddleware, getFormationMiddleware, deleteFormation)
	r.Get("/apps/:apps_id/formations", getAppMiddleware, listFormations)

	r.Post("/apps/:apps_id/jobs", getAppMiddleware, binding.Bind(ct.NewJob{}), runJob)
	r.Get("/apps/:apps_id/jobs", getAppMiddleware, jobList)
	r.Delete("/apps/:apps_id/jobs/:jobs_id", getAppMiddleware, connectHostMiddleware, killJob)
	r.Get("/apps/:apps_id/jobs/:jobs_id/log", getAppMiddleware, connectHostMiddleware, jobLog)

	r.Put("/apps/:apps_id/release", getAppMiddleware, binding.Bind(releaseID{}), setAppRelease)
	r.Get("/apps/:apps_id/release", getAppMiddleware, getAppRelease)

	r.Post("/providers/:providers_id/resources", getProviderMiddleware, binding.Bind(ct.ResourceReq{}), resourceServerMiddleware, provisionResource)
	r.Get("/providers/:providers_id/resources", getProviderMiddleware, getProviderResources)
	r.Get("/providers/:providers_id/resources/:resources_id", getProviderMiddleware, getResourceMiddleware, getResource)
	r.Put("/providers/:providers_id/resources/:resources_id", getProviderMiddleware, binding.Bind(ct.Resource{}), putResource)
	r.Get("/apps/:apps_id/resources", getAppMiddleware, getAppResources)

	r.Post("/apps/:apps_id/routes", getAppMiddleware, binding.Bind(strowger.Route{}), createRoute)
	r.Get("/apps/:apps_id/routes", getAppMiddleware, getRouteList)
	r.Get("/apps/:apps_id/routes/:routes_type/:routes_id", getAppMiddleware, getRouteMiddleware, getRoute)
	r.Delete("/apps/:apps_id/routes/:routes_type/:routes_id", getAppMiddleware, getRouteMiddleware, deleteRoute)

	return rpcMuxHandler(m, rpcHandler(formationRepo)), m
}

func rpcMuxHandler(main http.Handler, rpch http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == rpcplus.DefaultRPCPath {
			rpch.ServeHTTP(w, r)
		} else {
			main.ServeHTTP(w, r)
		}
	})
}

func putFormation(formation ct.Formation, app *ct.App, release *ct.Release, repo *FormationRepo, r render.Render) {
	formation.AppID = app.ID
	formation.ReleaseID = release.ID
	err := repo.Add(&formation)
	if app.Protected {
		for typ := range release.Processes {
			if formation.Processes[typ] == 0 {
				r.JSON(400, struct{}{})
				return
			}
		}
	}
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, &formation)
}

func getFormationMiddleware(c martini.Context, app *ct.App, params martini.Params, repo *FormationRepo, w http.ResponseWriter) {
	formation, err := repo.Get(app.ID, params["releases_id"])
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(404)
			return
		}
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	c.Map(formation)
}

func getFormation(formation *ct.Formation, r render.Render) {
	r.JSON(200, formation)
}

func deleteFormation(formation *ct.Formation, repo *FormationRepo, w http.ResponseWriter) {
	err := repo.Remove(formation.AppID, formation.ReleaseID)
	if err != nil {
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	w.WriteHeader(200)
}

func listFormations(app *ct.App, repo *FormationRepo, r render.Render) {
	list, err := repo.List(app.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, list)
}

type releaseID struct {
	ID string `json:"id"`
}

func setAppRelease(app *ct.App, rid releaseID, apps *AppRepo, releases *ReleaseRepo, formations *FormationRepo, r render.Render) {
	rel, err := releases.Get(rid.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	release := rel.(*ct.Release)
	apps.SetRelease(app.ID, release.ID)

	// TODO: use transaction/lock
	fs, err := formations.List(app.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	if len(fs) == 1 && fs[0].ReleaseID != release.ID {
		if err := formations.Add(&ct.Formation{
			AppID:     app.ID,
			ReleaseID: release.ID,
			Processes: fs[0].Processes,
		}); err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
		if err := formations.Remove(app.ID, fs[0].ReleaseID); err != nil {
			log.Println(err)
			r.JSON(500, struct{}{})
			return
		}
	}

	r.JSON(200, release)
}

func getAppRelease(app *ct.App, apps *AppRepo, r render.Render, w http.ResponseWriter) {
	release, err := apps.GetRelease(app.ID)
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(404)
			return
		}
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	r.JSON(200, release)
}

func resourceServerMiddleware(c martini.Context, p *ct.Provider, dc resource.DiscoverdClient, w http.ResponseWriter) {
	server, err := resource.NewServerWithDiscoverd(p.URL, dc)
	if err != nil {
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	c.Map(server)
	c.Next()
	server.Close()
}

func putResource(p *ct.Provider, params martini.Params, resource ct.Resource, repo *ResourceRepo, r render.Render) {
	resource.ID = params["resources_id"]
	resource.ProviderID = p.ID
	if err := repo.Add(&resource); err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, &resource)
}

func provisionResource(rs *resource.Server, p *ct.Provider, req ct.ResourceReq, repo *ResourceRepo, r render.Render) {
	var config []byte
	if req.Config != nil {
		config = *req.Config
	} else {
		config = []byte(`{}`)
	}
	data, err := rs.Provision(config)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}

	res := &ct.Resource{
		ProviderID: p.ID,
		ExternalID: data.ID,
		Env:        data.Env,
		Apps:       req.Apps,
	}
	if err := repo.Add(res); err != nil {
		// TODO: attempt to "rollback" provisioning
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, res)
}

func getResourceMiddleware(c martini.Context, params martini.Params, repo *ResourceRepo, w http.ResponseWriter) {
	resource, err := repo.Get(params["resources_id"])
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(404)
			return
		}
		log.Println(err)
		w.WriteHeader(500)
		return
	}
	c.Map(resource)
}

func getResource(resource *ct.Resource, r render.Render) {
	r.JSON(200, resource)
}

func getProviderResources(p *ct.Provider, repo *ResourceRepo, r render.Render) {
	res, err := repo.ProviderList(p.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, res)
}

func getAppResources(app *ct.App, repo *ResourceRepo, r render.Render) {
	res, err := repo.AppList(app.ID)
	if err != nil {
		log.Println(err)
		r.JSON(500, struct{}{})
		return
	}
	r.JSON(200, res)
}
<|endoftext|>


<commit_before>package dexcom_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/tidepool-org/platform/dexcom/test"
	"github.com/tidepool-org/platform/pointer"
	"github.com/tidepool-org/platform/structure/validator"

	"github.com/tidepool-org/platform/dexcom"
)

var _ = Describe("Event", func() {
	It("EventTypeCarbs is expected", func() {
		Expect(dexcom.EventTypeCarbs).To(Equal("carbs"))
	})

	It("EventTypeExercise is expected", func() {
		Expect(dexcom.EventTypeExercise).To(Equal("exercise"))
	})

	It("EventTypeHealth is expected", func() {
		Expect(dexcom.EventTypeHealth).To(Equal("health"))
	})

	It("EventTypeInsulin is expected", func() {
		Expect(dexcom.EventTypeInsulin).To(Equal("insulin"))
	})

	It("EventUnitCarbsGrams is expected", func() {
		Expect(dexcom.EventUnitCarbsGrams).To(Equal("grams"))
	})

	It("EventValueCarbsGramsMaximum is expected", func() {
		Expect(dexcom.EventValueCarbsGramsMaximum).To(Equal(1000.0))
	})

	It("EventValueCarbsGramsMinimum is expected", func() {
		Expect(dexcom.EventValueCarbsGramsMinimum).To(Equal(0.0))
	})

	It("EventSubTypeExerciseLight is expected", func() {
		Expect(dexcom.EventSubTypeExerciseLight).To(Equal("light"))
	})

	It("EventSubTypeExerciseMedium is expected", func() {
		Expect(dexcom.EventSubTypeExerciseMedium).To(Equal("medium"))
	})

	It("EventSubTypeExerciseHeavy is expected", func() {
		Expect(dexcom.EventSubTypeExerciseHeavy).To(Equal("heavy"))
	})

	It("EventUnitExerciseMinutes is expected", func() {
		Expect(dexcom.EventUnitExerciseMinutes).To(Equal("minutes"))
	})

	It("EventValueExerciseMinutesMaximum is expected", func() {
		Expect(dexcom.EventValueExerciseMinutesMaximum).To(Equal(10080.0))
	})

	It("EventValueExerciseMinutesMinimum is expected", func() {
		Expect(dexcom.EventValueExerciseMinutesMinimum).To(Equal(0.0))
	})

	It("EventSubTypeHealthAlcohol is expected", func() {
		Expect(dexcom.EventSubTypeHealthAlcohol).To(Equal("alcohol"))
	})

	It("EventSubTypeHealthCycle is expected", func() {
		Expect(dexcom.EventSubTypeHealthCycle).To(Equal("cycle"))
	})

	It("EventSubTypeHealthHighSymptoms is expected", func() {
		Expect(dexcom.EventSubTypeHealthHighSymptoms).To(Equal("highSymptoms"))
	})

	It("EventSubTypeHealthIllness is expected", func() {
		Expect(dexcom.EventSubTypeHealthIllness).To(Equal("illness"))
	})

	It("EventSubTypeHealthLowSymptoms is expected", func() {
		Expect(dexcom.EventSubTypeHealthLowSymptoms).To(Equal("lowSymptoms"))
	})

	It("EventSubTypeHealthStress is expected", func() {
		Expect(dexcom.EventSubTypeHealthStress).To(Equal("stress"))
	})

	It("EventSubTypeInsulinFastActing is expected", func() {
		Expect(dexcom.EventSubTypeInsulinFastActing).To(Equal("fastActing"))
	})

	It("EventSubTypeInsulinLongActing is expected", func() {
		Expect(dexcom.EventSubTypeInsulinLongActing).To(Equal("longActing"))
	})

	It("EventUnitInsulinUnits is expected", func() {
		Expect(dexcom.EventUnitInsulinUnits).To(Equal("units"))
	})

	It("EventValueInsulinUnitsMaximum is expected", func() {
		Expect(dexcom.EventValueInsulinUnitsMaximum).To(Equal(250.0))
	})

	It("EventValueInsulinUnitsMinimum is expected", func() {
		Expect(dexcom.EventValueInsulinUnitsMinimum).To(Equal(0.0))
	})

	It("EventStatusCreated is expected", func() {
		Expect(dexcom.EventStatusCreated).To(Equal("created"))
	})

	It("EventStatusDeleted is expected", func() {
		Expect(dexcom.EventStatusDeleted).To(Equal("deleted"))
	})

	It("EventTypes returns expected", func() {
		Expect(dexcom.EventTypes()).To(Equal([]string{"carbs", "exercise", "health", "insulin"}))
	})

	It("EventSubTypesExercise returns expected", func() {
		Expect(dexcom.EventSubTypesExercise()).To(Equal([]string{"light", "medium", "heavy"}))
	})

	It("EventSubTypesHealth returns expected", func() {
		Expect(dexcom.EventSubTypesHealth()).To(Equal([]string{"alcohol", "cycle", "highSymptoms", "illness", "lowSymptoms", "stress"}))
	})

	It("EventSubTypesInsulin returns expected", func() {
		Expect(dexcom.EventSubTypesInsulin()).To(Equal([]string{"fastActing", "longActing"}))
	})

	It("EventStatuses returns expected", func() {
		Expect(dexcom.EventStatuses()).To(Equal([]string{"created", "deleted"}))
	})

	Describe("Validate", func() {
		It("Allows health events value to be 0", func() {
			event := test.RandomEvent()
			event.Type = pointer.FromString(dexcom.EventTypeHealth)
			event.SubType = pointer.FromString(dexcom.EventSubTypeHealthIllness)
			event.Value = pointer.FromFloat64(0)

			validator := validator.New()
			event.Validate(validator)

			Expect(validator.Error()).ToNot(HaveOccurred())
		})
	})
})
<commit_msg>Make sure all health event attributes are valid<commit_after>package dexcom_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/tidepool-org/platform/dexcom/test"
	"github.com/tidepool-org/platform/pointer"
	"github.com/tidepool-org/platform/structure/validator"

	"github.com/tidepool-org/platform/dexcom"
)

var _ = Describe("Event", func() {
	It("EventTypeCarbs is expected", func() {
		Expect(dexcom.EventTypeCarbs).To(Equal("carbs"))
	})

	It("EventTypeExercise is expected", func() {
		Expect(dexcom.EventTypeExercise).To(Equal("exercise"))
	})

	It("EventTypeHealth is expected", func() {
		Expect(dexcom.EventTypeHealth).To(Equal("health"))
	})

	It("EventTypeInsulin is expected", func() {
		Expect(dexcom.EventTypeInsulin).To(Equal("insulin"))
	})

	It("EventUnitCarbsGrams is expected", func() {
		Expect(dexcom.EventUnitCarbsGrams).To(Equal("grams"))
	})

	It("EventValueCarbsGramsMaximum is expected", func() {
		Expect(dexcom.EventValueCarbsGramsMaximum).To(Equal(1000.0))
	})

	It("EventValueCarbsGramsMinimum is expected", func() {
		Expect(dexcom.EventValueCarbsGramsMinimum).To(Equal(0.0))
	})

	It("EventSubTypeExerciseLight is expected", func() {
		Expect(dexcom.EventSubTypeExerciseLight).To(Equal("light"))
	})

	It("EventSubTypeExerciseMedium is expected", func() {
		Expect(dexcom.EventSubTypeExerciseMedium).To(Equal("medium"))
	})

	It("EventSubTypeExerciseHeavy is expected", func() {
		Expect(dexcom.EventSubTypeExerciseHeavy).To(Equal("heavy"))
	})

	It("EventUnitExerciseMinutes is expected", func() {
		Expect(dexcom.EventUnitExerciseMinutes).To(Equal("minutes"))
	})

	It("EventValueExerciseMinutesMaximum is expected", func() {
		Expect(dexcom.EventValueExerciseMinutesMaximum).To(Equal(10080.0))
	})

	It("EventValueExerciseMinutesMinimum is expected", func() {
		Expect(dexcom.EventValueExerciseMinutesMinimum).To(Equal(0.0))
	})

	It("EventSubTypeHealthAlcohol is expected", func() {
		Expect(dexcom.EventSubTypeHealthAlcohol).To(Equal("alcohol"))
	})

	It("EventSubTypeHealthCycle is expected", func() {
		Expect(dexcom.EventSubTypeHealthCycle).To(Equal("cycle"))
	})

	It("EventSubTypeHealthHighSymptoms is expected", func() {
		Expect(dexcom.EventSubTypeHealthHighSymptoms).To(Equal("highSymptoms"))
	})

	It("EventSubTypeHealthIllness is expected", func() {
		Expect(dexcom.EventSubTypeHealthIllness).To(Equal("illness"))
	})

	It("EventSubTypeHealthLowSymptoms is expected", func() {
		Expect(dexcom.EventSubTypeHealthLowSymptoms).To(Equal("lowSymptoms"))
	})

	It("EventSubTypeHealthStress is expected", func() {
		Expect(dexcom.EventSubTypeHealthStress).To(Equal("stress"))
	})

	It("EventSubTypeInsulinFastActing is expected", func() {
		Expect(dexcom.EventSubTypeInsulinFastActing).To(Equal("fastActing"))
	})

	It("EventSubTypeInsulinLongActing is expected", func() {
		Expect(dexcom.EventSubTypeInsulinLongActing).To(Equal("longActing"))
	})

	It("EventUnitInsulinUnits is expected", func() {
		Expect(dexcom.EventUnitInsulinUnits).To(Equal("units"))
	})

	It("EventValueInsulinUnitsMaximum is expected", func() {
		Expect(dexcom.EventValueInsulinUnitsMaximum).To(Equal(250.0))
	})

	It("EventValueInsulinUnitsMinimum is expected", func() {
		Expect(dexcom.EventValueInsulinUnitsMinimum).To(Equal(0.0))
	})

	It("EventStatusCreated is expected", func() {
		Expect(dexcom.EventStatusCreated).To(Equal("created"))
	})

	It("EventStatusDeleted is expected", func() {
		Expect(dexcom.EventStatusDeleted).To(Equal("deleted"))
	})

	It("EventTypes returns expected", func() {
		Expect(dexcom.EventTypes()).To(Equal([]string{"carbs", "exercise", "health", "insulin"}))
	})

	It("EventSubTypesExercise returns expected", func() {
		Expect(dexcom.EventSubTypesExercise()).To(Equal([]string{"light", "medium", "heavy"}))
	})

	It("EventSubTypesHealth returns expected", func() {
		Expect(dexcom.EventSubTypesHealth()).To(Equal([]string{"alcohol", "cycle", "highSymptoms", "illness", "lowSymptoms", "stress"}))
	})

	It("EventSubTypesInsulin returns expected", func() {
		Expect(dexcom.EventSubTypesInsulin()).To(Equal([]string{"fastActing", "longActing"}))
	})

	It("EventStatuses returns expected", func() {
		Expect(dexcom.EventStatuses()).To(Equal([]string{"created", "deleted"}))
	})

	Describe("Validate", func() {
		It("Allows health events value to be 0", func() {
			event := test.RandomEvent()
			event.Type = pointer.FromString(dexcom.EventTypeHealth)
			event.SubType = pointer.FromString(dexcom.EventSubTypeHealthIllness)
			event.Unit = nil
			event.Value = pointer.FromFloat64(0)

			validator := validator.New()
			event.Validate(validator)

			Expect(validator.Error()).ToNot(HaveOccurred())
		})
	})
})
<|endoftext|>


<commit_before>package gotest

import (
	"fmt"
	"go/ast"
	"go/doc"
	"go/parser"
	"go/token"
	"os"
	"path"
	"strings"
	"text/template"
	"unicode"
	"unicode/utf8"
)

type testDescr struct {
	Package        string
	Main           string
	TestFunctions  []string
	BenchFunctions []string
	Examples       []*doc.Example
	CoverVars      []CoverVar
	Imports        []string
	Coverage       bool
	Benchmark      bool
}

// WriteTestMain templates a test main file from the given sources to the given output file.
// This mimics what 'go test' does, although we do not currently support benchmarks or examples.
func WriteTestMain(pkgDir, importPath string, sources []string, output string, coverage bool, coverVars []CoverVar, benchmark bool) error {
	testDescr, err := parseTestSources(sources)
	if err != nil {
		return err
	}
	testDescr.Coverage = coverage
	testDescr.CoverVars = coverVars
	if len(testDescr.TestFunctions) > 0 || len(testDescr.BenchFunctions) > 0 || len(testDescr.Examples) > 0 {
		// Can't set this if there are no test functions, it'll be an unused import.
		testDescr.Imports = extraImportPaths(testDescr.Package, pkgDir, importPath, coverVars)
	}

	testDescr.Benchmark = benchmark

	f, err := os.Create(output)
	if err != nil {
		return err
	}
	defer f.Close()
	// This might be consumed by other things.
	fmt.Printf("Package: %s\n", testDescr.Package)
	return testMainTmpl.Execute(f, testDescr)
}

// extraImportPaths returns the set of extra import paths that are needed.
func extraImportPaths(pkg, pkgDir, importPath string, coverVars []CoverVar) []string {
	pkgDir = collapseFinalDir(path.Join(pkgDir, pkg), importPath)
	ret := []string{fmt.Sprintf("%s \"%s\"", pkg, path.Join(importPath, pkgDir))}
	for i, v := range coverVars {
		name := fmt.Sprintf("_cover%d", i)
		coverVars[i].ImportName = name
		ret = append(ret, fmt.Sprintf("%s \"%s\"", name, path.Join(importPath, v.ImportPath)))
	}
	return ret
}

// parseTestSources parses the test sources and returns the package and set of test functions in them.
func parseTestSources(sources []string) (testDescr, error) {
	descr := testDescr{}
	for _, source := range sources {
		f, err := parser.ParseFile(token.NewFileSet(), source, nil, parser.ParseComments)
		if err != nil {
			log.Errorf("Error parsing %s: %s", source, err)
			return descr, err
		}
		descr.Package = f.Name.Name
		// If we're testing main, we will get errors from it clashing with func main.
		if descr.Package == "main" {
			descr.Package = "_main"
		}
		for _, d := range f.Decls {
			if fd, ok := d.(*ast.FuncDecl); ok && fd.Recv == nil {
				name := fd.Name.String()
				if isTestMain(fd) {
					descr.Main = name
				} else if isTest(fd, 1, name, "Test") {
					descr.TestFunctions = append(descr.TestFunctions, name)
				} else if isTest(fd, 1, name, "Benchmark") {
					descr.BenchFunctions = append(descr.BenchFunctions, name)
				}
			}
		}
		// Get doc to find the examples for us :)
		descr.Examples = append(descr.Examples, doc.Examples(f)...)
	}
	return descr, nil
}

// isTestMain returns true if fn is a TestMain(m *testing.M) function.
// Copied from Go sources.
func isTestMain(fn *ast.FuncDecl) bool {
	if fn.Name.String() != "TestMain" ||
		fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
		fn.Type.Params == nil ||
		len(fn.Type.Params.List) != 1 ||
		len(fn.Type.Params.List[0].Names) > 1 {
		return false
	}
	ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
	if !ok {
		return false
	}
	// We can't easily check that the type is *testing.M
	// because we don't know how testing has been imported,
	// but at least check that it's *M or *something.M.
	if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" {
		return true
	}
	if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" {
		return true
	}
	return false
}

// isTest returns true if the given function looks like a test.
// Copied from Go sources.
func isTest(fd *ast.FuncDecl, argLen int, name, prefix string) bool {
	if !strings.HasPrefix(name, prefix) || fd.Recv != nil || len(fd.Type.Params.List) != argLen {
		return false
	} else if len(name) == len(prefix) { // "Test" is ok
		return true
	}

	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
	return !unicode.IsLower(rune)
}

// testMainTmpl is the template for our test main, copied from Go's builtin one.
// Some bits are excluded because we don't support them and/or do them differently.
var testMainTmpl = template.Must(template.New("main").Parse(`
package main

import (
	_gostdlib_os "os"
	{{if not .Benchmark}}_gostdlib_strings "strings"{{end}}
	_gostdlib_testing "testing"
	_gostdlib_testdeps "testing/internal/testdeps"

{{range .Imports}}
	{{.}}
{{end}}
)

var tests = []_gostdlib_testing.InternalTest{
{{range .TestFunctions}}
	{"{{.}}", {{$.Package}}.{{.}}},
{{end}}
}
var examples = []_gostdlib_testing.InternalExample{
{{range .Examples}}
	{"{{.Name}}", {{$.Package}}.Example{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}

var benchmarks = []_gostdlib_testing.InternalBenchmark{
{{range .BenchFunctions}}
	{"{{.}}", {{$.Package}}.{{.}}},
{{end}}
}

{{if .Coverage}}

// Only updated by init functions, so no need for atomicity.
var (
	coverCounters = make(map[string][]uint32)
	coverBlocks = make(map[string][]_gostdlib_testing.CoverBlock)
)

func init() {
	{{range $i, $c := .CoverVars}}
	coverRegisterFile({{printf "%q" $c.File}}, {{$c.ImportName}}.{{$c.Var}}.Count[:], {{$c.ImportName}}.{{$c.Var}}.Pos[:], {{$c.ImportName}}.{{$c.Var}}.NumStmt[:])
	{{end}}
}

func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) {
	if 3*len(counter) != len(pos) || len(counter) != len(numStmts) {
		panic("coverage: mismatched sizes")
	}
	if coverCounters[fileName] != nil {
		// Already registered.
		return
	}
	coverCounters[fileName] = counter
	block := make([]_gostdlib_testing.CoverBlock, len(counter))
	for i := range counter {
		block[i] = _gostdlib_testing.CoverBlock{
			Line0: pos[3*i+0],
			Col0: uint16(pos[3*i+2]),
			Line1: pos[3*i+1],
			Col1: uint16(pos[3*i+2]>>16),
			Stmts: numStmts[i],
		}
	}
	coverBlocks[fileName] = block
}
{{end}}

var testDeps = _gostdlib_testdeps.TestDeps{}

func main() {
{{if .Coverage}}
	_gostdlib_testing.RegisterCover(_gostdlib_testing.Cover{
		Mode: "set",
		Counters: coverCounters,
		Blocks: coverBlocks,
		CoveredPackages: "",
	})
    coverfile := _gostdlib_os.Getenv("COVERAGE_FILE")
    args := []string{_gostdlib_os.Args[0], "-test.v", "-test.coverprofile", coverfile}
{{else}}
    args := []string{_gostdlib_os.Args[0], "-test.v"}
{{end}}
{{if not .Benchmark}}
    testVar := _gostdlib_os.Getenv("TESTS")
    if testVar != "" {
		testVar = _gostdlib_strings.Replace(testVar, " ", "|", -1)
		args = append(args, "-test.run", testVar)
    }
    _gostdlib_os.Args = append(args, _gostdlib_os.Args[1:]...)
	m := _gostdlib_testing.MainStart(testDeps, tests, nil, examples)
{{else}}
	args = append(args, "-test.bench", ".*")
	_gostdlib_os.Args = append(args, _gostdlib_os.Args[1:]...)
	m := _gostdlib_testing.MainStart(testDeps, nil, benchmarks, nil)
{{end}}

{{if .Main}}
	{{.Package}}.{{.Main}}(m)
{{else}}
	_gostdlib_os.Exit(m.Run())
{{end}}
}
`))
<commit_msg>Fixed issue with go testing.TestMain when there are no tests (#1245)<commit_after>package gotest

import (
	"fmt"
	"go/ast"
	"go/doc"
	"go/parser"
	"go/token"
	"os"
	"path"
	"strings"
	"text/template"
	"unicode"
	"unicode/utf8"
)

type testDescr struct {
	Package        string
	Main           string
	TestFunctions  []string
	BenchFunctions []string
	Examples       []*doc.Example
	CoverVars      []CoverVar
	Imports        []string
	Coverage       bool
	Benchmark      bool
}

// WriteTestMain templates a test main file from the given sources to the given output file.
// This mimics what 'go test' does, although we do not currently support benchmarks or examples.
func WriteTestMain(pkgDir, importPath string, sources []string, output string, coverage bool, coverVars []CoverVar, benchmark bool) error {
	testDescr, err := parseTestSources(sources)
	if err != nil {
		return err
	}
	testDescr.Coverage = coverage
	testDescr.CoverVars = coverVars
	if len(testDescr.TestFunctions) > 0 || len(testDescr.BenchFunctions) > 0 || len(testDescr.Examples) > 0 || testDescr.Main != "" {
		// Can't set this if there are no test functions, it'll be an unused import.
		testDescr.Imports = extraImportPaths(testDescr.Package, pkgDir, importPath, testDescr.CoverVars)
	}

	testDescr.Benchmark = benchmark

	f, err := os.Create(output)
	if err != nil {
		return err
	}
	defer f.Close()
	// This might be consumed by other things.
	fmt.Printf("Package: %s\n", testDescr.Package)
	return testMainTmpl.Execute(f, testDescr)
}

// extraImportPaths returns the set of extra import paths that are needed.
func extraImportPaths(pkg, pkgDir, importPath string, coverVars []CoverVar) []string {
	pkgDir = collapseFinalDir(path.Join(pkgDir, pkg), importPath)
	ret := []string{fmt.Sprintf("%s \"%s\"", pkg, path.Join(importPath, pkgDir))}
	for i, v := range coverVars {
		name := fmt.Sprintf("_cover%d", i)
		coverVars[i].ImportName = name
		ret = append(ret, fmt.Sprintf("%s \"%s\"", name, path.Join(importPath, v.ImportPath)))
	}
	return ret
}

// parseTestSources parses the test sources and returns the package and set of test functions in them.
func parseTestSources(sources []string) (testDescr, error) {
	descr := testDescr{}
	for _, source := range sources {
		f, err := parser.ParseFile(token.NewFileSet(), source, nil, parser.ParseComments)
		if err != nil {
			log.Errorf("Error parsing %s: %s", source, err)
			return descr, err
		}
		descr.Package = f.Name.Name
		// If we're testing main, we will get errors from it clashing with func main.
		if descr.Package == "main" {
			descr.Package = "_main"
		}
		for _, d := range f.Decls {
			if fd, ok := d.(*ast.FuncDecl); ok && fd.Recv == nil {
				name := fd.Name.String()
				if isTestMain(fd) {
					descr.Main = name
				} else if isTest(fd, 1, name, "Test") {
					descr.TestFunctions = append(descr.TestFunctions, name)
				} else if isTest(fd, 1, name, "Benchmark") {
					descr.BenchFunctions = append(descr.BenchFunctions, name)
				}
			}
		}
		// Get doc to find the examples for us :)
		descr.Examples = append(descr.Examples, doc.Examples(f)...)
	}
	return descr, nil
}

// isTestMain returns true if fn is a TestMain(m *testing.M) function.
// Copied from Go sources.
func isTestMain(fn *ast.FuncDecl) bool {
	if fn.Name.String() != "TestMain" ||
		fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
		fn.Type.Params == nil ||
		len(fn.Type.Params.List) != 1 ||
		len(fn.Type.Params.List[0].Names) > 1 {
		return false
	}
	ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
	if !ok {
		return false
	}
	// We can't easily check that the type is *testing.M
	// because we don't know how testing has been imported,
	// but at least check that it's *M or *something.M.
	if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" {
		return true
	}
	if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" {
		return true
	}
	return false
}

// isTest returns true if the given function looks like a test.
// Copied from Go sources.
func isTest(fd *ast.FuncDecl, argLen int, name, prefix string) bool {
	if !strings.HasPrefix(name, prefix) || fd.Recv != nil || len(fd.Type.Params.List) != argLen {
		return false
	} else if len(name) == len(prefix) { // "Test" is ok
		return true
	}

	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
	return !unicode.IsLower(rune)
}

// testMainTmpl is the template for our test main, copied from Go's builtin one.
// Some bits are excluded because we don't support them and/or do them differently.
var testMainTmpl = template.Must(template.New("main").Parse(`
package main

import (
	_gostdlib_os "os"
	{{if not .Benchmark}}_gostdlib_strings "strings"{{end}}
	_gostdlib_testing "testing"
	_gostdlib_testdeps "testing/internal/testdeps"

{{range .Imports}}
	{{.}}
{{end}}
)

var tests = []_gostdlib_testing.InternalTest{
{{range .TestFunctions}}
	{"{{.}}", {{$.Package}}.{{.}}},
{{end}}
}
var examples = []_gostdlib_testing.InternalExample{
{{range .Examples}}
	{"{{.Name}}", {{$.Package}}.Example{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}

var benchmarks = []_gostdlib_testing.InternalBenchmark{
{{range .BenchFunctions}}
	{"{{.}}", {{$.Package}}.{{.}}},
{{end}}
}

{{if .Coverage}}

// Only updated by init functions, so no need for atomicity.
var (
	coverCounters = make(map[string][]uint32)
	coverBlocks = make(map[string][]_gostdlib_testing.CoverBlock)
)

func init() {
	{{range $i, $c := .CoverVars}}
	coverRegisterFile({{printf "%q" $c.File}}, {{$c.ImportName}}.{{$c.Var}}.Count[:], {{$c.ImportName}}.{{$c.Var}}.Pos[:], {{$c.ImportName}}.{{$c.Var}}.NumStmt[:])
	{{end}}
}

func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) {
	if 3*len(counter) != len(pos) || len(counter) != len(numStmts) {
		panic("coverage: mismatched sizes")
	}
	if coverCounters[fileName] != nil {
		// Already registered.
		return
	}
	coverCounters[fileName] = counter
	block := make([]_gostdlib_testing.CoverBlock, len(counter))
	for i := range counter {
		block[i] = _gostdlib_testing.CoverBlock{
			Line0: pos[3*i+0],
			Col0: uint16(pos[3*i+2]),
			Line1: pos[3*i+1],
			Col1: uint16(pos[3*i+2]>>16),
			Stmts: numStmts[i],
		}
	}
	coverBlocks[fileName] = block
}
{{end}}

var testDeps = _gostdlib_testdeps.TestDeps{}

func main() {
{{if .Coverage}}
	_gostdlib_testing.RegisterCover(_gostdlib_testing.Cover{
		Mode: "set",
		Counters: coverCounters,
		Blocks: coverBlocks,
		CoveredPackages: "",
	})
    coverfile := _gostdlib_os.Getenv("COVERAGE_FILE")
    args := []string{_gostdlib_os.Args[0], "-test.v", "-test.coverprofile", coverfile}
{{else}}
    args := []string{_gostdlib_os.Args[0], "-test.v"}
{{end}}
{{if not .Benchmark}}
    testVar := _gostdlib_os.Getenv("TESTS")
    if testVar != "" {
		testVar = _gostdlib_strings.Replace(testVar, " ", "|", -1)
		args = append(args, "-test.run", testVar)
    }
    _gostdlib_os.Args = append(args, _gostdlib_os.Args[1:]...)
	m := _gostdlib_testing.MainStart(testDeps, tests, nil, examples)
{{else}}
	args = append(args, "-test.bench", ".*")
	_gostdlib_os.Args = append(args, _gostdlib_os.Args[1:]...)
	m := _gostdlib_testing.MainStart(testDeps, nil, benchmarks, nil)
{{end}}

{{if .Main}}
	{{.Package}}.{{.Main}}(m)
{{else}}
	_gostdlib_os.Exit(m.Run())
{{end}}
}
`))
<|endoftext|>


<commit_before>package arn

import (
	"sort"

	"github.com/aerogo/markdown"
	"github.com/aerogo/nano"
)

// Thread is a forum thread.
type Thread struct {
	Title  string   `json:"title" editable:"true"`
	Text   string   `json:"text" editable:"true" type:"textarea"`
	Sticky int      `json:"sticky" editable:"true"`
	Tags   []string `json:"tags" editable:"true"`
	Edited string   `json:"edited"`

	HasID
	HasText
	HasPosts
	HasCreator
	HasLikes
	HasLocked

	html string
}

// Link returns the relative URL of the thread.
func (thread *Thread) Link() string {
	return "/thread/" + thread.ID
}

// HTML returns the HTML representation of the thread.
func (thread *Thread) HTML() string {
	if thread.html != "" {
		return thread.html
	}

	thread.html = markdown.Render(thread.Text)
	return thread.html
}

// String implements the default string serialization.
func (thread *Thread) String() string {
	return thread.Title
}

// Parent always returns nil for threads.
func (thread *Thread) Parent() PostParent {
	return nil
}

// GetParentID always returns an empty string for threads.
func (thread *Thread) GetParentID() string {
	return ""
}

// Type returns the type name.
func (thread *Thread) Type() string {
	return "Thread"
}

// OnLike is called when the thread receives a like.
func (thread *Thread) OnLike(likedBy *User) {
	if !thread.Creator().Settings().Notification.ForumLikes {
		return
	}

	go func() {
		thread.Creator().SendNotification(&PushNotification{
			Title:   likedBy.Nick + " liked your thread",
			Message: likedBy.Nick + " liked your thread \"" + thread.Title + "\"",
			Icon:    "https:" + likedBy.AvatarLink("large"),
			Link:    "https://notify.moe" + likedBy.Link(),
			Type:    NotificationTypeLike,
		})
	}()
}

// OnLock is called when the thread is locked.
func (thread *Thread) OnLock(user *User) {
	logEntry := NewEditLogEntry(user.ID, "edit", "Thread", thread.ID, "Locked", "false", "true")
	logEntry.Save()
}

// OnUnlock is called when the thread is unlocked.
func (thread *Thread) OnUnlock(user *User) {
	logEntry := NewEditLogEntry(user.ID, "edit", "Thread", thread.ID, "Locked", "true", "false")
	logEntry.Save()
}

// TitleByUser returns the title of the thread,
// regardless of the user language settings
// because threads are bound to one language.
func (thread *Thread) TitleByUser(user *User) string {
	return thread.Title
}

// GetThread ...
func GetThread(id string) (*Thread, error) {
	obj, err := DB.Get("Thread", id)

	if err != nil {
		return nil, err
	}

	return obj.(*Thread), nil
}

// GetThreadsByTag ...
func GetThreadsByTag(tag string) []*Thread {
	var threads []*Thread
	allTags := (tag == "" || tag == "<nil>")

	for thread := range StreamThreads() {
		if (allTags && !Contains(thread.Tags, "update")) || Contains(thread.Tags, tag) {
			threads = append(threads, thread)
		}
	}

	return threads
}

// GetThreadsByUser ...
func GetThreadsByUser(user *User) []*Thread {
	var threads []*Thread

	for thread := range StreamThreads() {
		if thread.CreatedBy == user.ID {
			threads = append(threads, thread)
		}
	}

	return threads
}

// StreamThreads ...
func StreamThreads() chan *Thread {
	channel := make(chan *Thread, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("Thread") {
			channel <- obj.(*Thread)
		}

		close(channel)
	}()

	return channel
}

// AllThreads ...
func AllThreads() []*Thread {
	var all []*Thread

	for obj := range StreamThreads() {
		all = append(all, obj)
	}

	return all
}

// SortThreads sorts a slice of threads for the forum view (stickies first).
func SortThreads(threads []*Thread) {
	sort.Slice(threads, func(i, j int) bool {
		a := threads[i]
		b := threads[j]

		if a.Sticky != b.Sticky {
			return a.Sticky > b.Sticky
		}

		return a.Created > b.Created
	})
}

// SortThreadsLatestFirst sorts a slice of threads by creation date.
func SortThreadsLatestFirst(threads []*Thread) {
	sort.Slice(threads, func(i, j int) bool {
		return threads[i].Created > threads[j].Created
	})
}
<commit_msg>Fixed thread text<commit_after>package arn

import (
	"sort"

	"github.com/aerogo/markdown"
	"github.com/aerogo/nano"
)

// Thread is a forum thread.
type Thread struct {
	Title  string   `json:"title" editable:"true"`
	Sticky int      `json:"sticky" editable:"true"`
	Tags   []string `json:"tags" editable:"true"`
	Edited string   `json:"edited"`

	HasID
	HasText
	HasPosts
	HasCreator
	HasLikes
	HasLocked

	html string
}

// Link returns the relative URL of the thread.
func (thread *Thread) Link() string {
	return "/thread/" + thread.ID
}

// HTML returns the HTML representation of the thread.
func (thread *Thread) HTML() string {
	if thread.html != "" {
		return thread.html
	}

	thread.html = markdown.Render(thread.Text)
	return thread.html
}

// String implements the default string serialization.
func (thread *Thread) String() string {
	return thread.Title
}

// Parent always returns nil for threads.
func (thread *Thread) Parent() PostParent {
	return nil
}

// GetParentID always returns an empty string for threads.
func (thread *Thread) GetParentID() string {
	return ""
}

// Type returns the type name.
func (thread *Thread) Type() string {
	return "Thread"
}

// OnLike is called when the thread receives a like.
func (thread *Thread) OnLike(likedBy *User) {
	if !thread.Creator().Settings().Notification.ForumLikes {
		return
	}

	go func() {
		thread.Creator().SendNotification(&PushNotification{
			Title:   likedBy.Nick + " liked your thread",
			Message: likedBy.Nick + " liked your thread \"" + thread.Title + "\"",
			Icon:    "https:" + likedBy.AvatarLink("large"),
			Link:    "https://notify.moe" + likedBy.Link(),
			Type:    NotificationTypeLike,
		})
	}()
}

// OnLock is called when the thread is locked.
func (thread *Thread) OnLock(user *User) {
	logEntry := NewEditLogEntry(user.ID, "edit", "Thread", thread.ID, "Locked", "false", "true")
	logEntry.Save()
}

// OnUnlock is called when the thread is unlocked.
func (thread *Thread) OnUnlock(user *User) {
	logEntry := NewEditLogEntry(user.ID, "edit", "Thread", thread.ID, "Locked", "true", "false")
	logEntry.Save()
}

// TitleByUser returns the title of the thread,
// regardless of the user language settings
// because threads are bound to one language.
func (thread *Thread) TitleByUser(user *User) string {
	return thread.Title
}

// GetThread ...
func GetThread(id string) (*Thread, error) {
	obj, err := DB.Get("Thread", id)

	if err != nil {
		return nil, err
	}

	return obj.(*Thread), nil
}

// GetThreadsByTag ...
func GetThreadsByTag(tag string) []*Thread {
	var threads []*Thread
	allTags := (tag == "" || tag == "<nil>")

	for thread := range StreamThreads() {
		if (allTags && !Contains(thread.Tags, "update")) || Contains(thread.Tags, tag) {
			threads = append(threads, thread)
		}
	}

	return threads
}

// GetThreadsByUser ...
func GetThreadsByUser(user *User) []*Thread {
	var threads []*Thread

	for thread := range StreamThreads() {
		if thread.CreatedBy == user.ID {
			threads = append(threads, thread)
		}
	}

	return threads
}

// StreamThreads ...
func StreamThreads() chan *Thread {
	channel := make(chan *Thread, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("Thread") {
			channel <- obj.(*Thread)
		}

		close(channel)
	}()

	return channel
}

// AllThreads ...
func AllThreads() []*Thread {
	var all []*Thread

	for obj := range StreamThreads() {
		all = append(all, obj)
	}

	return all
}

// SortThreads sorts a slice of threads for the forum view (stickies first).
func SortThreads(threads []*Thread) {
	sort.Slice(threads, func(i, j int) bool {
		a := threads[i]
		b := threads[j]

		if a.Sticky != b.Sticky {
			return a.Sticky > b.Sticky
		}

		return a.Created > b.Created
	})
}

// SortThreadsLatestFirst sorts a slice of threads by creation date.
func SortThreadsLatestFirst(threads []*Thread) {
	sort.Slice(threads, func(i, j int) bool {
		return threads[i].Created > threads[j].Created
	})
}
<|endoftext|>