<commit_before>// The MIT License (MIT)

// Copyright (c) 2016 Maciej Borzecki

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package main

import (
	"github.com/bboozzoo/q3stats/store"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	"github.com/pkg/errors"
	"log"
	"os"
	"path"
)

const (
	defaultDbName = "q3stat.db"
)

type DB struct {
	db *gorm.DB
}

func NewDB() *DB {
	return &DB{}
}

func (d *DB) Open() error {
	p := C.Dbpath
	fi, _ := os.Stat(p)
	if fi.IsDir() == true {
		p = path.Join(p, defaultDbName)
	}

	log.Printf("opening DB file: %s", p)

	db, err := gorm.Open("sqlite3", p)
	if err != nil {
		return errors.Wrap(err, "failed to open DB")
	}

	d.db = db

	return nil
}

func (d *DB) Conn() *gorm.DB {
	return d.db
}

func (d *DB) Close() {
	if d.db != nil {
		d.db.Close()
	}
}

// return DB transaction wrapper
func (db *DB) Begin() store.DBTransaction {
	return &dbTx{
		db: db.db.Begin(),
	}
}

// helper DB transaction wrapper
type dbTx struct {
	db *gorm.DB
}

func (dbt *dbTx) Conn() *gorm.DB {
	return dbt.db
}

func (dbt *dbTx) Commit() {
	dbt.db.Commit()
}
<commit_msg>db: expect db path to be a file<commit_after>// The MIT License (MIT)

// Copyright (c) 2016 Maciej Borzecki

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package main

import (
	"github.com/bboozzoo/q3stats/store"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	"github.com/pkg/errors"
	"log"
)

const (
	defaultDbPath = "q3stat.db"
)

type DB struct {
	db *gorm.DB
}

func NewDB() *DB {
	return &DB{}
}

func (d *DB) Open(dbpath string) error {
	log.Printf("opening DB file: %s", dbpath)

	db, err := gorm.Open("sqlite3", dbpath)
	if err != nil {
		return errors.Wrap(err, "failed to open DB")
	}

	d.db = db

	return nil
}

func (d *DB) Conn() *gorm.DB {
	return d.db
}

func (d *DB) Close() {
	if d.db != nil {
		d.db.Close()
	}
}

// return DB transaction wrapper
func (db *DB) Begin() store.DBTransaction {
	return &dbTx{
		db: db.db.Begin(),
	}
}

// helper DB transaction wrapper
type dbTx struct {
	db *gorm.DB
}

func (dbt *dbTx) Conn() *gorm.DB {
	return dbt.db
}

func (dbt *dbTx) Commit() {
	dbt.db.Commit()
}
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package orm

import (
	"database/sql"
)

type DB struct {
	stdDB   *sql.DB
	dialect Dialect
	prefix  string
}

func NewDB(driverName, dataSourceName, prefix string, dialect Dialect) (*DB, error) {
	db, err := sql.Open(driverName, dataSourceName)
	if err != nil {
		return nil, err
	}

	return &DB{
		stdDB:   db,
		dialect: dialect,
		prefix:  prefix,
	}, nil
}

func (db *DB) Close() error {
	return db.Close()
}

// 返回标准包中的sql.DB指针
func (db *DB) StdDB() *sql.DB {
	return db.stdDB
}

func (db *DB) Dialect() Dialect {
	return db.dialect
}

func (db *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
	return db.stdDB.Query(query, args...)
}

func (db *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
	return db.stdDB.Exec(query, args...)
}

func (db *DB) Prepare(query string) (*sql.Stmt, error) {
	return db.stdDB.Prepare(query)
}

// 插入一个或是多个数据。v可以是struct或是struct指针，
// 每个v的o类型可以不相同。
func (db *DB) Insert(v ...interface{}) error {
	return insertMult(db, v...)
}

// 向table表中插入一条或多条数据。
func (db *DB) InsertMap(table string, values []map[string]interface{}) error {
	// TODO
	return nil
}

func (db *DB) Delete(v ...interface{}) error {
	return deleteMult(db, v...)
}

// 删除table表中col字段为vals的所有记录。
func (db *DB) DeleteCol(table string, col string, vals ...interface{}) error {
	//TODO
	return nil
}

func (db *DB) Update(v ...interface{}) error {
	return updateMult(db, v...)
}

// 删除table表中字段col的值为vals的所有记录。
func (db *DB) UpdateCol(table string, data map[string]interface{}, col string, vals ...interface{}) error {
	// TODO
	return nil
}

func (db *DB) Select(v ...interface{}) error {
	return findMult(db, v...)
}

// 查询table表中字段col的值为vals的所有记录
func (db *DB) Find(table string, col string, vals ...interface{}) ([]map[string]interface{}, error) {
	//TODO
	return nil, nil
}

func (db *DB) Create(v ...interface{}) error {
	return createMult(db, v...)
}

// 删除表
func (db *DB) Drop(v ...interface{}) error {
	return dropMult(db, v...)
}

func (db *DB) Truncate(v ...interface{}) error {
	return truncateMult(db, v...)
}

func (db *DB) Where(cond string, args ...interface{}) *Where {
	w := newWhere(db)
	return w.And(cond, args...)
}

// 事务对象
type Tx struct {
	db    *DB
	stdTx *sql.Tx
}

// 对orm/core.DB.DB()的实现。返回当前数据库对应的*sql.DB
func (t *Tx) StdTx() *sql.Tx {
	return t.stdTx
}

func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) {
	return tx.stdTx.Query(query, args...)
}

func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
	return tx.stdTx.Exec(query, args...)
}

func (tx *Tx) Prepare(query string) (*sql.Stmt, error) {
	return tx.stdTx.Prepare(query)
}

// 对orm/core.DB.Dialect()的实现。返回当前数据库对应的Dialect
func (tx *Tx) Dialect() Dialect {
	return tx.db.Dialect()
}

// 提交事务
// 提交之后，整个Tx对象将不再有效。
func (tx *Tx) Commit() error {
	return tx.stdTx.Commit()
}

// 回滚事务
func (tx *Tx) Rollback() error {
	return tx.stdTx.Rollback()
}

// 插入一个或多个数据。
// v可以是struct或是相同struct组成的数组。
// 若v中指定了自增字段，则该字段的值在插入数据库时，
// 会被自动忽略。
func (tx *Tx) Insert(v ...interface{}) error {
	return insertMult(tx, v...)
}

// 更新一个或多个类型。
// 更新依据为每个对象的主键或是唯一索引列。
// 若不存在此两个类型的字段，则返回错误信息。
func (tx *Tx) Update(v ...interface{}) error {
	return updateMult(tx, v...)
}

// 删除指定的数据对象。
func (tx *Tx) Delete(v ...interface{}) error {
	return deleteMult(tx, v...)
}

// 创建数据表。
func (tx *Tx) Create(v ...interface{}) error {
	return createMult(tx, v...)
}

// 删除表结构及数据。
func (tx *Tx) Drop(v ...interface{}) error {
	return dropMult(tx, v...)
}

// 清除表内容，但保留表结构。
func (tx *Tx) Truncate(v ...interface{}) error {
	return truncateMult(tx, v...)
}

func (tx *Tx) Where(cond string, args ...interface{}) *Where {
	w := newWhere(tx)
	return w.And(cond, args...)
}
<commit_msg>删除不必要的预留函数<commit_after>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package orm

import (
	"database/sql"
)

type DB struct {
	stdDB   *sql.DB
	dialect Dialect
	prefix  string
}

func NewDB(driverName, dataSourceName, prefix string, dialect Dialect) (*DB, error) {
	db, err := sql.Open(driverName, dataSourceName)
	if err != nil {
		return nil, err
	}

	return &DB{
		stdDB:   db,
		dialect: dialect,
		prefix:  prefix,
	}, nil
}

func (db *DB) Close() error {
	return db.Close()
}

// 返回标准包中的sql.DB指针
func (db *DB) StdDB() *sql.DB {
	return db.stdDB
}

func (db *DB) Dialect() Dialect {
	return db.dialect
}

func (db *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
	return db.stdDB.Query(query, args...)
}

func (db *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
	return db.stdDB.Exec(query, args...)
}

func (db *DB) Prepare(query string) (*sql.Stmt, error) {
	return db.stdDB.Prepare(query)
}

// 插入一个或是多个数据。v可以是struct或是struct指针，
// 每个v的o类型可以不相同。
func (db *DB) Insert(v ...interface{}) error {
	return insertMult(db, v...)
}

func (db *DB) Delete(v ...interface{}) error {
	return deleteMult(db, v...)
}

func (db *DB) Update(v ...interface{}) error {
	return updateMult(db, v...)
}

func (db *DB) Select(v ...interface{}) error {
	return findMult(db, v...)
}

// 查询table表中字段col的值为vals的所有记录
func (db *DB) Find(table string, col string, vals ...interface{}) ([]map[string]interface{}, error) {
	//TODO
	return nil, nil
}

func (db *DB) Create(v ...interface{}) error {
	return createMult(db, v...)
}

// 删除表
func (db *DB) Drop(v ...interface{}) error {
	return dropMult(db, v...)
}

func (db *DB) Truncate(v ...interface{}) error {
	return truncateMult(db, v...)
}

func (db *DB) Where(cond string, args ...interface{}) *Where {
	w := newWhere(db)
	return w.And(cond, args...)
}

// 事务对象
type Tx struct {
	db    *DB
	stdTx *sql.Tx
}

// 对orm/core.DB.DB()的实现。返回当前数据库对应的*sql.DB
func (t *Tx) StdTx() *sql.Tx {
	return t.stdTx
}

func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) {
	return tx.stdTx.Query(query, args...)
}

func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
	return tx.stdTx.Exec(query, args...)
}

func (tx *Tx) Prepare(query string) (*sql.Stmt, error) {
	return tx.stdTx.Prepare(query)
}

// 对orm/core.DB.Dialect()的实现。返回当前数据库对应的Dialect
func (tx *Tx) Dialect() Dialect {
	return tx.db.Dialect()
}

// 提交事务
// 提交之后，整个Tx对象将不再有效。
func (tx *Tx) Commit() error {
	return tx.stdTx.Commit()
}

// 回滚事务
func (tx *Tx) Rollback() error {
	return tx.stdTx.Rollback()
}

// 插入一个或多个数据。
// v可以是struct或是相同struct组成的数组。
// 若v中指定了自增字段，则该字段的值在插入数据库时，
// 会被自动忽略。
func (tx *Tx) Insert(v ...interface{}) error {
	return insertMult(tx, v...)
}

// 更新一个或多个类型。
// 更新依据为每个对象的主键或是唯一索引列。
// 若不存在此两个类型的字段，则返回错误信息。
func (tx *Tx) Update(v ...interface{}) error {
	return updateMult(tx, v...)
}

// 删除指定的数据对象。
func (tx *Tx) Delete(v ...interface{}) error {
	return deleteMult(tx, v...)
}

// 创建数据表。
func (tx *Tx) Create(v ...interface{}) error {
	return createMult(tx, v...)
}

// 删除表结构及数据。
func (tx *Tx) Drop(v ...interface{}) error {
	return dropMult(tx, v...)
}

// 清除表内容，但保留表结构。
func (tx *Tx) Truncate(v ...interface{}) error {
	return truncateMult(tx, v...)
}

func (tx *Tx) Where(cond string, args ...interface{}) *Where {
	w := newWhere(tx)
	return w.And(cond, args...)
}
<|endoftext|>


<commit_before>package spec

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/log"
)

var (
	ErrTimeout = errors.New("Timeout")
	ErrSkipped = errors.New("Skipped")
)

type TestGroup struct {
	Key         string
	Section     string
	Name        string
	Strict      bool
	Parent      *TestGroup
	Groups      []*TestGroup
	Tests       []*TestCase
	StrictTests []*TestCase

	PassedCount  int
	FailedCount  int
	SkippedCount int
}

func (tg *TestGroup) IsRoot() bool {
	return tg.Parent == nil
}

func (tg *TestGroup) ID() string {
	if tg.IsRoot() {
		return tg.Key
	}

	return fmt.Sprintf("%s/%s", tg.Key, tg.Section)
}

func (tg *TestGroup) Title() string {
	if tg.IsRoot() {
		return fmt.Sprintf("%s", tg.Name)
	} else {
		return fmt.Sprintf("%s. %s", tg.Section, tg.Name)
	}
}

func (tg *TestGroup) Level() int {
	if tg.IsRoot() {
		return 0
	}

	return strings.Count(tg.Section, ".") + 1
}

func (tg *TestGroup) Test(c *config.Config) {
	level := tg.Level()

	if tg.Strict && !c.Strict {
		return
	}

	mode := c.RunMode(tg.ID())
	if mode == config.RunModeNone {
		return
	}

	log.SetIndentLevel(level)
	log.Println(tg.Title())
	log.SetIndentLevel(level + 1)

	tests := append(tg.Tests, tg.StrictTests...)
	tested := false

	for i, tc := range tests {
		seq := i + 1

		err := tc.Test(c, seq)
		if err != nil {
			os.Exit(1)
		}

		if tc.Result != nil {
			if tc.Result.Failed {
				tg.FailedCount += 1
			} else if tc.Result.Skipped {
				tg.SkippedCount += 1
			} else {
				tg.PassedCount += 1
			}
		}

		tested = true
	}

	if tested {
		log.PrintBlankLine()
	}

	for _, g := range tg.Groups {
		g.Test(c)
		tg.FailedCount += g.FailedCount
		tg.SkippedCount += g.SkippedCount
		tg.PassedCount += g.PassedCount
	}
}

func (tg *TestGroup) AddTestGroup(stg *TestGroup) {
	stg.Parent = tg
	stg.Strict = tg.Strict
	tg.Groups = append(tg.Groups, stg)
}

func (tg *TestGroup) AddTestCase(tc *TestCase) {
	tc.Parent = tg
	if tg.Strict {
		tc.Strict = true
		tg.StrictTests = append(tg.StrictTests, tc)
	} else {
		tg.Tests = append(tg.Tests, tc)
	}
}

type TestCase struct {
	Desc        string
	Requirement string
	Strict      bool
	Parent      *TestGroup
	Result      *TestResult
	Run         func(c *config.Config, conn *Conn) error
}

func (tc *TestCase) Test(c *config.Config, seq int) error {
	if c.DryRun {
		msg := fmt.Sprintf("%s %s", seqStr(seq), tc.Desc)
		log.Println(msg)
		return nil
	}

	if tc.Strict && !c.Strict {
		return nil
	}

	mode := c.RunMode(fmt.Sprintf("%s/%d", tc.Parent.ID(), seq))
	if mode == config.RunModeNone {
		return nil
	}

	if !c.Verbose {
		msg := gray(fmt.Sprintf("  %s %s", seqStr(seq), tc.Desc))
		log.Print(msg)
	}

	conn, err := Dial(c)
	if err != nil {
		msg := red(fmt.Sprintf("  %s %s %s", "×", seqStr(seq), tc.Desc))
		log.ResetLine()
		log.Println(msg)
		return err
	}
	defer conn.Close()

	start := time.Now()
	err = tc.Run(c, conn)
	end := time.Now()

	log.ResetLine()

	tr := NewTestResult(tc, seq, err, end.Sub(start))
	tr.Print()
	tc.Result = tr

	return nil
}

type TestError struct {
	Expected []string
	Actual   string
}

func (e TestError) Error() string {
	return fmt.Sprintf("%s\n%s", strings.Join(e.Expected, "\n"), e.Actual)
}

type TestResult struct {
	TestCase *TestCase
	Sequence int
	Error    error
	Duration time.Duration

	Skipped bool
	Failed  bool
}

func NewTestResult(tc *TestCase, seq int, err error, d time.Duration) *TestResult {
	skipped := false
	failed := false

	if err != nil {
		if err == ErrSkipped {
			skipped = true
		} else {
			failed = true
		}
	}

	tr := TestResult{
		TestCase: tc,
		Sequence: seq,
		Error:    err,
		Duration: d,
		Skipped:  skipped,
		Failed:   failed,
	}

	return &tr
}

func (tr *TestResult) Print() {
	tc := tr.TestCase
	desc := tc.Desc
	seq := seqStr(tr.Sequence)

	if tr.Skipped {
		log.Println(cyan(fmt.Sprintf("%s %s", seq, desc)))
		return
	}

	if !tr.Failed {
		log.Println(fmt.Sprintf("%s %s %s", green("✔"), gray(seq), gray(desc)))
		return
	}

	log.Println(red(fmt.Sprintf("%s %s %s", "×", seq, desc)))
	err, ok := tr.Error.(*TestError)
	if ok {
		level := log.IndentLevel
		log.SetIndentLevel(level + 1)
		defer func() {
			log.SetIndentLevel(level)
		}()

		log.Println(red(fmt.Sprintf("-> %s", tc.Requirement)))
		label := "Expected: "
		for i, ex := range err.Expected {
			if i != 0 {
				label = strings.Repeat(" ", len(label))
			}
			log.Println(yellow(fmt.Sprintf("   %s%s", label, ex)))
		}
		log.Println(green(fmt.Sprintf("     Actual: %s", err.Actual)))

		return
	}
	if err == nil {
		log.Println(red(fmt.Sprintf("Error: %v", tr.Error.Error())))
	} else {
		log.Println(red(fmt.Sprintf("Error: %v", err)))
	}
}

func seqStr(seq int) string {
	return fmt.Sprintf("%d:", seq)
}
<commit_msg>Fix a bug of quit without error message<commit_after>package spec

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/log"
)

var (
	ErrTimeout = errors.New("Timeout")
	ErrSkipped = errors.New("Skipped")
)

type TestGroup struct {
	Key         string
	Section     string
	Name        string
	Strict      bool
	Parent      *TestGroup
	Groups      []*TestGroup
	Tests       []*TestCase
	StrictTests []*TestCase

	PassedCount  int
	FailedCount  int
	SkippedCount int
}

func (tg *TestGroup) IsRoot() bool {
	return tg.Parent == nil
}

func (tg *TestGroup) ID() string {
	if tg.IsRoot() {
		return tg.Key
	}

	return fmt.Sprintf("%s/%s", tg.Key, tg.Section)
}

func (tg *TestGroup) Title() string {
	if tg.IsRoot() {
		return fmt.Sprintf("%s", tg.Name)
	} else {
		return fmt.Sprintf("%s. %s", tg.Section, tg.Name)
	}
}

func (tg *TestGroup) Level() int {
	if tg.IsRoot() {
		return 0
	}

	return strings.Count(tg.Section, ".") + 1
}

func (tg *TestGroup) Test(c *config.Config) {
	level := tg.Level()

	if tg.Strict && !c.Strict {
		return
	}

	mode := c.RunMode(tg.ID())
	if mode == config.RunModeNone {
		return
	}

	log.SetIndentLevel(level)
	log.Println(tg.Title())
	log.SetIndentLevel(level + 1)

	tests := append(tg.Tests, tg.StrictTests...)
	tested := false

	for i, tc := range tests {
		seq := i + 1

		err := tc.Test(c, seq)
		if err != nil {
			fmt.Printf("\nError: %v\n", err)
			os.Exit(1)
		}

		if tc.Result != nil {
			if tc.Result.Failed {
				tg.FailedCount += 1
			} else if tc.Result.Skipped {
				tg.SkippedCount += 1
			} else {
				tg.PassedCount += 1
			}
		}

		tested = true
	}

	if tested {
		log.PrintBlankLine()
	}

	for _, g := range tg.Groups {
		g.Test(c)
		tg.FailedCount += g.FailedCount
		tg.SkippedCount += g.SkippedCount
		tg.PassedCount += g.PassedCount
	}
}

func (tg *TestGroup) AddTestGroup(stg *TestGroup) {
	stg.Parent = tg
	stg.Strict = tg.Strict
	tg.Groups = append(tg.Groups, stg)
}

func (tg *TestGroup) AddTestCase(tc *TestCase) {
	tc.Parent = tg
	if tg.Strict {
		tc.Strict = true
		tg.StrictTests = append(tg.StrictTests, tc)
	} else {
		tg.Tests = append(tg.Tests, tc)
	}
}

type TestCase struct {
	Desc        string
	Requirement string
	Strict      bool
	Parent      *TestGroup
	Result      *TestResult
	Run         func(c *config.Config, conn *Conn) error
}

func (tc *TestCase) Test(c *config.Config, seq int) error {
	if c.DryRun {
		msg := fmt.Sprintf("%s %s", seqStr(seq), tc.Desc)
		log.Println(msg)
		return nil
	}

	if tc.Strict && !c.Strict {
		return nil
	}

	mode := c.RunMode(fmt.Sprintf("%s/%d", tc.Parent.ID(), seq))
	if mode == config.RunModeNone {
		return nil
	}

	if !c.Verbose {
		msg := gray(fmt.Sprintf("  %s %s", seqStr(seq), tc.Desc))
		log.Print(msg)
	}

	conn, err := Dial(c)
	if err != nil {
		msg := red(fmt.Sprintf("%s %s %s", "×", seqStr(seq), tc.Desc))
		log.ResetLine()
		log.Println(msg)
		return err
	}
	defer conn.Close()

	start := time.Now()
	err = tc.Run(c, conn)
	end := time.Now()

	log.ResetLine()

	tr := NewTestResult(tc, seq, err, end.Sub(start))
	tr.Print()
	tc.Result = tr

	return nil
}

type TestError struct {
	Expected []string
	Actual   string
}

func (e TestError) Error() string {
	return fmt.Sprintf("%s\n%s", strings.Join(e.Expected, "\n"), e.Actual)
}

type TestResult struct {
	TestCase *TestCase
	Sequence int
	Error    error
	Duration time.Duration

	Skipped bool
	Failed  bool
}

func NewTestResult(tc *TestCase, seq int, err error, d time.Duration) *TestResult {
	skipped := false
	failed := false

	if err != nil {
		if err == ErrSkipped {
			skipped = true
		} else {
			failed = true
		}
	}

	tr := TestResult{
		TestCase: tc,
		Sequence: seq,
		Error:    err,
		Duration: d,
		Skipped:  skipped,
		Failed:   failed,
	}

	return &tr
}

func (tr *TestResult) Print() {
	tc := tr.TestCase
	desc := tc.Desc
	seq := seqStr(tr.Sequence)

	if tr.Skipped {
		log.Println(cyan(fmt.Sprintf("%s %s", seq, desc)))
		return
	}

	if !tr.Failed {
		log.Println(fmt.Sprintf("%s %s %s", green("✔"), gray(seq), gray(desc)))
		return
	}

	log.Println(red(fmt.Sprintf("%s %s %s", "×", seq, desc)))
	err, ok := tr.Error.(*TestError)
	if ok {
		level := log.IndentLevel
		log.SetIndentLevel(level + 1)
		defer func() {
			log.SetIndentLevel(level)
		}()

		log.Println(red(fmt.Sprintf("-> %s", tc.Requirement)))
		label := "Expected: "
		for i, ex := range err.Expected {
			if i != 0 {
				label = strings.Repeat(" ", len(label))
			}
			log.Println(yellow(fmt.Sprintf("   %s%s", label, ex)))
		}
		log.Println(green(fmt.Sprintf("     Actual: %s", err.Actual)))

		return
	}
	if err == nil {
		log.Println(red(fmt.Sprintf("Error: %v", tr.Error.Error())))
	} else {
		log.Println(red(fmt.Sprintf("Error: %v", err)))
	}
}

func seqStr(seq int) string {
	return fmt.Sprintf("%d:", seq)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 - The TXTdirect Authors
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 txtdirect

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
)

const (
	basezone        = "_redirect"
	defaultSub      = "www"
	defaultProtocol = "https"
)

type record struct {
	Version string
	To      string
	Code    int
	Type    string
	Vcs     string
	From    string
	Default string
}

// Config contains the middleware's configuration
type Config struct {
	Enable   []string
	Redirect string
}

func (r *record) Parse(str string) error {
	s := strings.Split(str, ";")
	for _, l := range s {
		switch {
		case strings.HasPrefix(l, "v="):
			l = strings.TrimPrefix(l, "v=")
			r.Version = l
			if r.Version != "txtv0" {
				return fmt.Errorf("unhandled version '%s'", r.Version)
			}
			log.Print("WARN: txtv0 is not suitable for production")

		case strings.HasPrefix(l, "to="):
			l = strings.TrimPrefix(l, "to=")
			r.To = l

		case strings.HasPrefix(l, "code="):
			l = strings.TrimPrefix(l, "code=")
			i, err := strconv.Atoi(l)
			if err != nil {
				return fmt.Errorf("could not parse status code: %s", err)
			}
			r.Code = i

		case strings.HasPrefix(l, "type="):
			l = strings.TrimPrefix(l, "type=")
			r.Type = l

		case strings.HasPrefix(l, "vcs="):
			l = strings.TrimPrefix(l, "vcs=")
			r.Vcs = l

		default:
			if r.To != "" {
				return fmt.Errorf("multiple values without keys")
			}
			r.To = l
		}
	}

	if r.Code == 0 {
		r.Code = 301
	}

	if r.Type == "" {
		r.Type = "host"
	}

	return nil
}

func getBaseTarget(rec record) (string, int) {
	return rec.To, rec.Code
}

func getRecord(host, path string) (record, error) {
	if strings.Contains(host, ":") {
		hostSlice := strings.Split(host, ":")
		host = hostSlice[0]
	}
	zone := strings.Join([]string{basezone, host}, ".")

	var absoluteZone string
	if strings.HasSuffix(zone, ".") {
		absoluteZone = zone
	} else {
		absoluteZone = strings.Join([]string{zone, "."}, "")
	}
	s, err := net.LookupTXT(absoluteZone)

	if err != nil {
		return record{}, fmt.Errorf("could not get TXT record: %s", err)
	}

	rec := record{}
	if err = rec.Parse(s[0]); err != nil {
		return rec, fmt.Errorf("could not parse record: %s", err)
	}

	if rec.To == "" {
		s := []string{defaultProtocol, "://", defaultSub, ".", host}
		rec.To = strings.Join(s, "")
	}

	return rec, nil
}

func contains(array []string, word string) bool {
	for _, w := range array {
		if w == word {
			return true
		}
	}
	return false
}

// Redirect the request depending on the redirect record found
func Redirect(w http.ResponseWriter, r *http.Request, c Config) error {
	host := r.Host
	path := r.URL.Path

	bl := make(map[string]bool)
	bl["/favicon.ico"] = true

	if bl[path] {
		http.Redirect(w, r, strings.Join([]string{host, path}, ""), 200)
		return nil
	}

	rec, err := getRecord(host, path)
	if err != nil {
		if strings.HasSuffix(err.Error(), "no such host") {
			if c.Redirect != "" {
				http.Redirect(w, r, c.Redirect, http.StatusMovedPermanently)
				return nil
			}
			if contains(c.Enable, "www") {
				s := []string{defaultProtocol, "://", defaultSub, ".", host}
				http.Redirect(w, r, strings.Join(s, ""), 301)
				return nil
			}
			http.NotFound(w, r)
			return nil
		}
		return err
	}

	if !contains(c.Enable, rec.Type) {
		return fmt.Errorf("option disabled")
	}

	if path == "/" {
		http.Redirect(w, r, rec.Default, rec.Code)
		return nil
	}

	if rec.Type == "path" && path != "" {
		zone, from, _ := zoneFromPath(host, path, rec)
		rec, err = getFinalRecord(zone, from)
		if err != nil {
			return err
		}
	}

	if rec.Type == "host" {
		to, code := getBaseTarget(rec)
		http.Redirect(w, r, to, code)
		return nil
	}

	if rec.Type == "gometa" {
		return gometa(w, rec, host, path)
	}

	return fmt.Errorf("record type %s unsupported", rec.Type)
}
<commit_msg>Add Default to Parser<commit_after>/*
Copyright 2017 - The TXTdirect Authors
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 txtdirect

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
)

const (
	basezone        = "_redirect"
	defaultSub      = "www"
	defaultProtocol = "https"
)

type record struct {
	Version string
	To      string
	Code    int
	Type    string
	Vcs     string
	From    string
	Default string
}

// Config contains the middleware's configuration
type Config struct {
	Enable   []string
	Redirect string
}

func (r *record) Parse(str string) error {
	s := strings.Split(str, ";")
	for _, l := range s {
		switch {
		case strings.HasPrefix(l, "v="):
			l = strings.TrimPrefix(l, "v=")
			r.Version = l
			if r.Version != "txtv0" {
				return fmt.Errorf("unhandled version '%s'", r.Version)
			}
			log.Print("WARN: txtv0 is not suitable for production")

		case strings.HasPrefix(l, "to="):
			l = strings.TrimPrefix(l, "to=")
			r.To = l

		case strings.HasPrefix(l, "code="):
			l = strings.TrimPrefix(l, "code=")
			i, err := strconv.Atoi(l)
			if err != nil {
				return fmt.Errorf("could not parse status code: %s", err)
			}
			r.Code = i

		case strings.HasPrefix(l, "type="):
			l = strings.TrimPrefix(l, "type=")
			r.Type = l

		case strings.HasPrefix(l, "vcs="):
			l = strings.TrimPrefix(l, "vcs=")
			r.Vcs = l

		case strings.HasPrefix(l, "default="):
			l = strings.TrimPrefix(l, "default=")
			r.Default = l

		default:
			if r.To != "" {
				return fmt.Errorf("multiple values without keys")
			}
			r.To = l
		}
	}

	if r.Code == 0 {
		r.Code = 301
	}

	if r.Type == "" {
		r.Type = "host"
	}

	return nil
}

func getBaseTarget(rec record) (string, int) {
	return rec.To, rec.Code
}

func getRecord(host, path string) (record, error) {
	if strings.Contains(host, ":") {
		hostSlice := strings.Split(host, ":")
		host = hostSlice[0]
	}
	zone := strings.Join([]string{basezone, host}, ".")

	var absoluteZone string
	if strings.HasSuffix(zone, ".") {
		absoluteZone = zone
	} else {
		absoluteZone = strings.Join([]string{zone, "."}, "")
	}
	s, err := net.LookupTXT(absoluteZone)

	if err != nil {
		return record{}, fmt.Errorf("could not get TXT record: %s", err)
	}

	rec := record{}
	if err = rec.Parse(s[0]); err != nil {
		return rec, fmt.Errorf("could not parse record: %s", err)
	}

	if rec.To == "" {
		s := []string{defaultProtocol, "://", defaultSub, ".", host}
		rec.To = strings.Join(s, "")
	}

	return rec, nil
}

func contains(array []string, word string) bool {
	for _, w := range array {
		if w == word {
			return true
		}
	}
	return false
}

// Redirect the request depending on the redirect record found
func Redirect(w http.ResponseWriter, r *http.Request, c Config) error {
	host := r.Host
	path := r.URL.Path

	bl := make(map[string]bool)
	bl["/favicon.ico"] = true

	if bl[path] {
		http.Redirect(w, r, strings.Join([]string{host, path}, ""), 200)
		return nil
	}

	rec, err := getRecord(host, path)
	if err != nil {
		if strings.HasSuffix(err.Error(), "no such host") {
			if c.Redirect != "" {
				http.Redirect(w, r, c.Redirect, http.StatusMovedPermanently)
				return nil
			}
			if contains(c.Enable, "www") {
				s := []string{defaultProtocol, "://", defaultSub, ".", host}
				http.Redirect(w, r, strings.Join(s, ""), 301)
				return nil
			}
			http.NotFound(w, r)
			return nil
		}
		return err
	}

	if !contains(c.Enable, rec.Type) {
		return fmt.Errorf("option disabled")
	}

	if path == "/" {
		http.Redirect(w, r, rec.Default, rec.Code)
		return nil
	}

	if rec.Type == "path" && path != "" {
		zone, from, _ := zoneFromPath(host, path, rec)
		rec, err = getFinalRecord(zone, from)
		if err != nil {
			return err
		}
	}

	if rec.Type == "host" {
		to, code := getBaseTarget(rec)
		http.Redirect(w, r, to, code)
		return nil
	}

	if rec.Type == "gometa" {
		return gometa(w, rec, host, path)
	}

	return fmt.Errorf("record type %s unsupported", rec.Type)
}
<|endoftext|>


<commit_before>package transport

import (
	"github.com/stefankopieczek/gossip/log"
	"github.com/stefankopieczek/gossip/timing"
)

// Fields of connTable should only be modified by the dedicated goroutine called by Init().
// All other callers should use connTable's associated public methods to access it.
type connTable struct {
	conns   map[string]*connWatcher
	stopped bool
}

type connWatcher struct {
	addr   string
	conn   *connection
	timer  timing.Timer
	update chan *connection
	stop   chan bool
}

// Create a new connection table.
func (t *connTable) Init() {
	log.Info("Init conntable %p")
	t.conns = make(map[string]*connWatcher)
}

// Push a connection to the connection table, registered under a specific address.
// If it is a new connection, start the socket expiry timer.
// If it is a known connection, restart the timer.
func (t *connTable) Notify(addr string, conn *connection) {
	if t.stopped {
		log.Debug("Ignoring conn notification for address %s after table stop.", addr)
		return
	}

	watcher, ok := t.conns[addr]
	if !ok {
		log.Debug("No connection watcher registered for %s; spawn one", addr)
		watcher = &connWatcher{addr, conn, timing.NewTimer(c_SOCKET_EXPIRY), make(chan *connection), make(chan bool)}
		t.conns[addr] = watcher
		go func(watcher *connWatcher) {
			// We expect to close off connections explicitly, but let's be safe and clean up
			// if we close unexpectedly.
			defer func(c *connection) {
				if c != nil {
					c.Close()
				}
			}(watcher.conn)

			for {
				select {
				case <-watcher.timer.C():
					// Socket expiry timer has run out. Close the connection.
					log.Debug("Socket %p (%s) inactive for too long; close it", watcher.conn, watcher.addr)
					watcher.conn.Close()
					watcher.conn = nil
				case update := <-watcher.update:
					// We've been pinged with a connection; update it and refresh the
					// timer.
					if update != watcher.conn {
						log.Debug("Manager for address %s received new socket %p; update records", watcher.addr, watcher.conn)
						watcher.conn = update
					}
					watcher.timer.Reset(c_SOCKET_EXPIRY)
				case stop := <-watcher.stop:
					// We've received a termination signal; stop managing this connection.
					if stop {
						log.Info("Connection watcher for address %s got the kill signal. Stopping.", watcher.addr)
						watcher.timer.Stop()
						watcher.conn.Close()
						watcher.conn = nil
						break
					}
				}
			}
		}(watcher)
	}

	watcher.update <- conn
}

// Return an existing open socket for the given address, or nil if no such socket
// exists.
func (t *connTable) GetConn(addr string) *connection {
	watcher, ok := t.conns[addr]
	if ok {
		log.Debug("Query connection for address %s returns %p", addr, watcher.conn)
		return watcher.conn
	} else {
		log.Debug("Query connection for address %s returns nil (no registered watcher)", addr)
		return nil
	}
}

// Close all sockets and stop socket management.
// The table cannot be restarted after Stop() has been called, and GetConn() will return nil.
func (t *connTable) Stop() {
	log.Info("Conntable %p stopped")
	t.stopped = true
	for _, watcher := range t.conns {
		watcher.stop <- true
	}
}
<commit_msg>Issue 13 - ConnTable can panic when it is stopped<commit_after>package transport

import (
	"time"

	"github.com/stefankopieczek/gossip/log"
	"github.com/stefankopieczek/gossip/timing"
)

// Fields of connTable should only be modified by the dedicated goroutine called by Init().
// All other callers should use connTable's associated public methods to access it.
type connTable struct {
	conns        map[string]*connWatcher
	connRequests chan *connRequest
	updates      chan *connUpdate
	expiries     chan string
	stop         chan bool
	stopped      bool
}

type connWatcher struct {
	addr       string
	conn       *connection
	timer      timing.Timer
	updated    chan bool
	expiryTime time.Time
	expiry     chan<- string
	stop       chan bool
}

// Create a new connection table.
func (t *connTable) Init() {
	log.Info("Init conntable %p", t)
	t.conns = make(map[string]*connWatcher)
	t.connRequests = make(chan *connRequest)
	t.updates = make(chan *connUpdate)
	t.expiries = make(chan string)
	t.stop = make(chan bool)
	go t.manage()
}

// Management loop for the connTable.
// Handles notifications of connection updates, expiries of connections, and
// the termination of the routine.
func (t *connTable) manage() {
	for {
		select {
		case request := <-t.connRequests:
			watcher := t.conns[request.addr]
			if watcher != nil {
				request.responseChan <- watcher.conn
			} else {
				request.responseChan <- nil
			}
		case update := <-t.updates:
			t.handleUpdate(update)
		case addr := <-t.expiries:
			if t.conns[addr].expiryTime.Before(time.Now()) {
				log.Debug("Conntable %p notified that the watcher for address %s has expired. Remove it.", t, addr)
				t.conns[addr].stop <- true
				t.conns[addr].conn.Close()
				delete(t.conns, addr)
			}
		case <-t.stop:
			log.Info("Conntable %p stopped")
			t.stopped = true
			for _, watcher := range t.conns {
				watcher.stop <- true
				watcher.conn.Close()
			}
			break
		}
	}
}

// Push a connection to the connection table, registered under a specific address.
// If it is a new connection, start the socket expiry timer.
// If it is a known connection, restart the timer.
func (t *connTable) Notify(addr string, conn *connection) {
	if t.stopped {
		log.Debug("Ignoring conn notification for address %s after table stop.", addr)
		return
	}

	t.updates <- &connUpdate{addr, conn}
}

func (t *connTable) handleUpdate(update *connUpdate) {
	log.Debug("Update received in connTable %p for address %s", t, update.addr)
	watcher, entry_exists := t.conns[update.addr]
	if !entry_exists {
		log.Debug("No connection watcher registered for %s; spawn one", update.addr)
		watcher = &connWatcher{update.addr, update.conn, timing.NewTimer(c_SOCKET_EXPIRY), make(chan bool), timing.Now().Add(c_SOCKET_EXPIRY), t.expiries, make(chan bool)}
		t.conns[update.addr] = watcher
		go watcher.loop()
	}

	watcher.Update(update.conn)
}

// Return an existing open socket for the given address, or nil if no such socket
// exists.
func (t *connTable) GetConn(addr string) *connection {
	responseChan := make(chan *connection)
	t.connRequests <- &connRequest{addr, responseChan}
	conn := <-responseChan

	log.Debug("Query connection for address %s returns %p", conn)
	return conn
}

// Close all sockets and stop socket management.
// The table cannot be restarted after Stop() has been called, and GetConn() will return nil.
func (t *connTable) Stop() {
	t.stop <- true
}

// Update the connection associated with a given connWatcher, and reset the
// timeout timer.
// Must only be called from the connTable goroutine (and in particular, must
// *not* be called from the connWatcher goroutine).
func (watcher *connWatcher) Update(c *connection) {
	if watcher.timer != nil {
		watcher.timer.Stop()
	}

	watcher.expiryTime = timing.Now().Add(c_SOCKET_EXPIRY)
	watcher.timer = timing.NewTimer(c_SOCKET_EXPIRY)
	watcher.conn = c
}

// connWatcher main loop. Waits for the connection to expire, and notifies the connTable
// when it does.
func (watcher *connWatcher) loop() {
	// We expect to close off connections explicitly, but let's be safe and clean up
	// if we close unexpectedly.
	defer func(c *connection) {
		if c != nil {
			c.Close()
		}
	}(watcher.conn)

	for {
		select {
		case <-watcher.timer.C():
			// Socket expiry timer has run out. Close the connection.
			log.Debug("Socket %p (%s) inactive for too long; close it", watcher.conn, watcher.addr)
			watcher.expiry <- watcher.addr

		case <-watcher.updated:
			// The connTable just updated our associated connection and refreshed our timer.
			// We don't need to do anything here except cycle the for loop so we're selecting on
			// the new timer channel rather than the old one.
			log.Debug("Connection watcher for address %s received an update ping", watcher.addr)

		case stop := <-watcher.stop:
			// We've received a termination signal; stop managing this connection.
			if stop {
				log.Info("Connection watcher for address %s got the kill signal. Stopping.", watcher.addr)
				watcher.timer.Stop()
				break
			}
		}
	}
}

type connUpdate struct {
	addr string
	conn *connection
}

type connRequest struct {
	addr         string
	responseChan chan *connection
}
<|endoftext|>


<commit_before>// Copyright 2015 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package grpc supports network connections to GRPC servers.
// This package is not intended for use by end developers. Use the
// google.golang.org/api/option package to configure API clients.
package grpc

import (
	"context"
	"crypto/tls"
	"errors"
	"log"
	"net"
	"os"
	"strings"

	"cloud.google.com/go/compute/metadata"
	"go.opencensus.io/plugin/ocgrpc"
	"golang.org/x/oauth2"
	"google.golang.org/api/internal"
	"google.golang.org/api/option"
	"google.golang.org/api/transport/internal/dca"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	grpcgoogle "google.golang.org/grpc/credentials/google"
	"google.golang.org/grpc/credentials/oauth"

	// Install grpclb, which is required for direct path.
	_ "google.golang.org/grpc/balancer/grpclb"
)

// Check env to disable DirectPath traffic.
const disableDirectPath = "GOOGLE_CLOUD_DISABLE_DIRECT_PATH"

// Check env to decide if using google-c2p resolver for DirectPath traffic.
const enableDirectPathXds = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS"

// Set at init time by dial_appengine.go. If nil, we're not on App Engine.
var appengineDialerHook func(context.Context) grpc.DialOption

// Set at init time by dial_socketopt.go. If nil, socketopt is not supported.
var timeoutDialerOption grpc.DialOption

// Dial returns a GRPC connection for use communicating with a Google cloud
// service, configured with the given ClientOptions.
func Dial(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	o, err := processAndValidateOpts(opts)
	if err != nil {
		return nil, err
	}
	if o.GRPCConnPool != nil {
		return o.GRPCConnPool.Conn(), nil
	}
	// NOTE(cbro): We removed support for option.WithGRPCConnPool (GRPCConnPoolSize)
	// on 2020-02-12 because RoundRobin and WithBalancer are deprecated and we need to remove usages of it.
	//
	// Connection pooling is only done via DialPool.
	return dial(ctx, false, o)
}

// DialInsecure returns an insecure GRPC connection for use communicating
// with fake or mock Google cloud service implementations, such as emulators.
// The connection is configured with the given ClientOptions.
func DialInsecure(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	o, err := processAndValidateOpts(opts)
	if err != nil {
		return nil, err
	}
	return dial(ctx, true, o)
}

// DialPool returns a pool of GRPC connections for the given service.
// This differs from the connection pooling implementation used by Dial, which uses a custom GRPC load balancer.
// DialPool should be used instead of Dial when a pool is used by default or a different custom GRPC load balancer is needed.
// The context and options are shared between each Conn in the pool.
// The pool size is configured using the WithGRPCConnectionPool option.
//
// This API is subject to change as we further refine requirements. It will go away if gRPC stubs accept an interface instead of the concrete ClientConn type. See https://github.com/grpc/grpc-go/issues/1287.
func DialPool(ctx context.Context, opts ...option.ClientOption) (ConnPool, error) {
	o, err := processAndValidateOpts(opts)
	if err != nil {
		return nil, err
	}
	if o.GRPCConnPool != nil {
		return o.GRPCConnPool, nil
	}
	poolSize := o.GRPCConnPoolSize
	if o.GRPCConn != nil {
		// WithGRPCConn is technically incompatible with WithGRPCConnectionPool.
		// Always assume pool size is 1 when a grpc.ClientConn is explicitly used.
		poolSize = 1
	}
	o.GRPCConnPoolSize = 0 // we don't *need* to set this to zero, but it's safe to.

	if poolSize == 0 || poolSize == 1 {
		// Fast path for common case for a connection pool with a single connection.
		conn, err := dial(ctx, false, o)
		if err != nil {
			return nil, err
		}
		return &singleConnPool{conn}, nil
	}

	pool := &roundRobinConnPool{}
	for i := 0; i < poolSize; i++ {
		conn, err := dial(ctx, false, o)
		if err != nil {
			defer pool.Close() // NOTE: error from Close is ignored.
			return nil, err
		}
		pool.conns = append(pool.conns, conn)
	}
	return pool, nil
}

func dial(ctx context.Context, insecure bool, o *internal.DialSettings) (*grpc.ClientConn, error) {
	if o.HTTPClient != nil {
		return nil, errors.New("unsupported HTTP client specified")
	}
	if o.GRPCConn != nil {
		return o.GRPCConn, nil
	}
	clientCertSource, endpoint, err := dca.GetClientCertificateSourceAndEndpoint(o)
	if err != nil {
		return nil, err
	}
	var grpcOpts []grpc.DialOption
	if insecure {
		grpcOpts = []grpc.DialOption{grpc.WithInsecure()}
	} else if !o.NoAuth {
		if o.APIKey != "" {
			log.Print("API keys are not supported for gRPC APIs. Remove the WithAPIKey option from your client-creating call.")
		}
		creds, err := internal.Creds(ctx, o)
		if err != nil {
			return nil, err
		}

		if o.QuotaProject == "" {
			o.QuotaProject = internal.QuotaProjectFromCreds(creds)
		}

		// Attempt Direct Path:
		if isDirectPathEnabled(endpoint, o) && isTokenSourceDirectPathCompatible(creds.TokenSource, o) && metadata.OnGCE() {
			grpcOpts = []grpc.DialOption{
				grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{oauth.TokenSource{creds.TokenSource}}))}
			if timeoutDialerOption != nil {
				grpcOpts = append(grpcOpts, timeoutDialerOption)
			}
			// Check if google-c2p resolver is enabled for DirectPath
			if strings.EqualFold(os.Getenv(enableDirectPathXds), "true") {
				// google-c2p resolver target must not have a port number
				if addr, _, err := net.SplitHostPort(endpoint); err == nil {
					endpoint = "google-c2p-experimental:///" + addr
				} else {
					endpoint = "google-c2p-experimental:///" + endpoint
				}
			} else {
				if !strings.HasPrefix(endpoint, "dns:///") {
					endpoint = "dns:///" + endpoint
				}
				grpcOpts = append(grpcOpts,
					// For now all DirectPath go clients will be using the following lb config, but in future
					// when different services need different configs, then we should change this to a
					// per-service config.
					grpc.WithDisableServiceConfig(),
					grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`))
			}
			// TODO(cbro): add support for system parameters (quota project, request reason) via chained interceptor.
		} else {
			tlsConfig := &tls.Config{
				GetClientCertificate: clientCertSource,
			}
			grpcOpts = []grpc.DialOption{
				grpc.WithPerRPCCredentials(grpcTokenSource{
					TokenSource:   oauth.TokenSource{creds.TokenSource},
					quotaProject:  o.QuotaProject,
					requestReason: o.RequestReason,
				}),
				grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
			}
		}
	}

	if appengineDialerHook != nil {
		// Use the Socket API on App Engine.
		// appengine dialer will override socketopt dialer
		grpcOpts = append(grpcOpts, appengineDialerHook(ctx))
	}

	// Add tracing, but before the other options, so that clients can override the
	// gRPC stats handler.
	// This assumes that gRPC options are processed in order, left to right.
	grpcOpts = addOCStatsHandler(grpcOpts, o)
	grpcOpts = append(grpcOpts, o.GRPCDialOpts...)
	if o.UserAgent != "" {
		grpcOpts = append(grpcOpts, grpc.WithUserAgent(o.UserAgent))
	}

	return grpc.DialContext(ctx, endpoint, grpcOpts...)
}

func addOCStatsHandler(opts []grpc.DialOption, settings *internal.DialSettings) []grpc.DialOption {
	if settings.TelemetryDisabled {
		return opts
	}
	return append(opts, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
}

// grpcTokenSource supplies PerRPCCredentials from an oauth.TokenSource.
type grpcTokenSource struct {
	oauth.TokenSource

	// Additional metadata attached as headers.
	quotaProject  string
	requestReason string
}

// GetRequestMetadata gets the request metadata as a map from a grpcTokenSource.
func (ts grpcTokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (
	map[string]string, error) {
	metadata, err := ts.TokenSource.GetRequestMetadata(ctx, uri...)
	if err != nil {
		return nil, err
	}

	// Attach system parameter
	if ts.quotaProject != "" {
		metadata["X-goog-user-project"] = ts.quotaProject
	}
	if ts.requestReason != "" {
		metadata["X-goog-request-reason"] = ts.requestReason
	}
	return metadata, nil
}

func isDirectPathEnabled(endpoint string, o *internal.DialSettings) bool {
	if !o.EnableDirectPath {
		return false
	}
	if !checkDirectPathEndPoint(endpoint) {
		return false
	}
	if strings.EqualFold(os.Getenv(disableDirectPath), "true") {
		return false
	}
	return true
}

func isTokenSourceDirectPathCompatible(ts oauth2.TokenSource, o *internal.DialSettings) bool {
	if ts == nil {
		return false
	}
	tok, err := ts.Token()
	if err != nil {
		return false
	}
	if tok == nil {
		return false
	}
	if o.AllowNonDefaultServiceAccount {
		return true
	}
	if source, _ := tok.Extra("oauth2.google.tokenSource").(string); source != "compute-metadata" {
		return false
	}
	if acct, _ := tok.Extra("oauth2.google.serviceAccount").(string); acct != "default" {
		return false
	}
	return true
}

func checkDirectPathEndPoint(endpoint string) bool {
	// Only [dns:///]host[:port] is supported, not other schemes (e.g., "tcp://" or "unix://").
	// Also don't try direct path if the user has chosen an alternate name resolver
	// (i.e., via ":///" prefix).
	//
	// TODO(cbro): once gRPC has introspectible options, check the user hasn't
	// provided a custom dialer in gRPC options.
	if strings.Contains(endpoint, "://") && !strings.HasPrefix(endpoint, "dns:///") {
		return false
	}

	if endpoint == "" {
		return false
	}

	return true
}

func processAndValidateOpts(opts []option.ClientOption) (*internal.DialSettings, error) {
	var o internal.DialSettings
	for _, opt := range opts {
		opt.Apply(&o)
	}
	if err := o.Validate(); err != nil {
		return nil, err
	}

	return &o, nil
}

type connPoolOption struct{ ConnPool }

// WithConnPool returns a ClientOption that specifies the ConnPool
// connection to use as the basis of communications.
//
// This is only to be used by Google client libraries internally, for example
// when creating a longrunning API client that shares the same connection pool
// as a service client.
func WithConnPool(p ConnPool) option.ClientOption {
	return connPoolOption{p}
}

func (o connPoolOption) Apply(s *internal.DialSettings) {
	s.GRPCConnPool = o.ConnPool
}
<commit_msg>fix(transport/grpc): separate resolution of creds and certs (#1759)<commit_after>// Copyright 2015 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package grpc supports network connections to GRPC servers.
// This package is not intended for use by end developers. Use the
// google.golang.org/api/option package to configure API clients.
package grpc

import (
	"context"
	"crypto/tls"
	"errors"
	"log"
	"net"
	"os"
	"strings"

	"cloud.google.com/go/compute/metadata"
	"go.opencensus.io/plugin/ocgrpc"
	"golang.org/x/oauth2"
	"google.golang.org/api/internal"
	"google.golang.org/api/option"
	"google.golang.org/api/transport/internal/dca"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	grpcgoogle "google.golang.org/grpc/credentials/google"
	grpcinsecure "google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/credentials/oauth"

	// Install grpclb, which is required for direct path.
	_ "google.golang.org/grpc/balancer/grpclb"
)

// Check env to disable DirectPath traffic.
const disableDirectPath = "GOOGLE_CLOUD_DISABLE_DIRECT_PATH"

// Check env to decide if using google-c2p resolver for DirectPath traffic.
const enableDirectPathXds = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS"

// Set at init time by dial_appengine.go. If nil, we're not on App Engine.
var appengineDialerHook func(context.Context) grpc.DialOption

// Set at init time by dial_socketopt.go. If nil, socketopt is not supported.
var timeoutDialerOption grpc.DialOption

// Dial returns a GRPC connection for use communicating with a Google cloud
// service, configured with the given ClientOptions.
func Dial(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	o, err := processAndValidateOpts(opts)
	if err != nil {
		return nil, err
	}
	if o.GRPCConnPool != nil {
		return o.GRPCConnPool.Conn(), nil
	}
	// NOTE(cbro): We removed support for option.WithGRPCConnPool (GRPCConnPoolSize)
	// on 2020-02-12 because RoundRobin and WithBalancer are deprecated and we need to remove usages of it.
	//
	// Connection pooling is only done via DialPool.
	return dial(ctx, false, o)
}

// DialInsecure returns an insecure GRPC connection for use communicating
// with fake or mock Google cloud service implementations, such as emulators.
// The connection is configured with the given ClientOptions.
func DialInsecure(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
	o, err := processAndValidateOpts(opts)
	if err != nil {
		return nil, err
	}
	return dial(ctx, true, o)
}

// DialPool returns a pool of GRPC connections for the given service.
// This differs from the connection pooling implementation used by Dial, which uses a custom GRPC load balancer.
// DialPool should be used instead of Dial when a pool is used by default or a different custom GRPC load balancer is needed.
// The context and options are shared between each Conn in the pool.
// The pool size is configured using the WithGRPCConnectionPool option.
//
// This API is subject to change as we further refine requirements. It will go away if gRPC stubs accept an interface instead of the concrete ClientConn type. See https://github.com/grpc/grpc-go/issues/1287.
func DialPool(ctx context.Context, opts ...option.ClientOption) (ConnPool, error) {
	o, err := processAndValidateOpts(opts)
	if err != nil {
		return nil, err
	}
	if o.GRPCConnPool != nil {
		return o.GRPCConnPool, nil
	}
	poolSize := o.GRPCConnPoolSize
	if o.GRPCConn != nil {
		// WithGRPCConn is technically incompatible with WithGRPCConnectionPool.
		// Always assume pool size is 1 when a grpc.ClientConn is explicitly used.
		poolSize = 1
	}
	o.GRPCConnPoolSize = 0 // we don't *need* to set this to zero, but it's safe to.

	if poolSize == 0 || poolSize == 1 {
		// Fast path for common case for a connection pool with a single connection.
		conn, err := dial(ctx, false, o)
		if err != nil {
			return nil, err
		}
		return &singleConnPool{conn}, nil
	}

	pool := &roundRobinConnPool{}
	for i := 0; i < poolSize; i++ {
		conn, err := dial(ctx, false, o)
		if err != nil {
			defer pool.Close() // NOTE: error from Close is ignored.
			return nil, err
		}
		pool.conns = append(pool.conns, conn)
	}
	return pool, nil
}

func dial(ctx context.Context, insecure bool, o *internal.DialSettings) (*grpc.ClientConn, error) {
	if o.HTTPClient != nil {
		return nil, errors.New("unsupported HTTP client specified")
	}
	if o.GRPCConn != nil {
		return o.GRPCConn, nil
	}
	clientCertSource, endpoint, err := dca.GetClientCertificateSourceAndEndpoint(o)
	if err != nil {
		return nil, err
	}

	var transportCreds credentials.TransportCredentials
	if insecure {
		transportCreds = grpcinsecure.NewCredentials()
	} else {
		transportCreds = credentials.NewTLS(&tls.Config{
			GetClientCertificate: clientCertSource,
		})
	}

	// Initialize gRPC dial options with transport-level security options.
	grpcOpts := []grpc.DialOption{
		grpc.WithTransportCredentials(transportCreds),
	}

	// Authentication can only be sent when communicating over a secure connection.
	//
	// TODO: Should we be more lenient in the future and allow sending credentials
	// when dialing an insecure connection?
	if !o.NoAuth && !insecure {
		if o.APIKey != "" {
			log.Print("API keys are not supported for gRPC APIs. Remove the WithAPIKey option from your client-creating call.")
		}
		creds, err := internal.Creds(ctx, o)
		if err != nil {
			return nil, err
		}

		if o.QuotaProject == "" {
			o.QuotaProject = internal.QuotaProjectFromCreds(creds)
		}

		grpcOpts = append(grpcOpts,
			grpc.WithPerRPCCredentials(grpcTokenSource{
				TokenSource:   oauth.TokenSource{creds.TokenSource},
				quotaProject:  o.QuotaProject,
				requestReason: o.RequestReason,
			}),
		)

		// Attempt Direct Path:
		if isDirectPathEnabled(endpoint, o) && isTokenSourceDirectPathCompatible(creds.TokenSource, o) && metadata.OnGCE() {
			// Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates.
			grpcOpts = []grpc.DialOption{
				grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{oauth.TokenSource{creds.TokenSource}}))}
			if timeoutDialerOption != nil {
				grpcOpts = append(grpcOpts, timeoutDialerOption)
			}
			// Check if google-c2p resolver is enabled for DirectPath
			if strings.EqualFold(os.Getenv(enableDirectPathXds), "true") {
				// google-c2p resolver target must not have a port number
				if addr, _, err := net.SplitHostPort(endpoint); err == nil {
					endpoint = "google-c2p-experimental:///" + addr
				} else {
					endpoint = "google-c2p-experimental:///" + endpoint
				}
			} else {
				if !strings.HasPrefix(endpoint, "dns:///") {
					endpoint = "dns:///" + endpoint
				}
				grpcOpts = append(grpcOpts,
					// For now all DirectPath go clients will be using the following lb config, but in future
					// when different services need different configs, then we should change this to a
					// per-service config.
					grpc.WithDisableServiceConfig(),
					grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`))
			}
			// TODO(cbro): add support for system parameters (quota project, request reason) via chained interceptor.
		}
	}

	if appengineDialerHook != nil {
		// Use the Socket API on App Engine.
		// appengine dialer will override socketopt dialer
		grpcOpts = append(grpcOpts, appengineDialerHook(ctx))
	}

	// Add tracing, but before the other options, so that clients can override the
	// gRPC stats handler.
	// This assumes that gRPC options are processed in order, left to right.
	grpcOpts = addOCStatsHandler(grpcOpts, o)
	grpcOpts = append(grpcOpts, o.GRPCDialOpts...)
	if o.UserAgent != "" {
		grpcOpts = append(grpcOpts, grpc.WithUserAgent(o.UserAgent))
	}

	return grpc.DialContext(ctx, endpoint, grpcOpts...)
}

func addOCStatsHandler(opts []grpc.DialOption, settings *internal.DialSettings) []grpc.DialOption {
	if settings.TelemetryDisabled {
		return opts
	}
	return append(opts, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
}

// grpcTokenSource supplies PerRPCCredentials from an oauth.TokenSource.
type grpcTokenSource struct {
	oauth.TokenSource

	// Additional metadata attached as headers.
	quotaProject  string
	requestReason string
}

// GetRequestMetadata gets the request metadata as a map from a grpcTokenSource.
func (ts grpcTokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (
	map[string]string, error) {
	metadata, err := ts.TokenSource.GetRequestMetadata(ctx, uri...)
	if err != nil {
		return nil, err
	}

	// Attach system parameter
	if ts.quotaProject != "" {
		metadata["X-goog-user-project"] = ts.quotaProject
	}
	if ts.requestReason != "" {
		metadata["X-goog-request-reason"] = ts.requestReason
	}
	return metadata, nil
}

func isDirectPathEnabled(endpoint string, o *internal.DialSettings) bool {
	if !o.EnableDirectPath {
		return false
	}
	if !checkDirectPathEndPoint(endpoint) {
		return false
	}
	if strings.EqualFold(os.Getenv(disableDirectPath), "true") {
		return false
	}
	return true
}

func isTokenSourceDirectPathCompatible(ts oauth2.TokenSource, o *internal.DialSettings) bool {
	if ts == nil {
		return false
	}
	tok, err := ts.Token()
	if err != nil {
		return false
	}
	if tok == nil {
		return false
	}
	if o.AllowNonDefaultServiceAccount {
		return true
	}
	if source, _ := tok.Extra("oauth2.google.tokenSource").(string); source != "compute-metadata" {
		return false
	}
	if acct, _ := tok.Extra("oauth2.google.serviceAccount").(string); acct != "default" {
		return false
	}
	return true
}

func checkDirectPathEndPoint(endpoint string) bool {
	// Only [dns:///]host[:port] is supported, not other schemes (e.g., "tcp://" or "unix://").
	// Also don't try direct path if the user has chosen an alternate name resolver
	// (i.e., via ":///" prefix).
	//
	// TODO(cbro): once gRPC has introspectible options, check the user hasn't
	// provided a custom dialer in gRPC options.
	if strings.Contains(endpoint, "://") && !strings.HasPrefix(endpoint, "dns:///") {
		return false
	}

	if endpoint == "" {
		return false
	}

	return true
}

func processAndValidateOpts(opts []option.ClientOption) (*internal.DialSettings, error) {
	var o internal.DialSettings
	for _, opt := range opts {
		opt.Apply(&o)
	}
	if err := o.Validate(); err != nil {
		return nil, err
	}

	return &o, nil
}

type connPoolOption struct{ ConnPool }

// WithConnPool returns a ClientOption that specifies the ConnPool
// connection to use as the basis of communications.
//
// This is only to be used by Google client libraries internally, for example
// when creating a longrunning API client that shares the same connection pool
// as a service client.
func WithConnPool(p ConnPool) option.ClientOption {
	return connPoolOption{p}
}

func (o connPoolOption) Apply(s *internal.DialSettings) {
	s.GRPCConnPool = o.ConnPool
}
<|endoftext|>


<commit_before>package myqlib

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

type MySQLAdminCommand string

const (
	MYSQLADMIN        string            = "mysqladmin"
	STATUS_COMMAND    MySQLAdminCommand = "extended-status"
	VARIABLES_COMMAND MySQLAdminCommand = "variables"
	// prefix of SHOW VARIABLES keys, they are stored (if available) in the same map as the status variables
	VAR_PREFIX = "V_"
)

type Loader interface {
	getStatus() (chan MyqSample, error)
	getVars() (chan MyqSample, error)
	getInterval() time.Duration
}

// MyqSamples are K->V maps
type MyqSample map[string]string

// Number of keys in the sample
func (s MyqSample) Length() int {
	return len(s)
}

// Get methods for the given key. Returns a value of the appropriate type (error is nil) or default value and an error if it can't parse
func (s MyqSample) getInt(key string) (int64, error) {
	val, ok := s[key]
	if !ok {
		return 0, errors.New("Key not found")
	}

	conv, err := strconv.ParseInt(val, 10, 64)
	if err != nil {
		return 0, err
	} else {
		return conv, nil
	}
}
func (s MyqSample) getFloat(key string) (float64, error) {
	val, ok := s[key]
	if !ok {
		return 0.0, errors.New("Key not found")
	}

	conv, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return 0.0, err
	} else {
		return conv, nil
	}
}
func (s MyqSample) getString(key string) (string, error) {
	val, ok := s[key]
	if !ok {
		return "", errors.New("Key not found")
	}
	return val, nil // no errors possible here
}

// Same as above, just ignore the error
func (s MyqSample) getI(key string) int64 {
	i, _ := s.getInt(key)
	return i
}
func (s MyqSample) getF(key string) float64 {
	f, _ := s.getFloat(key)
	return f
}
func (s MyqSample) getStr(key string) string {
	str, _ := s.getString(key)
	return str
}

// Gets either a float or an int (check type of result), or an error
func (s MyqSample) getNumeric(key string) (interface{}, error) {
	if val, err := s.getInt(key); err != nil {
		return val, nil
	} else if val, err := s.getFloat(key); err != nil {
		return val, nil
	} else {
		return nil, errors.New("Value is not numeric")
	}
}

// MyqState contains the current and previous SHOW STATUS outputs.  Also SHOW VARIABLES.
// Prev might be nil
type MyqState struct {
	Cur, Prev   MyqSample
	SecondsDiff float64 // Difference between Cur and Prev
	FirstUptime int64   // Uptime of our first sample this run
}

// Given a loader, get a channel of myqstates being returned
func GetState(l Loader) (chan *MyqState, error) {
	// First getVars, if possible
	var latestvars MyqSample // whatever the last vars sample is will be here (may be empty)
	varsch, varserr := l.getVars()
	// return the error if getVars fails, but not if it's just due to a missing file
	if varserr != nil && varserr.Error() != "No file given" {
		// Serious error
		return nil, varserr
	}

	// Now getStatus
	var ch = make(chan *MyqState)
	statusch, statuserr := l.getStatus()
	if statuserr != nil {
		return nil, statuserr
	}

	// Main status loop
	go func() {
		defer close(ch)

		var prev MyqSample
		var firstUptime int64
		for status := range statusch {
			// Init new state
			state := new(MyqState)
			state.Cur = status

			// Only needed for File loaders really
			if firstUptime == 0 {
				firstUptime, _ = status.getInt(`uptime`)
			}
			state.FirstUptime = firstUptime

			// Assign the prev
			if prev != nil {
				state.Prev = prev

				// Calcuate timediff if there is a prev.  Only file loader?
				curup, _ := status.getFloat(`uptime`)
				preup, _ := prev.getFloat(`uptime`)
				state.SecondsDiff = curup - preup

				// Skip to the next sample if SecondsDiff is < the interval
				if state.SecondsDiff < l.getInterval().Seconds() {
					continue
				}
			}

			// If varserr is clear at this point, we're expecting some vars
			if varserr == nil {
				// get some new vars, or skip if the varsch is closed
				newvars, ok := <-varsch
				if ok {
					latestvars = newvars
				}
			}

			// Add latest vars to status with prefix
			for k, v := range latestvars {
				newkey := fmt.Sprint(VAR_PREFIX, k)
				state.Cur[newkey] = v
			}

			// Send the state
			ch <- state

			// Set the state for the next round
			prev = status
		}
	}()

	return ch, nil
}

type loaderInterval time.Duration

func (l loaderInterval) getInterval() time.Duration {
	return time.Duration(l)
}

// Load mysql status output from a mysqladmin output file
type FileLoader struct {
	loaderInterval
	statusFile    string
	variablesFile string
}

func NewFileLoader(i time.Duration, statusFile, varFile string) *FileLoader {
	return &FileLoader{loaderInterval(i), statusFile, varFile}
}
func (l FileLoader) harvestFile(filename string) (chan MyqSample, error) {
	file, err := os.OpenFile(filename, os.O_RDONLY, 0)
	if err != nil {
		return nil, err
	}

	var ch = make(chan MyqSample)

	// The file scanning goes into the background
	go func() {
		defer file.Close()
		defer close(ch)
		parseSamples(file, ch, l.loaderInterval.getInterval())
	}()

	return ch, nil
}

func (l FileLoader) getStatus() (chan MyqSample, error) {
	return l.harvestFile(l.statusFile)
}

func (l FileLoader) getVars() (chan MyqSample, error) {
	if l.variablesFile != "" {
		return l.harvestFile(l.variablesFile)
	} else {
		return nil, errors.New("No file given")
	}
}

// SHOW output via mysqladmin on a live server
type LiveLoader struct {
	loaderInterval
	args string // other args for mysqladmin (like -u, -p, -h, etc.)
}

func NewLiveLoader(i time.Duration, args string) *LiveLoader {
	return &LiveLoader{loaderInterval(i), args}
}

// Collect output from mysqladmin and send it back in a sample
func (l LiveLoader) harvestMySQLAdmin(command MySQLAdminCommand) (chan MyqSample, error) {
	// Make sure we have MYSQLADMIN
	path, err := exec.LookPath(MYSQLADMIN)
	if err != nil {
		return nil, err
	}

	// Build the argument list
	args := []string{
		string(command), "-i",
		fmt.Sprintf("%.0f", l.getInterval().Seconds()),
	}
	if l.args != "" {
		args = append(args, strings.Split(l.args, ` `)...)
	}
	// fmt.Println( args )

	// Initialize the command
	cmd := exec.Command(path, args...)
	cleanupSubcmd(cmd)

	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	var ch = make(chan MyqSample)

	// The file scanning goes into the background
	go func() {
		defer close(ch)
		parseSamples(stdout, ch, l.loaderInterval.getInterval())
	}()

	// Handle if the subcommand exits
	go func() {
		err := cmd.Wait()
		if err != nil {
			fmt.Println(MYSQLADMIN, "exited: ", err, stderr.String())
		}
	}()

	return ch, nil
}

func (l LiveLoader) getStatus() (chan MyqSample, error) { return l.harvestMySQLAdmin(STATUS_COMMAND) }

func (l LiveLoader) getVars() (chan MyqSample, error) { return l.harvestMySQLAdmin(VARIABLES_COMMAND) }
<commit_msg>initial support for mysql cli over mysqladmin<commit_after>package myqlib

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

type MySQLCommand string

const (
	MYSQLCLI          string       = "mysql"
	STATUS_COMMAND    MySQLCommand = "SHOW GLOBAL STATUS;\n"
	VARIABLES_COMMAND MySQLCommand = "SHOW GLOBAL VARIABLES;\n"
	// prefix of SHOW VARIABLES keys, they are stored (if available) in the same map as the status variables
	VAR_PREFIX = "V_"
)

type Loader interface {
	getStatus() (chan MyqSample, error)
	getVars() (chan MyqSample, error)
	getInterval() time.Duration
}

// MyqSamples are K->V maps
type MyqSample map[string]string

// Number of keys in the sample
func (s MyqSample) Length() int {
	return len(s)
}

// Get methods for the given key. Returns a value of the appropriate type (error is nil) or default value and an error if it can't parse
func (s MyqSample) getInt(key string) (int64, error) {
	val, ok := s[key]
	if !ok {
		return 0, errors.New("Key not found")
	}

	conv, err := strconv.ParseInt(val, 10, 64)
	if err != nil {
		return 0, err
	} else {
		return conv, nil
	}
}
func (s MyqSample) getFloat(key string) (float64, error) {
	val, ok := s[key]
	if !ok {
		return 0.0, errors.New("Key not found")
	}

	conv, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return 0.0, err
	} else {
		return conv, nil
	}
}
func (s MyqSample) getString(key string) (string, error) {
	val, ok := s[key]
	if !ok {
		return "", errors.New("Key not found")
	}
	return val, nil // no errors possible here
}

// Same as above, just ignore the error
func (s MyqSample) getI(key string) int64 {
	i, _ := s.getInt(key)
	return i
}
func (s MyqSample) getF(key string) float64 {
	f, _ := s.getFloat(key)
	return f
}
func (s MyqSample) getStr(key string) string {
	str, _ := s.getString(key)
	return str
}

// Gets either a float or an int (check type of result), or an error
func (s MyqSample) getNumeric(key string) (interface{}, error) {
	if val, err := s.getInt(key); err != nil {
		return val, nil
	} else if val, err := s.getFloat(key); err != nil {
		return val, nil
	} else {
		return nil, errors.New("Value is not numeric")
	}
}

// MyqState contains the current and previous SHOW STATUS outputs.  Also SHOW VARIABLES.
// Prev might be nil
type MyqState struct {
	Cur, Prev   MyqSample
	SecondsDiff float64 // Difference between Cur and Prev
	FirstUptime int64   // Uptime of our first sample this run
}

// Given a loader, get a channel of myqstates being returned
func GetState(l Loader) (chan *MyqState, error) {
	// First getVars, if possible
	var latestvars MyqSample // whatever the last vars sample is will be here (may be empty)
	varsch, varserr := l.getVars()
	// return the error if getVars fails, but not if it's just due to a missing file
	if varserr != nil && varserr.Error() != "No file given" {
		// Serious error
		return nil, varserr
	}

	// Now getStatus
	var ch = make(chan *MyqState)
	statusch, statuserr := l.getStatus()
	if statuserr != nil {
		return nil, statuserr
	}

	// Main status loop
	go func() {
		defer close(ch)

		var prev MyqSample
		var firstUptime int64
		for status := range statusch {
			// Init new state
			state := new(MyqState)
			state.Cur = status

			// Only needed for File loaders really
			if firstUptime == 0 {
				firstUptime, _ = status.getInt(`uptime`)
			}
			state.FirstUptime = firstUptime

			// Assign the prev
			if prev != nil {
				state.Prev = prev

				// Calcuate timediff if there is a prev.  Only file loader?
				curup, _ := status.getFloat(`uptime`)
				preup, _ := prev.getFloat(`uptime`)
				state.SecondsDiff = curup - preup

				// Skip to the next sample if SecondsDiff is < the interval
				if state.SecondsDiff < l.getInterval().Seconds() {
					continue
				}
			}

			// If varserr is clear at this point, we're expecting some vars
			if varserr == nil {
				// get some new vars, or skip if the varsch is closed
				newvars, ok := <-varsch
				if ok {
					latestvars = newvars
				}
			}

			// Add latest vars to status with prefix
			for k, v := range latestvars {
				newkey := fmt.Sprint(VAR_PREFIX, k)
				state.Cur[newkey] = v
			}

			// Send the state
			ch <- state

			// Set the state for the next round
			prev = status
		}
	}()

	return ch, nil
}

type loaderInterval time.Duration

func (l loaderInterval) getInterval() time.Duration {
	return time.Duration(l)
}

// Load mysql status output from a mysqladmin output file
type FileLoader struct {
	loaderInterval
	statusFile    string
	variablesFile string
}

func NewFileLoader(i time.Duration, statusFile, varFile string) *FileLoader {
	return &FileLoader{loaderInterval(i), statusFile, varFile}
}
func (l FileLoader) harvestFile(filename string) (chan MyqSample, error) {
	file, err := os.OpenFile(filename, os.O_RDONLY, 0)
	if err != nil {
		return nil, err
	}

	var ch = make(chan MyqSample)

	// The file scanning goes into the background
	go func() {
		defer file.Close()
		defer close(ch)
		parseSamples(file, ch, l.loaderInterval.getInterval())
	}()

	return ch, nil
}

func (l FileLoader) getStatus() (chan MyqSample, error) {
	return l.harvestFile(l.statusFile)
}

func (l FileLoader) getVars() (chan MyqSample, error) {
	if l.variablesFile != "" {
		return l.harvestFile(l.variablesFile)
	} else {
		return nil, errors.New("No file given")
	}
}

// SHOW output via mysqladmin on a live server
type LiveLoader struct {
	loaderInterval
	args string // other args for mysqladmin (like -u, -p, -h, etc.)
}

func NewLiveLoader(i time.Duration, args string) *LiveLoader {
	return &LiveLoader{loaderInterval(i), args}
}

// Collect output from MYSQLCLI and send it back in a sample
func (l LiveLoader) harvestMySQL(command MySQLCommand) (chan MyqSample, error) {
	// Make sure we have MYSQLCLI
	path, err := exec.LookPath(MYSQLCLI)
	if err != nil {
		return nil, err
	}

	// Build the argument list
	args := []string{
		"-B",
	}
	if l.args != "" {
		args = append(args, strings.Split(l.args, ` `)...)
	}
	// fmt.Println( args )

	// Initialize the command
	cmd := exec.Command(path, args...)
	cleanupSubcmd(cmd)

	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	// parse samples in the background
	var ch = make(chan MyqSample)
	go func() {
		defer close(ch)
		parseSamples(stdout, ch, l.loaderInterval.getInterval())
	}()

	// feed the MYSQLCLI the given command every interval to produce more output
	ticker := time.NewTicker(l.getInterval())
	go func() {
		defer stdin.Close()
		for range ticker.C {
			_, err := stdin.Write([]byte(command))
			if err != nil {
				panic("Could not write to MySQL command any longer")
			}
		}
	}()

	// Handle if the subcommand exits
	go func() {
		err := cmd.Wait()
		if err != nil {
			fmt.Println(MYSQLCLI, "exited: ", err, stderr.String())
		}
	}()

	return ch, nil
}

func (l LiveLoader) getStatus() (chan MyqSample, error) { return l.harvestMySQL(STATUS_COMMAND) }

func (l LiveLoader) getVars() (chan MyqSample, error) { return l.harvestMySQL(VARIABLES_COMMAND) }
<|endoftext|>


<commit_before>//Author : Thomas Duffy
//Date : 25/9/17

//Adapted from : https://play.golang.org/p/vhHmjhOMEo

package main

import "fmt"

func main() { //function to create a list of numbers
	x := []int{
		48, 96, 86, 68,
		57, 82, 63, 70,
		37, 34, 83, 27,
		19, 97, 9, 17,
	}

	smallest, biggest := x[0], x[0] //set largest and smallest to 0
	for _, v := range x { //loop through the list
		if v > biggest { //if the number is bigger than the last sorted largest overwrite it
			biggest = v
		}
		if v < smallest { //if the number is smaller than the last sorted smallest overwrite it
			smallest = v
		}
	}
	//print out largest and smallest
	fmt.Println("The biggest number is ", biggest)
	fmt.Println("The smallest number is ", smallest)
}<commit_msg>Added list print out<commit_after>//Author : Thomas Duffy
//Date : 25/9/17

//Adapted from : https://play.golang.org/p/vhHmjhOMEo

package main

import "fmt"

func main() { //function to create a list of numbers
	x := []int{
		48, 96, 86, 68,
		57, 82, 63, 70,
		37, 34, 83, 27,
		19, 97, 9, 17,
	}

	smallest, biggest := x[0], x[0] //set largest and smallest to 0
	for _, v := range x { //loop through the list
		if v > biggest { //if the number is bigger than the last sorted largest overwrite it
			biggest = v
		}
		if v < smallest { //if the number is smaller than the last sorted smallest overwrite it
			smallest = v
		}
	}
	//print out largest and smallest
	fmt.Println("List of numbers used : ", x)
	fmt.Println("The biggest number is ", biggest)
	fmt.Println("The smallest number is ", smallest)
}<|endoftext|>


<commit_before>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strconv"
)

type MarketItemPriceOverview struct {
	Success     bool   `json:"success"`
	LowestPrice string `json:"lowest_price"`
	MedianPrice string `json:"median_price"`
	Volume      uint32 `json:"volume,string"`
}

type MarketItemPrice struct {
	Date  string
	Price float64
	Count string
}

type MarketItemResponse struct {
	Success     bool        `json:"success"`
	PricePrefix string      `json:"price_prefix"`
	PriceSuffix string      `json:"price_suffix"`
	Prices      interface{} `json:"prices"`
}

var (
	ErrCannotLoadPrices     = errors.New("unable to load prices at this time")
	ErrInvalidPriceResponse = errors.New("invalid market pricehistory response")
)

func (community *Community) GetMarketItemPriceHistory(appID uint16, marketHashName string) ([]*MarketItemPrice, error) {
	resp, err := community.client.Get("https://steamcommunity.com/market/pricehistory/?" + url.Values{
		"appid":            {strconv.FormatUint(uint64(appID), 10)},
		"market_hash_name": {marketHashName},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	response := MarketItemResponse{}
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	if !response.Success {
		return nil, ErrCannotLoadPrices
	}

	switch response.Prices.(type) {
	case []interface{}:
		items := []*MarketItemPrice{}
		for _, v := range response.Prices.([]interface{}) {
			switch v.(type) {
			case []interface{}:
				d := v.([]interface{})
				item := &MarketItemPrice{}
				for _, val := range d {
					switch val.(type) {
					case string:
						if len(item.Date) != 0 {
							item.Count = val.(string)
						} else {
							item.Date = val.(string)
						}
					case float64:
						item.Price = val.(float64)
					}
				}

				items = append(items, item)
			default:
				// ignore
			}
		}

		return items, nil
	case bool:
		return nil, ErrCannotLoadPrices
	}

	return nil, fmt.Errorf("GetMarketItemPriceHistory(): please implement type handler for %v", response.Prices)
}

func (community *Community) GetMarketItemPriceOverview(appID uint16, marketHashName string) (*MarketItemPriceOverview, error) {
	resp, err := community.client.Get("https://steamcommunity.com/market/priceoverview/?" + url.Values{
		"appid":            {strconv.FormatUint(uint64(appID), 10)},
		"market_hash_name": {marketHashName},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	overview := &MarketItemPriceOverview{}
	if err = json.NewDecoder(resp.Body).Decode(overview); err != nil {
		return nil, err
	}

	return overview, nil
}
<commit_msg>market: volume is now a string<commit_after>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strconv"
)

type MarketItemPriceOverview struct {
	Success     bool   `json:"success"`
	LowestPrice string `json:"lowest_price"`
	MedianPrice string `json:"median_price"`
	Volume      string `json:"volume"`
}

type MarketItemPrice struct {
	Date  string
	Price float64
	Count string
}

type MarketItemResponse struct {
	Success     bool        `json:"success"`
	PricePrefix string      `json:"price_prefix"`
	PriceSuffix string      `json:"price_suffix"`
	Prices      interface{} `json:"prices"`
}

var (
	ErrCannotLoadPrices     = errors.New("unable to load prices at this time")
	ErrInvalidPriceResponse = errors.New("invalid market pricehistory response")
)

func (community *Community) GetMarketItemPriceHistory(appID uint16, marketHashName string) ([]*MarketItemPrice, error) {
	resp, err := community.client.Get("https://steamcommunity.com/market/pricehistory/?" + url.Values{
		"appid":            {strconv.FormatUint(uint64(appID), 10)},
		"market_hash_name": {marketHashName},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	response := MarketItemResponse{}
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	if !response.Success {
		return nil, ErrCannotLoadPrices
	}

	switch response.Prices.(type) {
	case []interface{}:
		items := []*MarketItemPrice{}
		for _, v := range response.Prices.([]interface{}) {
			switch v.(type) {
			case []interface{}:
				d := v.([]interface{})
				item := &MarketItemPrice{}
				for _, val := range d {
					switch val.(type) {
					case string:
						if len(item.Date) != 0 {
							item.Count = val.(string)
						} else {
							item.Date = val.(string)
						}
					case float64:
						item.Price = val.(float64)
					}
				}

				items = append(items, item)
			default:
				// ignore
			}
		}

		return items, nil
	case bool:
		return nil, ErrCannotLoadPrices
	}

	return nil, fmt.Errorf("GetMarketItemPriceHistory(): please implement type handler for %v", response.Prices)
}

func (community *Community) GetMarketItemPriceOverview(appID uint16, marketHashName string) (*MarketItemPriceOverview, error) {
	resp, err := community.client.Get("https://steamcommunity.com/market/priceoverview/?" + url.Values{
		"appid":            {strconv.FormatUint(uint64(appID), 10)},
		"market_hash_name": {marketHashName},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	overview := &MarketItemPriceOverview{}
	if err = json.NewDecoder(resp.Body).Decode(overview); err != nil {
		return nil, err
	}

	return overview, nil
}
<|endoftext|>


<commit_before>package vecty

import (
	"fmt"
	"strings"

	"github.com/gopherjs/gopherjs/js"
)

// Markup represents some markup that can be applied to a DOM element. For
// example, styles like font size, properties like checked status of an input,
// or child elements.
type Markup interface {
	// Apply should apply the markup to the given element.
	Apply(element *Element)
}

type property struct {
	Name  string
	Value interface{}
}

// Apply implements the Markup interface.
func (p *property) Apply(element *Element) {
	if element.Properties == nil {
		element.Properties = make(map[string]interface{})
	}
	if _, ok := element.Properties[p.Name]; ok {
		panic(fmt.Sprintf("duplicate property: %s", p.Name))
	}
	element.Properties[p.Name] = p.Value
}

// Property returns Markup which applies the given value to the named property
// of an DOM element.
func Property(name string, value interface{}) Markup {
	return &property{Name: name, Value: value}
}

// ClassMap is markup that specifies classes to be applied to an element if
// their boolean value are true.
type ClassMap map[string]bool

// Apply implements the Markup interface.
func (m ClassMap) Apply(element *Element) {
	var classes []string
	for name, active := range m {
		if active {
			classes = append(classes, name)
		}
	}
	Property("className", strings.Join(classes, " ")).Apply(element)
}

type style struct {
	Name  string
	Value interface{}
}

// Apply implements the Markup interface.
func (s *style) Apply(element *Element) {
	if element.Style == nil {
		element.Style = make(map[string]interface{})
	}
	if _, ok := element.Style[s.Name]; ok {
		panic(fmt.Sprintf("duplicate style: %s", s.Name))
	}
	element.Style[s.Name] = s.Value
}

// Style returns Markup which applies the style with the given value to an
// element.
func Style(name string, value interface{}) Markup {
	return &style{Name: name, Value: value}
}

// EventListener is markup that specifies a callback function to be invoked when
// the named DOM event is fired.
type EventListener struct {
	Name               string
	Listener           func(*Event)
	callPreventDefault bool
	wrapper            func(jsEvent *js.Object)
}

// PreventDefault prevents the default behavior of the event from occuring.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault.
func (l *EventListener) PreventDefault() *EventListener {
	l.callPreventDefault = true
	return l
}

// Apply implements the Markup interface.
func (l *EventListener) Apply(element *Element) {
	element.EventListeners = append(element.EventListeners, l)
}

// Event represents a DOM event.
type Event struct {
	Target *js.Object
}

// List represents a list of markup which will all be applied to an element.
type List []Markup

// Apply implements the Markup interface.
func (g List) Apply(element *Element) {
	for _, m := range g {
		if m != nil {
			m.Apply(element)
		}
	}
}

// If returns nil if cond is false, otherwise it returns a list of markup.
func If(cond bool, markup ...Markup) Markup {
	if cond {
		return List(markup)
	}
	return nil
}
<commit_msg>doc comment fix<commit_after>package vecty

import (
	"fmt"
	"strings"

	"github.com/gopherjs/gopherjs/js"
)

// Markup represents some markup that can be applied to a DOM element. For
// example, styles like font size, properties like checked status of an input,
// or child elements.
type Markup interface {
	// Apply should apply the markup to the given element.
	Apply(element *Element)
}

type property struct {
	Name  string
	Value interface{}
}

// Apply implements the Markup interface.
func (p *property) Apply(element *Element) {
	if element.Properties == nil {
		element.Properties = make(map[string]interface{})
	}
	if _, ok := element.Properties[p.Name]; ok {
		panic(fmt.Sprintf("duplicate property: %s", p.Name))
	}
	element.Properties[p.Name] = p.Value
}

// Property returns Markup which applies the given value to the named property
// of a DOM element.
func Property(name string, value interface{}) Markup {
	return &property{Name: name, Value: value}
}

// ClassMap is markup that specifies classes to be applied to an element if
// their boolean value are true.
type ClassMap map[string]bool

// Apply implements the Markup interface.
func (m ClassMap) Apply(element *Element) {
	var classes []string
	for name, active := range m {
		if active {
			classes = append(classes, name)
		}
	}
	Property("className", strings.Join(classes, " ")).Apply(element)
}

type style struct {
	Name  string
	Value interface{}
}

// Apply implements the Markup interface.
func (s *style) Apply(element *Element) {
	if element.Style == nil {
		element.Style = make(map[string]interface{})
	}
	if _, ok := element.Style[s.Name]; ok {
		panic(fmt.Sprintf("duplicate style: %s", s.Name))
	}
	element.Style[s.Name] = s.Value
}

// Style returns Markup which applies the style with the given value to an
// element.
func Style(name string, value interface{}) Markup {
	return &style{Name: name, Value: value}
}

// EventListener is markup that specifies a callback function to be invoked when
// the named DOM event is fired.
type EventListener struct {
	Name               string
	Listener           func(*Event)
	callPreventDefault bool
	wrapper            func(jsEvent *js.Object)
}

// PreventDefault prevents the default behavior of the event from occuring.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault.
func (l *EventListener) PreventDefault() *EventListener {
	l.callPreventDefault = true
	return l
}

// Apply implements the Markup interface.
func (l *EventListener) Apply(element *Element) {
	element.EventListeners = append(element.EventListeners, l)
}

// Event represents a DOM event.
type Event struct {
	Target *js.Object
}

// List represents a list of markup which will all be applied to an element.
type List []Markup

// Apply implements the Markup interface.
func (g List) Apply(element *Element) {
	for _, m := range g {
		if m != nil {
			m.Apply(element)
		}
	}
}

// If returns nil if cond is false, otherwise it returns a list of markup.
func If(cond bool, markup ...Markup) Markup {
	if cond {
		return List(markup)
	}
	return nil
}
<|endoftext|>


<commit_before>package marvel

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"

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

type Client struct {
	public, private string
}

func NewClient(public, private string) Client {
	return Client{public, private}
}

// See http://developer.marvel.com/documentation/authorization
func (c Client) hash() (int64, string) {
	ts := time.Now().Unix()
	hash := md5.New()
	io.WriteString(hash, fmt.Sprintf("%d%s%s", ts, c.private, c.public))
	return ts, fmt.Sprintf("%x", hash.Sum(nil))
}

func (c Client) baseURL(req interface{}) url.URL {
	u := url.URL{
		Scheme: "https",
		Host:   "gateway.marvel.com",
		Path:   "/v1/public/",
	}
	ts, hash := c.hash()
	u.RawQuery = url.Values(map[string][]string{
		"ts":     []string{fmt.Sprintf("%d", ts)},
		"apikey": []string{c.public},
		"hash":   []string{hash},
	}).Encode()
	if req != nil {
		q, _ := query.Values(req)
		u.RawQuery += "&" + q.Encode()
	}
	return u
}

// Fields common to all response entities
type commonResponse struct {
	Code            int    `json:"code"`
	ETag            string `json:"etag"`
	Status          string `json:"status"`
	Copyright       string `json:"copyright"`
	AttributionText string `json:"attributionText"`
	AttributionHTML string `json:"attributionHTML"`
}

type CommonRequest struct {
	Offset int `url:"offset,omitempty"`
	Limit  int `url:"limit,omitempty"`
}

// Fields common to data that lists entities, with pagination
type commonList struct {
	Offset int `json:"offset"`
	Limit  int `json:"limit"`
	Total  int `json:"total"`
	Count  int `json:"count"`
}

type Image struct {
	Path      string
	Extension string
}

type Variant string

var (
	PortraitSmall       = Variant("portrait_small")
	PortraitMedium      = Variant("portrait_medium")
	PortraitXLarge      = Variant("portrait_xlarge")
	PortraitFantastic   = Variant("portrait_fantastic")
	PortraitUncanny     = Variant("portrait_uncanny")
	PortraitIncredible  = Variant("portrait_incredible")
	StandardSmall       = Variant("standard_small")
	StandardMedium      = Variant("standard_medium")
	StandardXLarge      = Variant("standard_xlarge")
	StandardFantastic   = Variant("standard_fantastic")
	StandardUncanny     = Variant("standard_uncanny")
	StandardIncredible  = Variant("standard_incredible")
	LandscapeSmall      = Variant("landscape_small")
	LandscapeMedium     = Variant("landscape_medium")
	LandscapeXLarge     = Variant("landscape_xlarge")
	LandscapeFantastic  = Variant("landscape_fantastic")
	LandscapeUncanny    = Variant("landscape_uncanny")
	LandscapeIncredible = Variant("landscape_incredible")
)

func (i Image) URL(v Variant) string {
	return fmt.Sprintf("%s/%s.%s", i.Path, string(v), i.Extension)
}

type Date string

const dateLayout = "2006-01-02T15:04:05-0700"

func (d Date) Parse() time.Time {
	t, err := time.Parse(dateLayout, string(d))
	if err != nil {
		panic(err)
	}
	return t
}

func (c Client) Series(id int64, req CommonRequest) (resp struct {
	commonResponse
	Data struct {
		commonList
		Results []struct {
			ID          int
			Title       string
			Description string
			ResourceURI string
			URLs        []struct {
				Type string
				URL  string
			}
			StartYear int
			EndYear   int
			Rating    string
			Modified  Date
			Thumbnail Image
			Comics    struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
				}
			}
			Stories struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Events struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Characters struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Creators struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Next struct {
				ResourceURI string
				Name        string
			}
			Previous struct {
				ResourceURI string
				Name        string
			}
		}
	}
}, err error) {
	u := c.baseURL(req)
	u.Path += fmt.Sprintf("series/%d/comics", id)
	r, err := c.fetch(u)
	if err != nil {
		return
	}
	defer r.Close()
	err = json.NewDecoder(r).Decode(&resp)
	return
}

func (c Client) fetch(u url.URL) (io.ReadCloser, error) {
	resp, err := http.Get(u.String())
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= http.StatusBadRequest {
		slurp, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		return nil, fmt.Errorf("error response from API: %d\n%s", resp.StatusCode, slurp)
	}
	return resp.Body, nil
}
<commit_msg>going to bed, some TODOs for later<commit_after>// TODO: For structs that define a ResourceURI, add a method to fetch those
//       contents and parse them into the correct response struct.
//       e.g., Series(123).Data.Results[0].Characters.Items[0].Get()...
// TODO: Add a test to fetch a resource, serialize it into JSON and compare
//       it against the response JSON to catch missing fields
// TODO: Figure out the correct incantation to let Series not have to take an
//       empty struct and instead take nil
// TODO: Find/write Swagger Go client generator?

package marvel

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"

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

type Client struct {
	public, private string
}

func NewClient(public, private string) Client {
	return Client{public, private}
}

// See http://developer.marvel.com/documentation/authorization
func (c Client) hash() (int64, string) {
	ts := time.Now().Unix()
	hash := md5.New()
	io.WriteString(hash, fmt.Sprintf("%d%s%s", ts, c.private, c.public))
	return ts, fmt.Sprintf("%x", hash.Sum(nil))
}

func (c Client) baseURL(req interface{}) url.URL {
	u := url.URL{
		Scheme: "https",
		Host:   "gateway.marvel.com",
		Path:   "/v1/public/",
	}
	ts, hash := c.hash()
	u.RawQuery = url.Values(map[string][]string{
		"ts":     []string{fmt.Sprintf("%d", ts)},
		"apikey": []string{c.public},
		"hash":   []string{hash},
	}).Encode()
	if req != nil {
		q, _ := query.Values(req)
		u.RawQuery += "&" + q.Encode()
	}
	return u
}

// Fields common to all response entities
type commonResponse struct {
	Code            int    `json:"code"`
	ETag            string `json:"etag"`
	Status          string `json:"status"`
	Copyright       string `json:"copyright"`
	AttributionText string `json:"attributionText"`
	AttributionHTML string `json:"attributionHTML"`
}

type CommonRequest struct {
	Offset int `url:"offset,omitempty"`
	Limit  int `url:"limit,omitempty"`
}

// Fields common to data that lists entities, with pagination
type commonList struct {
	Offset int `json:"offset"`
	Limit  int `json:"limit"`
	Total  int `json:"total"`
	Count  int `json:"count"`
}

type Image struct {
	Path      string
	Extension string
}

type Variant string

var (
	PortraitSmall       = Variant("portrait_small")
	PortraitMedium      = Variant("portrait_medium")
	PortraitXLarge      = Variant("portrait_xlarge")
	PortraitFantastic   = Variant("portrait_fantastic")
	PortraitUncanny     = Variant("portrait_uncanny")
	PortraitIncredible  = Variant("portrait_incredible")
	StandardSmall       = Variant("standard_small")
	StandardMedium      = Variant("standard_medium")
	StandardXLarge      = Variant("standard_xlarge")
	StandardFantastic   = Variant("standard_fantastic")
	StandardUncanny     = Variant("standard_uncanny")
	StandardIncredible  = Variant("standard_incredible")
	LandscapeSmall      = Variant("landscape_small")
	LandscapeMedium     = Variant("landscape_medium")
	LandscapeXLarge     = Variant("landscape_xlarge")
	LandscapeFantastic  = Variant("landscape_fantastic")
	LandscapeUncanny    = Variant("landscape_uncanny")
	LandscapeIncredible = Variant("landscape_incredible")
)

func (i Image) URL(v Variant) string {
	return fmt.Sprintf("%s/%s.%s", i.Path, string(v), i.Extension)
}

type Date string

const dateLayout = "2006-01-02T15:04:05-0700"

func (d Date) Parse() time.Time {
	t, err := time.Parse(dateLayout, string(d))
	if err != nil {
		panic(err)
	}
	return t
}

func (c Client) Series(id int64, req CommonRequest) (resp struct {
	commonResponse
	Data struct {
		commonList
		Results []struct {
			ID          int
			Title       string
			Description string
			ResourceURI string
			URLs        []struct {
				Type string
				URL  string
			}
			StartYear int
			EndYear   int
			Rating    string
			Modified  Date
			Thumbnail Image
			Comics    struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
				}
			}
			Stories struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Events struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Characters struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Creators struct {
				Available     int
				Returned      int
				CollectionURI string
				Items         []struct {
					ResourceURI string
					Name        string
					Type        string
				}
			}
			Next struct {
				ResourceURI string
				Name        string
			}
			Previous struct {
				ResourceURI string
				Name        string
			}
		}
	}
}, err error) {
	u := c.baseURL(req)
	u.Path += fmt.Sprintf("series/%d/comics", id)
	r, err := c.fetch(u)
	if err != nil {
		return
	}
	defer r.Close()
	err = json.NewDecoder(r).Decode(&resp)
	return
}

func (c Client) fetch(u url.URL) (io.ReadCloser, error) {
	resp, err := http.Get(u.String())
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= http.StatusBadRequest {
		slurp, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		return nil, fmt.Errorf("error response from API: %d\n%s", resp.StatusCode, slurp)
	}
	return resp.Body, nil
}
<|endoftext|>