<commit_before>package orm

import (
	"database/sql"
	"errors"
	"fmt"
	"log"
	"reflect"
	"strings"
)

var dbHive map[string]*sql.DB = make(map[string]*sql.DB)

//create new database
func NewDatabase(dbname, dbtype, url string) {
	db, err := sql.Open(dbtype, url)
	if err != nil {
		panic(err.Error())
	}
	err = db.Ping()
	if err != nil {
		panic(err.Error())
	}
	dbHive[dbname] = db
}

//module
type Module struct {
	columnstr string //select field
	tableName string //table
	filters   string //condition
	orderby   string //orderby
	limit     string //limit
	join      string //join
	pk        string //pk
	dbname    string //dbname
}

//create new Module
func NewModule(tableName string) *Module {
	m := &Module{tableName: tableName, columnstr: "*", dbname: "default", pk: "id"}
	return m
}

func (m *Module) Clean() *Module {
	m.columnstr = "*"
	m.filters = ""
	m.orderby = ""
	m.limit = ""
	m.join = ""
	m.pk = "id"
	return m
}

func (m *Module) GetDB() *sql.DB {
	return dbHive[m.dbname]
}

//change db
func (m *Module) User(dbname string) *Module {
	m.dbname = dbname
	return m
}

//select fields
func (m *Module) Select(fields ...string) *Module {
	m.columnstr = ""
	for _, f := range fields {
		m.columnstr = m.columnstr + f + ","
	}
	return m
}

//Filter
func (m *Module) Filter(param ...interface{}) *Module {
	for _, p := range param {
		m.filters += fmt.Sprintf("%v", p)
	}
	return m
}

//orderBy
func (m *Module) OrderBy(param string) *Module {
	m.orderby = fmt.Sprintf("ORDER By %v", param)
	return m
}

//limit
func (m *Module) Limit(size ...int) *Module {
	if len(size) > 1 {
		m.limit = fmt.Sprintf("Limit %d,%d", size[0], size[1])
		return m
	} else {
		m.limit = fmt.Sprintf("Limit %d", size[0])
		return m
	}
}

//leftJoin
func (m *Module) LeftJoin(table, condition string) *Module {
	m.join = fmt.Sprintf("LEFT JOIN %v ON %v", table, condition)
	return m
}

//rightJoin
func (m *Module) RightJoin(table, condition string) *Module {
	m.join = fmt.Sprintf("RIGHT JOIN %v ON %v", table, condition)
	return m
}

//join
func (m *Module) Join(table, condition string) *Module {
	m.join = fmt.Sprintf("INNER JOIN %v ON %v", table, condition)
	return m
}

//fulljoin
func (m *Module) FullJoin(table, condition string) *Module {
	m.join = fmt.Sprintf("FULL JOIN %v ON %v", table, condition)
	return m
}

func (m *Module) getSqlString() string {
	columnstr := m.columnstr
	if l := len(columnstr); l > 1 {
		columnstr = columnstr[:l-1]
	}
	query := m.buildSql(columnstr)
	query += " " + m.limit
	log.Println("sql = ", query)
	return query
}

func (m *Module) buildSql(columnstr string) string {
	where := m.filters
	where = strings.TrimSpace(where)
	if len(where) > 0 {
		where = "where " + where
	}
	query := fmt.Sprintf("select %v from %v %v %v %v", columnstr, m.tableName, m.join, where, m.orderby)
	return query
}

func (m *Module) QueryPage(page *Page, callBackFunc func(*sql.Rows)) error {
	db := dbHive[m.dbname]
	m.Limit(page.StartRow(), page.PageSize)
	query := m.buildSql("count(*)")
	log.Println(query)
	row := db.QueryRow(query)
	err := row.Scan(&page.ResultCount)
	if err != nil {
		return err
	}
	rows, err := db.Query(m.getSqlString())
	if err != nil {
		return err
	}
	defer rows.Close()
	callBackFunc(rows)
	return nil
}

func (m *Module) Query(callBackFunc func(*sql.Rows)) error {
	db := dbHive[m.dbname]
	rows, err := db.Query(m.getSqlString())
	if err != nil {
		return err
	}
	defer rows.Close()
	callBackFunc(rows)
	return nil
}
func (m *Module) QueryOne(callBackFunc func(*sql.Row)) {
	db := dbHive[m.dbname]
	row := db.QueryRow(m.getSqlString())
	callBackFunc(row)
}

func (m *Module) IsExist() (bool, error) {
	count, err := m.Count()
	if count > 0 {
		return true, nil
	}
	return false, err
}

func (m *Module) Count() (int, error) {
	db := dbHive[m.dbname]
	query := m.buildSql("count(*)")
	log.Println("sql = ", query)
	row := db.QueryRow(query)
	var count int
	err := row.Scan(&count)
	return count, err
}

func (m *Module) OneRecord() (record Record, err error) {
	rs, err := m.Limit(1).AllRecords()
	if err != nil {
		return record, err
	}
	if len(rs) == 0 {
		return NewRecord(), errors.New("not fond record")
	}
	return rs[0], nil
}

func (m *Module) AllRecords() ([]Record, error) {
	db := dbHive[m.dbname]
	rows, err := db.Query(m.getSqlString())
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	records := make([]Record, 0)
	columns, _ := rows.Columns()
	values := make([]sql.RawBytes, len(columns))
	scanargs := make([]interface{}, len(values))
	for i := range values {
		scanargs[i] = &values[i]
	}
	for rows.Next() {
		err := rows.Scan(scanargs...)
		if err != nil {
			fmt.Println(err)
		}
		record := NewRecord()
		for i, v := range values {
			record.result[columns[i]] = v
		}
		records = append(records, record)
	}
	return records, nil
}
func (m *Module) SetPK(pk string) *Module {
	m.pk = pk
	return m
}

func (m *Module) FindRecordById(id int) *Module {
	m.Filter(m.pk, "=", id)
	return m
}

func (m *Module) Insert(record Record) (int, error) {
	columns := ""
	values := ""
	for c, v := range record.param {
		columns = columns + c + ","
		rv := reflect.ValueOf(v)
		switch rv.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Bool:
			values = values + fmt.Sprintf("%v", v) + ","
		default:
			values = values + fmt.Sprintf("'%v'", v) + ","
		}
	}
	if l := len(columns); l > 0 {
		columns = columns[:l-1]
	}
	if l := len(values); l > 0 {
		values = values[:l-1]
	}
	insertSql := fmt.Sprintf("insert into %v(%v) values(%v)", m.tableName, columns, values)
	fmt.Println(insertSql)
	db := dbHive[m.dbname]
	result, err := db.Exec(insertSql)
	if err != nil {
		return 0, err
	}
	id, err := result.LastInsertId()
	return int(id), err
}

func (m *Module) Update(record Record) error {
	values := ""
	for c, v := range record.param {
		values = values + c + "="
		rv := reflect.ValueOf(v)
		switch rv.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Bool:
			values += fmt.Sprintf("%v", v)
		default:
			values += fmt.Sprintf("'%v'", v)
		}
		values += ","
	}
	if l := len(values); l > 0 {
		values = values[:l-1]
	}
	sql := fmt.Sprintf("update %v set %v where %v", m.tableName, values, m.filters)
	log.Println("sql = ", sql)
	db := dbHive[m.dbname]
	_, err := db.Exec(sql)
	return err
}

func (m *Module) DeleteById(id int) error {
	m.Filter(m.pk, "=", id)
	return m.Delete()
}

func (m *Module) FindById(id int) *Module {
	m.Filter(m.pk, "=", id)
	return m
}

func (m *Module) Delete() error {
	where := m.filters
	where = strings.TrimSpace(where)
	if len(where) > 0 {
		where = "where " + where
	}
	delSql := fmt.Sprintf("delete from %v %v", m.tableName, where)
	fmt.Println(delSql)
	_, err := dbHive[m.dbname].Exec(delSql)
	return err
}
<commit_msg>fix left join table<commit_after>package orm

import (
	"database/sql"
	"errors"
	"fmt"
	"log"
	"reflect"
	"strings"
)

var dbHive map[string]*sql.DB = make(map[string]*sql.DB)

//create new database
func NewDatabase(dbname, dbtype, url string) {
	db, err := sql.Open(dbtype, url)
	if err != nil {
		panic(err.Error())
	}
	err = db.Ping()
	if err != nil {
		panic(err.Error())
	}
	dbHive[dbname] = db
}

//module
type Module struct {
	columnstr string //select field
	tableName string //table
	filters   string //condition
	orderby   string //orderby
	limit     string //limit
	join      string //join
	pk        string //pk
	dbname    string //dbname
}

//create new Module
func NewModule(tableName string) *Module {
	m := &Module{tableName: tableName, columnstr: "*", dbname: "default", pk: "id"}
	return m
}

func (m *Module) Clean() *Module {
	m.columnstr = "*"
	m.filters = ""
	m.orderby = ""
	m.limit = ""
	m.join = ""
	m.pk = "id"
	return m
}

func (m *Module) GetDB() *sql.DB {
	return dbHive[m.dbname]
}

//change db
func (m *Module) User(dbname string) *Module {
	m.dbname = dbname
	return m
}

//select fields
func (m *Module) Select(fields ...string) *Module {
	m.columnstr = ""
	for _, f := range fields {
		m.columnstr = m.columnstr + f + ","
	}
	return m
}

//Filter
func (m *Module) Filter(param ...interface{}) *Module {
	for _, p := range param {
		m.filters += fmt.Sprintf("%v", p)
	}
	return m
}

//orderBy
func (m *Module) OrderBy(param string) *Module {
	m.orderby = fmt.Sprintf("ORDER By %v", param)
	return m
}

//limit
func (m *Module) Limit(size ...int) *Module {
	if len(size) > 1 {
		m.limit = fmt.Sprintf("Limit %d,%d", size[0], size[1])
		return m
	} else {
		m.limit = fmt.Sprintf("Limit %d", size[0])
		return m
	}
}

//leftJoin
func (m *Module) LeftJoin(table, condition string) *Module {
	m.join += fmt.Sprintf(" LEFT JOIN %v ON %v", table, condition)
	return m
}

//rightJoin
func (m *Module) RightJoin(table, condition string) *Module {
	m.join += fmt.Sprintf(" RIGHT JOIN %v ON %v", table, condition)
	return m
}

//join
func (m *Module) Join(table, condition string) *Module {
	m.join += fmt.Sprintf(" INNER JOIN %v ON %v", table, condition)
	return m
}

//fulljoin
func (m *Module) FullJoin(table, condition string) *Module {
	m.join += fmt.Sprintf(" FULL JOIN %v ON %v", table, condition)
	return m
}

func (m *Module) getSqlString() string {
	columnstr := m.columnstr
	if l := len(columnstr); l > 1 {
		columnstr = columnstr[:l-1]
	}
	query := m.buildSql(columnstr)
	query += " " + m.limit
	log.Println("sql = ", query)
	return query
}

func (m *Module) buildSql(columnstr string) string {
	where := m.filters
	where = strings.TrimSpace(where)
	if len(where) > 0 {
		where = "where " + where
	}
	query := fmt.Sprintf("select %v from %v %v %v %v", columnstr, m.tableName, m.join, where, m.orderby)
	return query
}

func (m *Module) QueryPage(page *Page, callBackFunc func(*sql.Rows)) error {
	db := dbHive[m.dbname]
	m.Limit(page.StartRow(), page.PageSize)
	query := m.buildSql("count(*)")
	log.Println(query)
	row := db.QueryRow(query)
	err := row.Scan(&page.ResultCount)
	if err != nil {
		return err
	}
	rows, err := db.Query(m.getSqlString())
	if err != nil {
		return err
	}
	defer rows.Close()
	callBackFunc(rows)
	return nil
}

func (m *Module) Query(callBackFunc func(*sql.Rows)) error {
	db := dbHive[m.dbname]
	rows, err := db.Query(m.getSqlString())
	if err != nil {
		return err
	}
	defer rows.Close()
	callBackFunc(rows)
	return nil
}
func (m *Module) QueryOne(callBackFunc func(*sql.Row)) {
	db := dbHive[m.dbname]
	row := db.QueryRow(m.getSqlString())
	callBackFunc(row)
}

func (m *Module) IsExist() (bool, error) {
	count, err := m.Count()
	if count > 0 {
		return true, nil
	}
	return false, err
}

func (m *Module) Count() (int, error) {
	db := dbHive[m.dbname]
	query := m.buildSql("count(*)")
	log.Println("sql = ", query)
	row := db.QueryRow(query)
	var count int
	err := row.Scan(&count)
	return count, err
}

func (m *Module) OneRecord() (record Record, err error) {
	rs, err := m.Limit(1).AllRecords()
	if err != nil {
		return record, err
	}
	if len(rs) == 0 {
		return NewRecord(), errors.New("not fond record")
	}
	return rs[0], nil
}

func (m *Module) AllRecords() ([]Record, error) {
	db := dbHive[m.dbname]
	rows, err := db.Query(m.getSqlString())
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	records := make([]Record, 0)
	columns, _ := rows.Columns()
	values := make([]sql.RawBytes, len(columns))
	scanargs := make([]interface{}, len(values))
	for i := range values {
		scanargs[i] = &values[i]
	}
	for rows.Next() {
		err := rows.Scan(scanargs...)
		if err != nil {
			fmt.Println(err)
		}
		record := NewRecord()
		for i, v := range values {
			record.result[columns[i]] = v
		}
		records = append(records, record)
	}
	return records, nil
}
func (m *Module) SetPK(pk string) *Module {
	m.pk = pk
	return m
}

func (m *Module) FindRecordById(id int) *Module {
	m.Filter(m.pk, "=", id)
	return m
}

func (m *Module) Insert(record Record) (int, error) {
	columns := ""
	values := ""
	for c, v := range record.param {
		columns = columns + c + ","
		rv := reflect.ValueOf(v)
		switch rv.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Bool:
			values = values + fmt.Sprintf("%v", v) + ","
		default:
			values = values + fmt.Sprintf("'%v'", v) + ","
		}
	}
	if l := len(columns); l > 0 {
		columns = columns[:l-1]
	}
	if l := len(values); l > 0 {
		values = values[:l-1]
	}
	insertSql := fmt.Sprintf("insert into %v(%v) values(%v)", m.tableName, columns, values)
	fmt.Println(insertSql)
	db := dbHive[m.dbname]
	result, err := db.Exec(insertSql)
	if err != nil {
		return 0, err
	}
	id, err := result.LastInsertId()
	return int(id), err
}

func (m *Module) Update(record Record) error {
	values := ""
	for c, v := range record.param {
		values = values + c + "="
		rv := reflect.ValueOf(v)
		switch rv.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Bool:
			values += fmt.Sprintf("%v", v)
		default:
			values += fmt.Sprintf("'%v'", v)
		}
		values += ","
	}
	if l := len(values); l > 0 {
		values = values[:l-1]
	}
	sql := fmt.Sprintf("update %v set %v where %v", m.tableName, values, m.filters)
	log.Println("sql = ", sql)
	db := dbHive[m.dbname]
	_, err := db.Exec(sql)
	return err
}

func (m *Module) DeleteById(id int) error {
	m.Filter(m.pk, "=", id)
	return m.Delete()
}

func (m *Module) FindById(id int) *Module {
	m.Filter(m.pk, "=", id)
	return m
}

func (m *Module) Delete() error {
	where := m.filters
	where = strings.TrimSpace(where)
	if len(where) > 0 {
		where = "where " + where
	}
	delSql := fmt.Sprintf("delete from %v %v", m.tableName, where)
	fmt.Println(delSql)
	_, err := dbHive[m.dbname].Exec(delSql)
	return err
}
<|endoftext|>


<commit_before>package TriUI

import (
	"strconv"
	"strings"
	"trident.li/keyval"
	pf "trident.li/pitchfork/lib"
	pu "trident.li/pitchfork/ui"
	tr "trident.li/trident/src/lib"
)

func h_group_member(cui pu.PfUI) {
	path := cui.GetPath()

	if len(path) != 0 && path[0] != "" {
		pu.H_group_member_profile(cui)
		return
	}

	var err error

	tctx := tr.TriGetCtx(cui)
	total := 0
	offset := 0

	offset_v, err := cui.FormValue("offset")
	if err == nil && offset_v != "" {
		offset, _ = strconv.Atoi(offset_v)
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	grp := tctx.TriSelectedGroup()

	total, err = grp.ListGroupMembersTot(search)
	if err != nil {
		cui.Err("error: " + err.Error())
		return
	}

	members, err := grp.ListGroupMembers(search, cui.TheUser().GetUserName(), offset, 10, false, cui.IAmGroupAdmin(), false)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	/* Output the page */
	type Page struct {
		*pu.PfPage
		Group        pf.PfGroup
		GroupMembers []pf.PfGroupMember
		PagerOffset  int
		PagerTotal   int
		Search       string
		IsAdmin      bool
	}
	isadmin := cui.IAmGroupAdmin()

	p := Page{cui.Page_def(), grp, members, offset, total, search, isadmin}
	cui.Page_show("group/members.tmpl", p)
}

type NominateAdd struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Vouchee      string          `label:"Username" pfset:"nobody" pfget:"none"`
	Comment      string          `label:"Vouch comment" pftype:"text" hint:"Vouch description for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts" pfcheckboxmode:"yes"`
	Button       string          `label:"Nominate" pftype:"submit"`
	Message      string          /* Used by pfform() */
	Error        string          /* Used by pfform() */
}

func (na *NominateAdd) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate_existing(cui pu.PfUI) {
	msg := ""
	errmsg := ""
	tctx := tr.TriGetCtx(cui)
	grp := tctx.TriSelectedGroup()

	vouchee_name, err := cui.FormValue("vouchee")
	if err != nil {
		pu.H_errtxt(cui, "No valid vouchee")
		return
	}

	err = tctx.SelectVouchee(vouchee_name, pu.PERM_USER_NOMINATE)
	if err != nil {
		pu.H_errtxt(cui, "Vouchee unselectable")
		return
	}

	if cui.IsPOST() {
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			msg, err = vouch_nominate(cui)
			if err != nil {
				errmsg = err.Error()
			}
		}
	}

	vouchee := tctx.SelectedVouchee()

	type Page struct {
		*pu.PfPage
		Vouchee     string
		GroupName   string
		NominateAdd *NominateAdd
	}

	na := &NominateAdd{group: grp, Vouchee: vouchee.GetUserName(), Action: "nominate", Message: msg, Error: errmsg}

	p := Page{cui.Page_def(), vouchee.GetUserName(), grp.GetGroupName(), na}
	cui.Page_show("group/nominate_existing.tmpl", p)
}

type NominateNew struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Search       string          `label:"Search" pftype:"hidden"`
	Email        string          `label:"Email address of nominee" pfset:"none"`
	FullName     string          `label:"Full Name" hint:"Full Name of this user" pfreq:"yes"`
	Affiliation  string          `label:"Affiliation" hint:"Who the user is affiliated to" pfreq:"yes"`
	Biography    string          `label:"Biography" pftype:"text" hint:"Biography for this user" pfreq:"yes"`
	Comment      string          `label:"Vouch Comment" pftype:"text" hint:"Vouch for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts" pfcheckboxmode:"yes"`
	Button       string          `label:"Nominate" pftype:"submit"`
	Message      string          /* Used by pfform() */
	Error        string          /* Used by pfform() */
}

func (na *NominateNew) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate(cui pu.PfUI) {
	var msg string
	var err error
	var errmsg string
	var list []pf.PfUser
	var search string

	tctx := tr.TriGetCtx(cui)
	user := tctx.TriSelectedUser()
	grp := tctx.TriSelectedGroup()
	added := false

	/* Something posted? */
	if cui.IsPOST() {
		/* An action to perform? */
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			msg, err = vouch_nominate_new(cui)
			if err != nil {
				errmsg += err.Error()
			}
			added = true
		}

		/* Search field? */
		search, err = cui.FormValue("search")
		if err != nil {
			search = ""
		}

		/* Simple 'is there an @ sign, it must be an email address' check */
		if strings.Index(search, "@") == -1 {
			/* Not an email, do not allow searching */
			search = ""
		}
	}

	/* Need to search the list? */
	notfound := true
	if search != "" {
		/* Get list of users matching the given search query */
		list, err = user.GetList(cui, search, 0, 0, true)
		if err != nil {
			cui.Errf("Listing users failed: %s", err.Error())
			pu.H_error(cui, pu.StatusBadRequest)
			return
		}

		if len(list) != 0 {
			notfound = false
		}
	}

	type Page struct {
		*pu.PfPage
		Search    string
		GroupName string
		Users     []pf.PfUser
		NotFound  bool
		NewForm   *NominateNew
	}

	if added {
		notfound = true
	}

	/* Re-fill in the form (for people who do not enable the attestations) */
	descr, _ := cui.FormValue("fullname")
	affil, _ := cui.FormValue("affiliation")
	bio, _ := cui.FormValue("biography")
	comment, _ := cui.FormValue("comment")

	newform := &NominateNew{group: grp, Action: "nominate", Email: search, Message: msg, Error: errmsg, Search: search, FullName: descr, Affiliation: affil, Biography: bio, Comment: comment}

	p := Page{cui.Page_def(), search, grp.GetGroupName(), list, notfound, newform}
	cui.Page_show("group/nominate.tmpl", p)
}

func h_vouches_csv(cui pu.PfUI) {
	grp := cui.SelectedGroup()

	vouches, err := tr.Vouches_Get(cui, grp.GetGroupName())
	if err != nil {
		pu.H_errmsg(cui, err)
		return
	}

	csv := ""

	for _, v := range vouches {
		csv += v.Vouchor + "," + v.Vouchee + "," + v.Entered.Format(pf.Config.DateFormat) + "\n"
	}

	fname := grp.GetGroupName() + ".csv"

	cui.SetContentType("text/vcard")
	cui.SetFileName(fname)
	cui.SetExpires(60)
	cui.SetRaw([]byte(csv))
	return
}

func h_vouches(cui pu.PfUI) {
	fmt := cui.GetArg("format")

	if fmt == "csv" {
		h_vouches_csv(cui)
		return
	}

	grp := cui.SelectedGroup()
	vouches, err := tr.Vouches_Get(cui, grp.GetGroupName())
	if err != nil {
		pu.H_errmsg(cui, err)
		return
	}

	/* Output the page */
	type Page struct {
		*pu.PfPage
		Vouches []tr.Vouch
	}

	p := Page{cui.Page_def(), vouches}
	cui.Page_show("group/vouches.tmpl", p)
}

func h_group(cui pu.PfUI, menu *pu.PfUIMenu) {
	menu.Replace("member", h_group_member)

	m := []pu.PfUIMentry{
		{"nominate", "Nominate", pf.PERM_GROUP_MEMBER, h_group_nominate, nil},
		{"nominate_existing", "Nominate existing user", pf.PERM_GROUP_MEMBER | pf.PERM_HIDDEN, h_group_nominate_existing, nil},
		{"vouches", "Vouches", pf.PERM_GROUP_MEMBER, h_vouches, nil},
		{"vcp", "Vouching Control Panel", pf.PERM_GROUP_MEMBER, h_group_vcp, nil},
	}

	menu.Add(m...)
}
<commit_msg>Down-case incoming nomination search email The DB is forced lowercase, so all searches should compare on lowercase. Also passing an upper-case to the New nomination form will cause the nomination to fail.<commit_after>package TriUI

import (
	"strconv"
	"strings"

	"trident.li/keyval"
	pf "trident.li/pitchfork/lib"
	pu "trident.li/pitchfork/ui"
	tr "trident.li/trident/src/lib"
)

func h_group_member(cui pu.PfUI) {
	path := cui.GetPath()

	if len(path) != 0 && path[0] != "" {
		pu.H_group_member_profile(cui)
		return
	}

	var err error

	tctx := tr.TriGetCtx(cui)
	total := 0
	offset := 0

	offset_v, err := cui.FormValue("offset")
	if err == nil && offset_v != "" {
		offset, _ = strconv.Atoi(offset_v)
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	grp := tctx.TriSelectedGroup()

	total, err = grp.ListGroupMembersTot(search)
	if err != nil {
		cui.Err("error: " + err.Error())
		return
	}

	members, err := grp.ListGroupMembers(search, cui.TheUser().GetUserName(), offset, 10, false, cui.IAmGroupAdmin(), false)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	/* Output the page */
	type Page struct {
		*pu.PfPage
		Group        pf.PfGroup
		GroupMembers []pf.PfGroupMember
		PagerOffset  int
		PagerTotal   int
		Search       string
		IsAdmin      bool
	}
	isadmin := cui.IAmGroupAdmin()

	p := Page{cui.Page_def(), grp, members, offset, total, search, isadmin}
	cui.Page_show("group/members.tmpl", p)
}

type NominateAdd struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Vouchee      string          `label:"Username" pfset:"nobody" pfget:"none"`
	Comment      string          `label:"Vouch comment" pftype:"text" hint:"Vouch description for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts" pfcheckboxmode:"yes"`
	Button       string          `label:"Nominate" pftype:"submit"`
	Message      string          /* Used by pfform() */
	Error        string          /* Used by pfform() */
}

func (na *NominateAdd) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate_existing(cui pu.PfUI) {
	msg := ""
	errmsg := ""
	tctx := tr.TriGetCtx(cui)
	grp := tctx.TriSelectedGroup()

	vouchee_name, err := cui.FormValue("vouchee")
	if err != nil {
		pu.H_errtxt(cui, "No valid vouchee")
		return
	}

	err = tctx.SelectVouchee(vouchee_name, pu.PERM_USER_NOMINATE)
	if err != nil {
		pu.H_errtxt(cui, "Vouchee unselectable")
		return
	}

	if cui.IsPOST() {
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			msg, err = vouch_nominate(cui)
			if err != nil {
				errmsg = err.Error()
			}
		}
	}

	vouchee := tctx.SelectedVouchee()

	type Page struct {
		*pu.PfPage
		Vouchee     string
		GroupName   string
		NominateAdd *NominateAdd
	}

	na := &NominateAdd{group: grp, Vouchee: vouchee.GetUserName(), Action: "nominate", Message: msg, Error: errmsg}

	p := Page{cui.Page_def(), vouchee.GetUserName(), grp.GetGroupName(), na}
	cui.Page_show("group/nominate_existing.tmpl", p)
}

type NominateNew struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Search       string          `label:"Search" pftype:"hidden"`
	Email        string          `label:"Email address of nominee" pfset:"none"`
	FullName     string          `label:"Full Name" hint:"Full Name of this user" pfreq:"yes"`
	Affiliation  string          `label:"Affiliation" hint:"Who the user is affiliated to" pfreq:"yes"`
	Biography    string          `label:"Biography" pftype:"text" hint:"Biography for this user" pfreq:"yes"`
	Comment      string          `label:"Vouch Comment" pftype:"text" hint:"Vouch for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts" pfcheckboxmode:"yes"`
	Button       string          `label:"Nominate" pftype:"submit"`
	Message      string          /* Used by pfform() */
	Error        string          /* Used by pfform() */
}

func (na *NominateNew) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate(cui pu.PfUI) {
	var msg string
	var err error
	var errmsg string
	var list []pf.PfUser
	var search string

	tctx := tr.TriGetCtx(cui)
	user := tctx.TriSelectedUser()
	grp := tctx.TriSelectedGroup()
	added := false

	/* Something posted? */
	if cui.IsPOST() {
		/* An action to perform? */
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			msg, err = vouch_nominate_new(cui)
			if err != nil {
				errmsg += err.Error()
			}
			added = true
		}

		/* Search field? */
		search, err = cui.FormValue("search")
		if err != nil {
			search = ""
		}

		/* case-fold to lowercase */
		search = strings.ToLower(search)

		/* Simple 'is there an @ sign, it must be an email address' check */
		if strings.Index(search, "@") == -1 {
			/* Not an email, do not allow searching */
			search = ""
		}
	}

	/* Need to search the list? */
	notfound := true
	if search != "" {
		/* Get list of users matching the given search query */
		list, err = user.GetList(cui, search, 0, 0, true)
		if err != nil {
			cui.Errf("Listing users failed: %s", err.Error())
			pu.H_error(cui, pu.StatusBadRequest)
			return
		}

		if len(list) != 0 {
			notfound = false
		}
	}

	type Page struct {
		*pu.PfPage
		Search    string
		GroupName string
		Users     []pf.PfUser
		NotFound  bool
		NewForm   *NominateNew
	}

	if added {
		notfound = true
	}

	/* Re-fill in the form (for people who do not enable the attestations) */
	descr, _ := cui.FormValue("fullname")
	affil, _ := cui.FormValue("affiliation")
	bio, _ := cui.FormValue("biography")
	comment, _ := cui.FormValue("comment")

	newform := &NominateNew{group: grp, Action: "nominate", Email: search, Message: msg, Error: errmsg, Search: search, FullName: descr, Affiliation: affil, Biography: bio, Comment: comment}

	p := Page{cui.Page_def(), search, grp.GetGroupName(), list, notfound, newform}
	cui.Page_show("group/nominate.tmpl", p)
}

func h_vouches_csv(cui pu.PfUI) {
	grp := cui.SelectedGroup()

	vouches, err := tr.Vouches_Get(cui, grp.GetGroupName())
	if err != nil {
		pu.H_errmsg(cui, err)
		return
	}

	csv := ""

	for _, v := range vouches {
		csv += v.Vouchor + "," + v.Vouchee + "," + v.Entered.Format(pf.Config.DateFormat) + "\n"
	}

	fname := grp.GetGroupName() + ".csv"

	cui.SetContentType("text/vcard")
	cui.SetFileName(fname)
	cui.SetExpires(60)
	cui.SetRaw([]byte(csv))
	return
}

func h_vouches(cui pu.PfUI) {
	fmt := cui.GetArg("format")

	if fmt == "csv" {
		h_vouches_csv(cui)
		return
	}

	grp := cui.SelectedGroup()
	vouches, err := tr.Vouches_Get(cui, grp.GetGroupName())
	if err != nil {
		pu.H_errmsg(cui, err)
		return
	}

	/* Output the page */
	type Page struct {
		*pu.PfPage
		Vouches []tr.Vouch
	}

	p := Page{cui.Page_def(), vouches}
	cui.Page_show("group/vouches.tmpl", p)
}

func h_group(cui pu.PfUI, menu *pu.PfUIMenu) {
	menu.Replace("member", h_group_member)

	m := []pu.PfUIMentry{
		{"nominate", "Nominate", pf.PERM_GROUP_MEMBER, h_group_nominate, nil},
		{"nominate_existing", "Nominate existing user", pf.PERM_GROUP_MEMBER | pf.PERM_HIDDEN, h_group_nominate_existing, nil},
		{"vouches", "Vouches", pf.PERM_GROUP_MEMBER, h_vouches, nil},
		{"vcp", "Vouching Control Panel", pf.PERM_GROUP_MEMBER, h_group_vcp, nil},
	}

	menu.Add(m...)
}
<|endoftext|>


<commit_before>package integration_test

import (
	"time"

	"github.com/concourse/baggageclaim"
	"github.com/concourse/baggageclaim/volume"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("TTL's", func() {
	var (
		runner *BaggageClaimRunner
		client baggageclaim.Client
	)

	BeforeEach(func() {
		runner = NewRunner(baggageClaimPath)
		runner.Start()

		client = runner.Client()
	})

	AfterEach(func() {
		runner.Stop()
		runner.Cleanup()
	})

	It("can set a ttl", func() {
		spec := baggageclaim.VolumeSpec{
			TTL: 10 * time.Second,
		}

		emptyVolume, err := client.CreateVolume(logger, spec)
		Expect(err).NotTo(HaveOccurred())

		expectedExpiresAt := time.Now().Add(volume.TTL(10).Duration())

		someVolume, err := client.LookupVolume(logger, emptyVolume.Handle())
		Expect(err).NotTo(HaveOccurred())

		ttl, actualExpiresAt, err := someVolume.Expiration()
		Expect(err).NotTo(HaveOccurred())
		Expect(ttl).To(Equal(10 * time.Second))
		Expect(actualExpiresAt).To(BeTemporally("~", expectedExpiresAt, 1*time.Second))
	})

	It("removes the volume after the ttl duration", func() {
		spec := baggageclaim.VolumeSpec{
			TTL: 1 * time.Second,
		}

		emptyVolume, err := client.CreateVolume(logger, spec)
		Expect(err).NotTo(HaveOccurred())

		emptyVolume.Release(0)

		volumes, err := client.ListVolumes(logger, nil)
		Expect(err).NotTo(HaveOccurred())
		Expect(volumes).To(HaveLen(1))

		volumes[0].Release(0)

		time.Sleep(2 * time.Second)

		Expect(runner.CurrentHandles()).To(BeEmpty())
	})

	Describe("heartbeating", func() {
		It("keeps the container alive, and lets it expire once released", func() {
			spec := baggageclaim.VolumeSpec{TTL: 2 * time.Second}

			volume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			Consistently(runner.CurrentHandles, 3*time.Second).Should(ContainElement(volume.Handle()))

			volume.Release(0)

			// note: don't use Eventually; CurrentHandles causes it to heartbeat

			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(BeEmpty())
		})

		Describe("releasing with a final ttl", func() {
			It("lets it expire after the given TTL", func() {
				spec := baggageclaim.VolumeSpec{TTL: 2 * time.Second}

				volume, err := client.CreateVolume(logger, spec)
				Expect(err).NotTo(HaveOccurred())

				Consistently(runner.CurrentHandles, 3*time.Second).Should(ContainElement(volume.Handle()))

				volume.Release(3 * time.Second)

				ttl, _, err := volume.Expiration()
				Expect(err).NotTo(HaveOccurred())
				Expect(ttl).To(Equal(3 * time.Second))

				time.Sleep(4 * time.Second)
				Expect(runner.CurrentHandles()).To(BeEmpty())
			})
		})

		Context("when you look up a volume by handle", func() {
			It("heartbeats the volume once before returning it", func() {
				spec := baggageclaim.VolumeSpec{
					TTL: 5 * time.Second,
				}

				emptyVolume, err := client.CreateVolume(logger, spec)
				Ω(err).ShouldNot(HaveOccurred())

				time.Sleep(2 * time.Second)

				_, err = client.LookupVolume(logger, emptyVolume.Handle())

				_, expiresAt, err := emptyVolume.Expiration()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(expiresAt).Should(BeTemporally("~", time.Now().Add(5*time.Second), 1*time.Second))
			})
		})
	})

	Describe("resetting the ttl", func() {
		It("pauses the parent if you create a cow volume", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 2 * time.Second,
			}

			parentVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			Consistently(runner.CurrentHandles, 1*time.Second).Should(ContainElement(parentVolume.Handle()))

			childVolume, err := client.CreateVolume(logger, baggageclaim.VolumeSpec{
				Strategy: baggageclaim.COWStrategy{Parent: parentVolume},
				TTL:      4 * time.Second,
			})
			Expect(err).NotTo(HaveOccurred())

			parentVolume.Release(0)

			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(ContainElement(parentVolume.Handle()))

			childVolume.Release(0)

			time.Sleep(5 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(childVolume.Handle()))

			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(parentVolume.Handle()))
		})

		It("pauses the parent as long as *any* child volumes are present", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 2 * time.Second,
			}
			parentVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			Consistently(runner.CurrentHandles, 1*time.Second).Should(ContainElement(parentVolume.Handle()))

			childVolume1, err := client.CreateVolume(logger, baggageclaim.VolumeSpec{
				Strategy: baggageclaim.COWStrategy{Parent: parentVolume},
				TTL:      2 * time.Second,
			})
			Expect(err).NotTo(HaveOccurred())

			childVolume2, err := client.CreateVolume(logger, baggageclaim.VolumeSpec{
				Strategy: baggageclaim.COWStrategy{Parent: parentVolume},
				TTL:      2 * time.Second,
			})
			Expect(err).NotTo(HaveOccurred())

			parentVolume.Release(0)

			By("the parent should stay paused")
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(ContainElement(parentVolume.Handle()))

			By("the first child should be removed")
			childVolume1.Release(0)
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(childVolume1.Handle()))

			By("the parent should still be paused")
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(ContainElement(parentVolume.Handle()))

			By("the second child should be removed")
			childVolume2.Release(0)
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(childVolume2.Handle()))

			By("the parent should be removed")
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(parentVolume.Handle()))
		})

		It("resets to a new value if you update the ttl", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 2 * time.Second,
			}

			emptyVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			ttl, _, err := emptyVolume.Expiration()
			Expect(err).NotTo(HaveOccurred())
			Expect(ttl).To(Equal(2 * time.Second))

			emptyVolume.Release(0)

			err = emptyVolume.SetTTL(3 * time.Second)
			Expect(err).NotTo(HaveOccurred())

			ttl, _, err = emptyVolume.Expiration()
			Expect(err).NotTo(HaveOccurred())
			Expect(ttl).To(Equal(3 * time.Second))
		})

		It("returns ErrVolumeNotFound when setting the TTL after it's expired", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 1 * time.Second,
			}

			emptyVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			emptyVolume.Release(0)
			time.Sleep(2 * time.Second)

			err = emptyVolume.SetTTL(1 * time.Second)
			Expect(err).To(Equal(baggageclaim.ErrVolumeNotFound))
		})
	})
})
<commit_msg>reduce flakiness<commit_after>package integration_test

import (
	"time"

	"github.com/concourse/baggageclaim"
	"github.com/concourse/baggageclaim/volume"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("TTL's", func() {
	var (
		runner *BaggageClaimRunner
		client baggageclaim.Client
	)

	BeforeEach(func() {
		runner = NewRunner(baggageClaimPath)
		runner.Start()

		client = runner.Client()
	})

	AfterEach(func() {
		runner.Stop()
		runner.Cleanup()
	})

	It("can set a ttl", func() {
		spec := baggageclaim.VolumeSpec{
			TTL: 10 * time.Second,
		}

		emptyVolume, err := client.CreateVolume(logger, spec)
		Expect(err).NotTo(HaveOccurred())

		expectedExpiresAt := time.Now().Add(volume.TTL(10).Duration())

		someVolume, err := client.LookupVolume(logger, emptyVolume.Handle())
		Expect(err).NotTo(HaveOccurred())

		ttl, actualExpiresAt, err := someVolume.Expiration()
		Expect(err).NotTo(HaveOccurred())
		Expect(ttl).To(Equal(10 * time.Second))
		Expect(actualExpiresAt).To(BeTemporally("~", expectedExpiresAt, 1*time.Second))
	})

	It("removes the volume after the ttl duration", func() {
		spec := baggageclaim.VolumeSpec{
			TTL: 1 * time.Second,
		}

		emptyVolume, err := client.CreateVolume(logger, spec)
		Expect(err).NotTo(HaveOccurred())

		emptyVolume.Release(0)

		volumes, err := client.ListVolumes(logger, nil)
		Expect(err).NotTo(HaveOccurred())
		Expect(volumes).To(HaveLen(1))

		volumes[0].Release(0)

		time.Sleep(2 * time.Second)

		Expect(runner.CurrentHandles()).To(BeEmpty())
	})

	Describe("heartbeating", func() {
		It("keeps the container alive, and lets it expire once released", func() {
			spec := baggageclaim.VolumeSpec{TTL: 2 * time.Second}

			volume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			Consistently(runner.CurrentHandles, 3*time.Second).Should(ContainElement(volume.Handle()))

			volume.Release(0)

			// note: don't use Eventually; CurrentHandles causes it to heartbeat

			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(BeEmpty())
		})

		Describe("releasing with a final ttl", func() {
			It("lets it expire after the given TTL", func() {
				spec := baggageclaim.VolumeSpec{TTL: 2 * time.Second}

				volume, err := client.CreateVolume(logger, spec)
				Expect(err).NotTo(HaveOccurred())

				Consistently(runner.CurrentHandles, 3*time.Second).Should(ContainElement(volume.Handle()))

				volume.Release(3 * time.Second)

				ttl, _, err := volume.Expiration()
				Expect(err).NotTo(HaveOccurred())
				Expect(ttl).To(Equal(3 * time.Second))

				time.Sleep(4 * time.Second)
				Expect(runner.CurrentHandles()).To(BeEmpty())
			})
		})

		Context("when you look up a volume by handle", func() {
			It("heartbeats the volume once before returning it", func() {
				spec := baggageclaim.VolumeSpec{
					TTL: 5 * time.Second,
				}

				emptyVolume, err := client.CreateVolume(logger, spec)
				Ω(err).ShouldNot(HaveOccurred())

				time.Sleep(2 * time.Second)

				lookedUpAt := time.Now()

				_, err = client.LookupVolume(logger, emptyVolume.Handle())
				Ω(err).ShouldNot(HaveOccurred())

				_, expiresAt, err := emptyVolume.Expiration()
				Ω(err).ShouldNot(HaveOccurred())
				Ω(expiresAt).Should(BeTemporally("~", lookedUpAt.Add(5*time.Second), 1*time.Second))
			})
		})
	})

	Describe("resetting the ttl", func() {
		It("pauses the parent if you create a cow volume", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 2 * time.Second,
			}

			parentVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			Consistently(runner.CurrentHandles, 1*time.Second).Should(ContainElement(parentVolume.Handle()))

			childVolume, err := client.CreateVolume(logger, baggageclaim.VolumeSpec{
				Strategy: baggageclaim.COWStrategy{Parent: parentVolume},
				TTL:      4 * time.Second,
			})
			Expect(err).NotTo(HaveOccurred())

			parentVolume.Release(0)

			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(ContainElement(parentVolume.Handle()))

			childVolume.Release(0)

			time.Sleep(5 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(childVolume.Handle()))

			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(parentVolume.Handle()))
		})

		It("pauses the parent as long as *any* child volumes are present", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 2 * time.Second,
			}
			parentVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			Consistently(runner.CurrentHandles, 1*time.Second).Should(ContainElement(parentVolume.Handle()))

			childVolume1, err := client.CreateVolume(logger, baggageclaim.VolumeSpec{
				Strategy: baggageclaim.COWStrategy{Parent: parentVolume},
				TTL:      2 * time.Second,
			})
			Expect(err).NotTo(HaveOccurred())

			childVolume2, err := client.CreateVolume(logger, baggageclaim.VolumeSpec{
				Strategy: baggageclaim.COWStrategy{Parent: parentVolume},
				TTL:      2 * time.Second,
			})
			Expect(err).NotTo(HaveOccurred())

			parentVolume.Release(0)

			By("the parent should stay paused")
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(ContainElement(parentVolume.Handle()))

			By("the first child should be removed")
			childVolume1.Release(0)
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(childVolume1.Handle()))

			By("the parent should still be paused")
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).To(ContainElement(parentVolume.Handle()))

			By("the second child should be removed")
			childVolume2.Release(0)
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(childVolume2.Handle()))

			By("the parent should be removed")
			time.Sleep(3 * time.Second)
			Expect(runner.CurrentHandles()).ToNot(ContainElement(parentVolume.Handle()))
		})

		It("resets to a new value if you update the ttl", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 2 * time.Second,
			}

			emptyVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			ttl, _, err := emptyVolume.Expiration()
			Expect(err).NotTo(HaveOccurred())
			Expect(ttl).To(Equal(2 * time.Second))

			emptyVolume.Release(0)

			err = emptyVolume.SetTTL(3 * time.Second)
			Expect(err).NotTo(HaveOccurred())

			ttl, _, err = emptyVolume.Expiration()
			Expect(err).NotTo(HaveOccurred())
			Expect(ttl).To(Equal(3 * time.Second))
		})

		It("returns ErrVolumeNotFound when setting the TTL after it's expired", func() {
			spec := baggageclaim.VolumeSpec{
				TTL: 1 * time.Second,
			}

			emptyVolume, err := client.CreateVolume(logger, spec)
			Expect(err).NotTo(HaveOccurred())

			emptyVolume.Release(0)
			time.Sleep(2 * time.Second)

			err = emptyVolume.SetTTL(1 * time.Second)
			Expect(err).To(Equal(baggageclaim.ErrVolumeNotFound))
		})
	})
})
<|endoftext|>


<commit_before>package lifecycle_test

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/cloudfoundry-incubator/garden"
	"github.com/cloudfoundry-incubator/garden-linux/integration/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
)

var _ = Describe("Limits", func() {
	const BTRFS_WAIT_TIME = 90

	var container garden.Container
	var startGardenArgs []string

	var privilegedContainer bool
	var rootfs string

	JustBeforeEach(func() {
		var err error
		client = startGarden(startGardenArgs...)
		container, err = client.Create(garden.ContainerSpec{Privileged: privilegedContainer, RootFSPath: rootfs})
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		if container != nil {
			Expect(client.Destroy(container.Handle())).To(Succeed())
		}
	})

	BeforeEach(func() {
		privilegedContainer = false
		rootfs = ""
		startGardenArgs = []string{}
	})

	Describe("LimitMemory", func() {
		Context("with a memory limit", func() {
			JustBeforeEach(func() {
				err := container.LimitMemory(garden.MemoryLimits{
					LimitInBytes: 64 * 1024 * 1024,
				})
				Expect(err).ToNot(HaveOccurred())
			})

			Context("when the process writes too much to /dev/shm", func() {
				It("is killed", func() {
					process, err := container.Run(garden.ProcessSpec{
						User: "vcap",
						Path: "dd",
						Args: []string{"if=/dev/urandom", "of=/dev/shm/too-big", "bs=1M", "count=65"},
					}, garden.ProcessIO{})
					Expect(err).ToNot(HaveOccurred())

					Expect(process.Wait()).ToNot(Equal(0))
				})
			})
		})
	})

	Describe("LimitDisk", func() {
		Context("with quotas disabled", func() {
			BeforeEach(func() {
				startGardenArgs = []string{"-disableQuotas=true"}
				rootfs = runner.RootFSPath
				privilegedContainer = true
			})

			Context("and there is a disk limit", func() {
				quotaLimit := garden.DiskLimits{
					ByteSoft: 5 * 1024 * 1024,
					ByteHard: 5 * 1024 * 1024,
				}

				JustBeforeEach(func() {
					Expect(container.LimitDisk(quotaLimit)).To(Succeed())
				})

				It("reports the disk limit size of the container as zero", func() {
					limit, err := container.CurrentDiskLimits()
					Expect(err).ToNot(HaveOccurred())
					Expect(limit).To(Equal(garden.DiskLimits{}))
				})

				Context("and it runs a process that exceeds the limit", func() {
					It("does not kill the process", func() {
						dd, err := container.Run(garden.ProcessSpec{
							User: "vcap",
							Path: "dd",
							Args: []string{"if=/dev/zero", "of=/tmp/some-file", "bs=1M", "count=6"},
						}, garden.ProcessIO{})
						Expect(err).ToNot(HaveOccurred())
						Expect(dd.Wait()).To(Equal(0))
					})
				})
			})
		})

		Context("when quotas are enabled and there is a disk limit", func() {
			var quotaLimit garden.DiskLimits

			if os.Getenv("BTRFS_SUPPORTED") == "" {
				log.Println("btrfs not supported: skipping disk limit tests")
				return
			}

			BeforeEach(func() {
				privilegedContainer = false
				rootfs = runner.RootFSPath
				quotaLimit = garden.DiskLimits{
					ByteSoft: 180 * 1024 * 1024,
					ByteHard: 180 * 1024 * 1024,
				}
			})

			JustBeforeEach(func() {
				err := container.LimitDisk(quotaLimit)
				Expect(err).ToNot(HaveOccurred())
			})

			Context("on a directory rootfs container", func() {
				BeforeEach(func() {
					privilegedContainer = false
					rootfs = runner.RootFSPath
				})

				It("reports correct disk usage", func() {
					var diskUsage uint64
					stdout := gbytes.NewBuffer()

					process, err := container.Run(garden.ProcessSpec{
						User: "vcap",
						Path: "sh",
						Args: []string{"-c", "du -d 0 / | awk ' {print $1 }'"},
					}, garden.ProcessIO{Stdout: stdout})
					Expect(err).ToNot(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					_, err = fmt.Sscanf(strings.TrimSpace(string(stdout.Contents())), "%d", &diskUsage)
					Expect(err).ToNot(HaveOccurred())

					process, err = container.Run(garden.ProcessSpec{
						User: "vcap",
						Path: "dd",
						Args: []string{"if=/dev/urandom", "of=/home/vcap/some-file", "bs=1M", "count=10"},
					}, garden.ProcessIO{})
					Expect(err).ToNot(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					metrics := func() uint64 {
						metricsAfter, err := container.Metrics()
						Expect(err).ToNot(HaveOccurred())

						return metricsAfter.DiskStat.BytesUsed
					}

					expectedBytes := (diskUsage * 1024) + uint64(10*1024*1024)
					Eventually(metrics, BTRFS_WAIT_TIME, 30).Should(BeNumerically("~", expectedBytes, 1269760))

					process, err = container.Run(garden.ProcessSpec{
						User: "vcap",
						Path: "dd",
						Args: []string{"if=/dev/urandom", "of=/home/vcap/another-file", "bs=1M", "count=10"},
					}, garden.ProcessIO{})
					Expect(err).ToNot(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					expectedBytes = (diskUsage * 1024) + uint64(20*1024*1024)
					Eventually(metrics, BTRFS_WAIT_TIME, 30).Should(BeNumerically("~", expectedBytes, 1269760))
				})

				Context("on a rootfs with pre-existing users", func() {
					if os.Getenv("GARDEN_PREEXISTING_USERS_TEST_ROOTFS") == "" {
						log.Println("pre-existing users rootfs not found: skipping some limit disk tests")
						return
					}

					BeforeEach(func() {
						rootfs = os.Getenv("GARDEN_PREEXISTING_USERS_TEST_ROOTFS")
						quotaLimit = garden.DiskLimits{
							ByteSoft: 10 * 1024 * 1024,
							ByteHard: 10 * 1024 * 1024,
						}
					})

					Context("and run a process that exceeds the quota as bob", func() {
						It("kills the process", func() {
							dd, err := container.Run(garden.ProcessSpec{
								User: "bob",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/home/bob/test", "bs=1M", "count=11"},
							}, garden.ProcessIO{})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).ToNot(Equal(0))
						})
					})

					Context("and run a process that exceeds the quota as alice", func() {
						It("kills the process", func() {
							dd, err := container.Run(garden.ProcessSpec{
								User: "alice",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/home/alice/test", "bs=1M", "count=11"},
							}, garden.ProcessIO{})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).ToNot(Equal(0))
						})
					})
				})
			})

			Context("on a Docker container", func() {
				BeforeEach(func() {
					privilegedContainer = false
					rootfs = "docker:///busybox"
					quotaLimit = garden.DiskLimits{
						ByteSoft: 10 * 1024 * 1024,
						ByteHard: 10 * 1024 * 1024,
					}
				})

				It("reports the correct disk limit size of the container", func() {
					limit, err := container.CurrentDiskLimits()
					Expect(err).ToNot(HaveOccurred())
					Expect(limit).To(Equal(quotaLimit))
				})

				Context("and run a process that exceeds the quota", func() {
					It("kills the process", func() {
						dd, err := container.Run(garden.ProcessSpec{
							User: "vcap",
							Path: "dd",
							Args: []string{"if=/dev/zero", "of=/root/test", "bs=1M", "count=11"},
						}, garden.ProcessIO{})
						Expect(err).ToNot(HaveOccurred())
						Expect(dd.Wait()).ToNot(Equal(0))
					})
				})

				Context("on a rootfs with pre-existing users", func() {
					BeforeEach(func() {
						rootfs = "docker:///cloudfoundry/preexisting_users"
					})

					Context("and run a process that exceeds the quota as bob", func() {
						BeforeEach(func() {
							quotaLimit = garden.DiskLimits{
								ByteSoft: 10 * 1024 * 1024,
								ByteHard: 10 * 1024 * 1024,
							}
						})

						It("kills the process", func() {
							dd, err := container.Run(garden.ProcessSpec{
								User: "bob",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/home/bob/test", "bs=1M", "count=11"},
							}, garden.ProcessIO{})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).ToNot(Equal(0))
						})
					})

					Context("and run a process that exceeds the quota as alice", func() {
						BeforeEach(func() {
							quotaLimit = garden.DiskLimits{
								ByteSoft: 10 * 1024 * 1024,
								ByteHard: 10 * 1024 * 1024,
							}
						})

						It("kills the process", func() {
							dd, err := container.Run(garden.ProcessSpec{
								User: "alice",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/home/alice/test", "bs=1M", "count=11"},
							}, garden.ProcessIO{})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).ToNot(Equal(0))
						})
					})

					Context("user alice is getting near the set limit", func() {
						JustBeforeEach(func() {
							metrics := func() uint64 {
								metricsAfter, err := container.Metrics()
								Expect(err).ToNot(HaveOccurred())

								return metricsAfter.DiskStat.BytesUsed
							}

							Eventually(metrics, BTRFS_WAIT_TIME, 30).Should(BeNumerically("~", uint64(10*1024*1024), 1024*1024))

							bytesUsed := metrics()

							quotaLimit = garden.DiskLimits{
								ByteSoft: 10*1024*1024 + bytesUsed,
								ByteHard: 10*1024*1024 + bytesUsed,
							}

							err := container.LimitDisk(quotaLimit)
							Expect(err).ToNot(HaveOccurred())

							dd, err := container.Run(garden.ProcessSpec{
								User: "alice",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/home/alice/test", "bs=1M", "count=8"},
							}, garden.ProcessIO{
								Stderr: GinkgoWriter,
								Stdout: GinkgoWriter,
							})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).To(Equal(0))
						})

						It("kills the process if user bob tries to exceed the shared limit", func() {
							dd, err := container.Run(garden.ProcessSpec{
								User: "bob",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/home/bob/test", "bs=1M", "count=3"},
							}, garden.ProcessIO{
								Stderr: GinkgoWriter,
								Stdout: GinkgoWriter,
							})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).ToNot(Equal(0))
						})
					})
				})

				Context("that is privileged", func() {
					BeforeEach(func() {
						privilegedContainer = true
					})

					Context("and run a process that exceeds the quota as root", func() {
						It("kills the process", func() {
							dd, err := container.Run(garden.ProcessSpec{
								User: "root",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/root/test", "bs=1M", "count=11"},
							}, garden.ProcessIO{})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).ToNot(Equal(0))
						})
					})

					Context("and run a process that exceeds the quota as a new user", func() {
						It("kills the process", func() {
							addUser, err := container.Run(garden.ProcessSpec{
								User: "root",
								Path: "adduser",
								Args: []string{"-D", "-g", "", "bob"},
							}, garden.ProcessIO{})
							Expect(err).ToNot(HaveOccurred())
							Expect(addUser.Wait()).To(Equal(0))

							dd, err := container.Run(garden.ProcessSpec{
								User: "bob",
								Path: "dd",
								Args: []string{"if=/dev/zero", "of=/home/bob/test", "bs=1M", "count=11"},
							}, garden.ProcessIO{})
							Expect(err).ToNot(HaveOccurred())
							Expect(dd.Wait()).ToNot(Equal(0))
						})
					})
				})
			})

			Context("when multiple containers are created for the same user", func() {
				var container2 garden.Container
				var err error

				BeforeEach(func() {
					privilegedContainer = false
					rootfs = runner.RootFSPath
					quotaLimit = garden.DiskLimits{
						ByteSoft: 50 * 1024 * 1024,
						ByteHard: 50 * 1024 * 1024,
					}
				})

				JustBeforeEach(func() {
					container2, err = client.Create(garden.ContainerSpec{
						Privileged: privilegedContainer,
						RootFSPath: rootfs,
					})
					Expect(err).ToNot(HaveOccurred())
				})

				AfterEach(func() {
					if container2 != nil {
						Expect(client.Destroy(container2.Handle())).To(Succeed())
					}
				})

				It("gives each container its own quota", func() {
					process, err := container.Run(garden.ProcessSpec{
						User: "vcap",
						Path: "dd",
						Args: []string{"if=/dev/urandom", "of=/home/vcap/some-file", "bs=1M", "count=40"},
					}, garden.ProcessIO{})
					Expect(err).ToNot(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					process, err = container2.Run(garden.ProcessSpec{
						User: "vcap",
						Path: "dd",
						Args: []string{"if=/dev/urandom", "of=/home/vcap/some-file", "bs=1M", "count=40"},
					}, garden.ProcessIO{})
					Expect(err).ToNot(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))
				})
			})
		})
	})
})
<commit_msg>move some limits_test specs to GITs<commit_after>package lifecycle_test

import (
	"github.com/cloudfoundry-incubator/garden"
	"github.com/cloudfoundry-incubator/garden-linux/integration/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Limits", func() {
	const BTRFS_WAIT_TIME = 90

	var container garden.Container
	var startGardenArgs []string

	var privilegedContainer bool
	var rootfs string

	JustBeforeEach(func() {
		var err error
		client = startGarden(startGardenArgs...)
		container, err = client.Create(garden.ContainerSpec{Privileged: privilegedContainer, RootFSPath: rootfs})
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		if container != nil {
			Expect(client.Destroy(container.Handle())).To(Succeed())
		}
	})

	BeforeEach(func() {
		privilegedContainer = false
		rootfs = ""
		startGardenArgs = []string{}
	})

	Describe("LimitDisk", func() {
		Context("with quotas disabled", func() {
			BeforeEach(func() {
				startGardenArgs = []string{"-disableQuotas=true"}
				rootfs = runner.RootFSPath
				privilegedContainer = true
			})

			Context("and there is a disk limit", func() {
				quotaLimit := garden.DiskLimits{
					ByteSoft: 5 * 1024 * 1024,
					ByteHard: 5 * 1024 * 1024,
				}

				JustBeforeEach(func() {
					Expect(container.LimitDisk(quotaLimit)).To(Succeed())
				})

				It("reports the disk limit size of the container as zero", func() {
					limit, err := container.CurrentDiskLimits()
					Expect(err).ToNot(HaveOccurred())
					Expect(limit).To(Equal(garden.DiskLimits{}))
				})

				Context("and it runs a process that exceeds the limit", func() {
					It("does not kill the process", func() {
						dd, err := container.Run(garden.ProcessSpec{
							User: "vcap",
							Path: "dd",
							Args: []string{"if=/dev/zero", "of=/tmp/some-file", "bs=1M", "count=6"},
						}, garden.ProcessIO{})
						Expect(err).ToNot(HaveOccurred())
						Expect(dd.Wait()).To(Equal(0))
					})
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 save

import (
	"fmt"
	"os"
	"path"
	"regexp"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/graph"
)

// Create a graph.SuccessorFinder that models the directory hierarchy rooted at
// the given base path, excluding all relative paths that matches any of the
// supplied exclusions, along with all of their descendants.
//
// The nodes involved are of type *fsNode. The successor finder fills in the
// RelPath, Info and Parent fields of the successors, and the Children field of
// the node on which it is called.
func newSuccessorFinder(
	basePath string,
	exclusions []*regexp.Regexp) (sf graph.SuccessorFinder) {
	sf = &fsSuccessorFinder{
		basePath:   basePath,
		exclusions: exclusions,
	}

	return
}

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

type fsSuccessorFinder struct {
	basePath   string
	exclusions []*regexp.Regexp
}

func (sf *fsSuccessorFinder) FindDirectSuccessors(
	ctx context.Context,
	node graph.Node) (successors []graph.Node, err error) {
	// Ensure the input is of the correct type.
	n, ok := node.(*fsNode)
	if !ok {
		err = fmt.Errorf("Node has unexpected type: %T", node)
		return
	}

	// Skip non-directories; they have no successors.
	if n.Info.Type != fs.TypeDirectory {
		return
	}

	// Read and lstat all of the names in the directory.
	listing, err := sf.readDir(n.RelPath)
	if err != nil {
		err = fmt.Errorf("readDir: %v", err)
		return
	}

	// Filter out excluded entries, converting the rest to *fs.DirectoryEntry and
	// returning them as successors.
	for _, fi := range listing {
		// Skip?
		childRelPath := path.Join(n.RelPath, fi.Name())
		if sf.shouldSkip(childRelPath) {
			continue
		}

		// Read a symlink target if necesssary.
		var symlinkTarget string
		if fi.Mode()&os.ModeSymlink != 0 {
			symlinkTarget, err = os.Readlink(path.Join(sf.basePath, childRelPath))
			if err != nil {
				err = fmt.Errorf("Readlink: %v", err)
				return
			}
		}

		// Convert.
		var entry *fs.DirectoryEntry
		entry, err = fs.ConvertFileInfo(fi, symlinkTarget)
		if err != nil {
			err = fmt.Errorf("ConvertFileInfo: %v", err)
			return
		}

		// Return a successor.
		child := &fsNode{
			RelPath: childRelPath,
			Info:    *entry,
			Parent:  n,
		}

		successors = append(successors, child)
		n.Children = append(n.Children, child)
	}

	return
}

// Read and lstat everything in the directory with the given relative path.
func (sf *fsSuccessorFinder) readDir(
	relPath string) (entries []os.FileInfo, err error) {
	// Open the directory for reading.
	f, err := os.Open(path.Join(sf.basePath, relPath))
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	defer func() {
		closeErr := f.Close()
		if err == nil && closeErr != nil {
			err = fmt.Errorf("Close: %v", closeErr)
		}
	}()

	// Read.
	entries, err = f.Readdir(0)
	if err != nil {
		err = fmt.Errorf("Readdir: %v", err)
		return
	}

	return
}

func (sf *fsSuccessorFinder) shouldSkip(relPath string) bool {
	for _, re := range sf.exclusions {
		if re.MatchString(relPath) {
			return true
		}
	}

	return false
}
<commit_msg>Clarify the behavior of newSuccessorFinder.<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 save

import (
	"fmt"
	"os"
	"path"
	"regexp"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/graph"
)

// Create a graph.SuccessorFinder that models the directory hierarchy rooted at
// the given base path, excluding all relative paths that matches any of the
// supplied exclusions, along with all of their descendants.
//
// The nodes involved are of type *fsNode. The successor finder fills in the
// RelPath, Info and Parent fields of the successors, and the Children field of
// the node on which it is called. The Scores field of Info is left as nil,
// however.
func newSuccessorFinder(
	basePath string,
	exclusions []*regexp.Regexp) (sf graph.SuccessorFinder) {
	sf = &fsSuccessorFinder{
		basePath:   basePath,
		exclusions: exclusions,
	}

	return
}

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

type fsSuccessorFinder struct {
	basePath   string
	exclusions []*regexp.Regexp
}

func (sf *fsSuccessorFinder) FindDirectSuccessors(
	ctx context.Context,
	node graph.Node) (successors []graph.Node, err error) {
	// Ensure the input is of the correct type.
	n, ok := node.(*fsNode)
	if !ok {
		err = fmt.Errorf("Node has unexpected type: %T", node)
		return
	}

	// Skip non-directories; they have no successors.
	if n.Info.Type != fs.TypeDirectory {
		return
	}

	// Read and lstat all of the names in the directory.
	listing, err := sf.readDir(n.RelPath)
	if err != nil {
		err = fmt.Errorf("readDir: %v", err)
		return
	}

	// Filter out excluded entries, converting the rest to *fs.DirectoryEntry and
	// returning them as successors.
	for _, fi := range listing {
		// Skip?
		childRelPath := path.Join(n.RelPath, fi.Name())
		if sf.shouldSkip(childRelPath) {
			continue
		}

		// Read a symlink target if necesssary.
		var symlinkTarget string
		if fi.Mode()&os.ModeSymlink != 0 {
			symlinkTarget, err = os.Readlink(path.Join(sf.basePath, childRelPath))
			if err != nil {
				err = fmt.Errorf("Readlink: %v", err)
				return
			}
		}

		// Convert.
		var entry *fs.DirectoryEntry
		entry, err = fs.ConvertFileInfo(fi, symlinkTarget)
		if err != nil {
			err = fmt.Errorf("ConvertFileInfo: %v", err)
			return
		}

		// Return a successor.
		child := &fsNode{
			RelPath: childRelPath,
			Info:    *entry,
			Parent:  n,
		}

		successors = append(successors, child)
		n.Children = append(n.Children, child)
	}

	return
}

// Read and lstat everything in the directory with the given relative path.
func (sf *fsSuccessorFinder) readDir(
	relPath string) (entries []os.FileInfo, err error) {
	// Open the directory for reading.
	f, err := os.Open(path.Join(sf.basePath, relPath))
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	defer func() {
		closeErr := f.Close()
		if err == nil && closeErr != nil {
			err = fmt.Errorf("Close: %v", closeErr)
		}
	}()

	// Read.
	entries, err = f.Readdir(0)
	if err != nil {
		err = fmt.Errorf("Readdir: %v", err)
		return
	}

	return
}

func (sf *fsSuccessorFinder) shouldSkip(relPath string) bool {
	for _, re := range sf.exclusions {
		if re.MatchString(relPath) {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>package util

import (
	"errors"

	"github.com/nytlabs/gojee"
)

func ParseBool(ruleI interface{}, key string) (bool, error) {
	rule := ruleI.(map[string]interface{})
	var val bool
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(bool)
	if !ok {
		return val, errors.New("Key's value was not a bool")
	}
	return val, nil
}

func ParseString(ruleI interface{}, key string) (string, error) {
	rule := ruleI.(map[string]interface{})
	var val string
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(string)
	if !ok {
		return val, errors.New("Key was not a string")
	}
	return val, nil
}
func ParseMap(ruleI interface{}, key string) (map[string]interface{}, error) {
	rule := ruleI.(map[string]interface{})
	val := make(map[string]interface{})
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(map[string]interface{})
	if !ok {
		return val, errors.New("Key was not a string")
	}
	return val, nil
}

func ParseRequiredString(ruleI interface{}, key string) (string, error) {
	val, err := ParseString(ruleI, key)
	if err != nil {
		return val, err
	}
	if len(val) == 0 {
		return val, errors.New(key + " was an empty string")
	}
	return val, nil
}

func ParseFloat(ruleI interface{}, key string) (float64, error) {
	rule := ruleI.(map[string]interface{})
	var val float64
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(float64)
	if !ok {
		return val, errors.New("Key was not a float64")
	}
	return val, nil
}

func ParseInt(ruleI interface{}, key string) (int, error) {
	rule := ruleI.(map[string]interface{})
	var val int
	var ok bool
	var floatval float64
	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	floatval, ok = foundRule.(float64)
	if !ok {
		return val, errors.New("Key was not a number")
	}
	val = int(floatval)
	return val, nil
}

func KeyExists(ruleI interface{}, key string) bool {
	rule := ruleI.(map[string]interface{})
	_, ok := rule[key]
	return ok
}

func ParseArrayString(ruleI interface{}, key string) ([]string, error) {
	var val []string

	rule := ruleI.(map[string]interface{})
	var ok bool
	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Path was not in rule")
	}

	switch foundRule.(type) {
	case []interface{}:
		valI, ok := foundRule.([]interface{})
		if !ok {
			return val, errors.New("Supplied value was not an array of interfaces")
		}
		val = make([]string, len(valI))
		for i, vi := range valI {
			v, ok := vi.(string)
			if !ok {
				return val, errors.New("Failed asserting to []string")
			}
			val[i] = v
		}
	case []string:
		val, ok = foundRule.([]string)
		if !ok {
			return val, errors.New("Supplied value was not an array of strings")
		}
	}
	return val, nil
}

func ParseArrayFloat(ruleI interface{}, key string) ([]float64, error) {
	rule := ruleI.(map[string]interface{})
	var ok bool
	var val []float64
	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Path was not in rule")
	}
	valI, ok := foundRule.([]interface{})
	if !ok {
		return val, errors.New("Supplied value was not an array")
	}
	val = make([]float64, len(valI))
	for i, vi := range valI {
		v, ok := vi.(float64)
		if !ok {
			return val, errors.New("Supplied value was not an array of numbers")
		}
		val[i] = v
	}
	return val, nil
}

func BuildTokenTree(path string) (tree *jee.TokenTree, err error) {
	token, err := jee.Lexer(path)
	if err != nil {
		return nil, err
	}
	return jee.Parser(token)
}
<commit_msg>Fixed bug when converting int values.<commit_after>package util

import (
	"errors"

	"github.com/nytlabs/gojee"
)

func ParseBool(ruleI interface{}, key string) (bool, error) {
	rule := ruleI.(map[string]interface{})
	var val bool
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(bool)
	if !ok {
		return val, errors.New("Key's value was not a bool")
	}
	return val, nil
}

func ParseString(ruleI interface{}, key string) (string, error) {
	rule := ruleI.(map[string]interface{})
	var val string
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(string)
	if !ok {
		return val, errors.New("Key was not a string")
	}
	return val, nil
}
func ParseMap(ruleI interface{}, key string) (map[string]interface{}, error) {
	rule := ruleI.(map[string]interface{})
	val := make(map[string]interface{})
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(map[string]interface{})
	if !ok {
		return val, errors.New("Key was not a string")
	}
	return val, nil
}

func ParseRequiredString(ruleI interface{}, key string) (string, error) {
	val, err := ParseString(ruleI, key)
	if err != nil {
		return val, err
	}
	if len(val) == 0 {
		return val, errors.New(key + " was an empty string")
	}
	return val, nil
}

func ParseFloat(ruleI interface{}, key string) (float64, error) {
	rule := ruleI.(map[string]interface{})
	var val float64
	var ok bool

	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	val, ok = foundRule.(float64)
	if !ok {
		return val, errors.New("Key was not a float64")
	}
	return val, nil
}

func ParseInt(ruleI interface{}, key string) (int, error) {
	rule := ruleI.(map[string]interface{})
	var val int
	var ok bool
	var floatval float64
	var intval int
	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Key was not in rule")
	}
	floatval, ok = foundRule.(float64)
	if !ok {
		intval, ok = foundRule.(int)
		if !ok {
			return val, errors.New("Key was not a number")
		}
		return intval, nil
	}
	val = int(floatval)
	return val, nil
}

func KeyExists(ruleI interface{}, key string) bool {
	rule := ruleI.(map[string]interface{})
	_, ok := rule[key]
	return ok
}

func ParseArrayString(ruleI interface{}, key string) ([]string, error) {
	var val []string

	rule := ruleI.(map[string]interface{})
	var ok bool
	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Path was not in rule")
	}

	switch foundRule.(type) {
	case []interface{}:
		valI, ok := foundRule.([]interface{})
		if !ok {
			return val, errors.New("Supplied value was not an array of interfaces")
		}
		val = make([]string, len(valI))
		for i, vi := range valI {
			v, ok := vi.(string)
			if !ok {
				return val, errors.New("Failed asserting to []string")
			}
			val[i] = v
		}
	case []string:
		val, ok = foundRule.([]string)
		if !ok {
			return val, errors.New("Supplied value was not an array of strings")
		}
	}
	return val, nil
}

func ParseArrayFloat(ruleI interface{}, key string) ([]float64, error) {
	rule := ruleI.(map[string]interface{})
	var ok bool
	var val []float64
	foundRule, ok := rule[key]
	if !ok {
		return val, errors.New("Path was not in rule")
	}
	valI, ok := foundRule.([]interface{})
	if !ok {
		return val, errors.New("Supplied value was not an array")
	}
	val = make([]float64, len(valI))
	for i, vi := range valI {
		v, ok := vi.(float64)
		if !ok {
			return val, errors.New("Supplied value was not an array of numbers")
		}
		val[i] = v
	}
	return val, nil
}

func BuildTokenTree(path string) (tree *jee.TokenTree, err error) {
	token, err := jee.Lexer(path)
	if err != nil {
		return nil, err
	}
	return jee.Parser(token)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/koding/multiconfig"
)

type DefaultConf struct {
	Board       string `default:"car"`
	BaseUrl     string `default:"https://www.ptt.cc/bbs/"`
	CarBoardUrl string `default:"https://www.ptt.cc/bbs/car/index.html"`
	Page        int    `default:1`
	Mode        string `default:"view"`
}

var conf = loadConfig()

func loadConfig() *DefaultConf {
	m := multiconfig.New()
	conf := new(DefaultConf)
	m.MustLoad(conf)

	return conf
}

func main() {

	switch conf.Mode {
		case "view":
			fetchMultiPages(conf.Board, conf.Page)
		case "crawl":
			fmt.Println("Will support crawl later on")
	}
}

func fetchSingle(url string) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc, _ := goquery.NewDocumentFromReader(io.Reader(resp.Body))

	doc.Find("div.title").Each(func(i int, s *goquery.Selection) {
		link, _ := s.Find("a").Attr("href")
		title := strings.TrimSpace(s.Text())
		fmt.Println(title + "\t" + "https://www.ptt.cc" + link)
	})
}

func fetchPages(url string, ch chan int) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc, _ := goquery.NewDocumentFromReader(io.Reader(resp.Body))
	href, _ := doc.Find("div.action-bar").Find("a.btn").Eq(3).Attr("href")
	pages, _ := strconv.Atoi(strings.Trim(strings.Split(strings.Split(href, "/")[3], ".")[0], "index"))
	ch <- pages + 1
}

func fetchMultiPages(board string, pre int) {
	fmt.Println("Do fetch multiple pages")
	ch := make(chan int)
	url := conf.BaseUrl + board + "/index.html"
	go fetchPages(url, ch)
	p := <-ch

	var pagesURL = make([]string, pre+1)
	for i := pre; i >= 0; i-- {
		pagesURL[i] = conf.BaseUrl + board + "/index" + strconv.Itoa(p-i) + ".html"
		fmt.Println("\n" + pagesURL[i] + "\n")
		fetchSingle(pagesURL[i])
	}
}

func fetch(url string) *http.Response {
	resp, err := http.Get(url)

	if err != nil {
		fmt.Println(err)
	}

	return resp
}
<commit_msg>Refactor of methods<commit_after>package main

import (
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/koding/multiconfig"
)

type DefaultConf struct {
	Board       string `default:"car"`
	BaseUrl     string `default:"https://www.ptt.cc/bbs/"`
	CarBoardUrl string `default:"https://www.ptt.cc/bbs/car/index.html"`
	Page        int    `default:1`
	Mode        string `default:"view"`
}

var conf = loadConfig()

func loadConfig() *DefaultConf {
	m := multiconfig.New()
	conf := new(DefaultConf)
	m.MustLoad(conf)

	return conf
}

func main() {

	switch conf.Mode {
		case "view":
			fetchMultiPages(conf.Board, conf.Page)
		case "crawl":
			fmt.Println("Will support crawl later on")
	}
}

func fetchSingle(url string) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc := getDocument(resp)
	doc.Find("div.title").Each(func(i int, s *goquery.Selection) {
		link, _ := s.Find("a").Attr("href")
		title := strings.TrimSpace(s.Text())
		if (len(link) != 0) {
			fmt.Println(title + "\t" + "https://www.ptt.cc" + link)
		}
	})
}

func fetchPages(url string, ch chan int) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc := getDocument(resp)
	href, _ := doc.Find("div.action-bar").Find("a.btn").Eq(3).Attr("href")
	pages, _ := strconv.Atoi(strings.Trim(strings.Split(strings.Split(href, "/")[3], ".")[0], "index"))
	ch <- pages + 1
}

func fetchMultiPages(board string, pre int) {
	fmt.Println("Do fetch multiple pages")
	ch := make(chan int)
	url := conf.BaseUrl + board + "/index.html"
	go fetchPages(url, ch)
	p := <-ch

	var pagesURL = make([]string, pre+1)
	for i := pre; i >= 0; i-- {
		pagesURL[i] = conf.BaseUrl + board + "/index" + strconv.Itoa(p-i) + ".html"
		fmt.Println("\n" + pagesURL[i] + "\n")
		fetchSingle(pagesURL[i])
	}
}

func getDocument(resp *http.Response) *goquery.Document {
	r := io.Reader(resp.Body)
	doc, _ := goquery.NewDocumentFromReader(r)
	return doc
}

func fetch(url string) *http.Response {
	resp, _ := http.Get(url)
	return resp
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/koding/multiconfig"
)

type DefaultConf struct {
	Board       string
	BaseUrl     string
	CarBoardUrl string
}

func main() {
	m := multiconfig.NewWithPath("config.toml")
	defaultConf := new(DefaultConf)
	m.MustLoad(defaultConf)

	flag.Parse()
	args := flag.Args()
	var url string
	var board string

	if len(args) == 0 {
		url = defaultConf.CarBoardUrl
		fmt.Println("Fetch default car board")
		fetchSingle(url)
	} else if len(args) == 2 && args[1] == "allpages" {
		board = args[0]
		url = defaultConf.BaseUrl + board + "/index.html"
		fmt.Println("Fetch allpages " + url)
		fetchPages(url)
	} else if len(args) == 1 && args[0] != "allpages" {
		board = args[0]
		url = defaultConf.BaseUrl + board + "/index.html"
		fmt.Println("Fetch Single Pages " + url)
		fetchSingle(url)
	} else {
		board = args[0]
		page := args[1]
		url = defaultConf.BaseUrl + board + "/index" + page + ".html"
		fmt.Println("Fetch single pages with page " + url)
		fetchSingle(url)
	}
}

func fetchSingle(url string) {
	resp, err := http.Get(url)

	if err != nil {
		return
	}

	defer resp.Body.Close()

	doc, err := goquery.NewDocumentFromReader(io.Reader(resp.Body))

	doc.Find("div.title").Each(func(i int, s *goquery.Selection) {
		a := s.Find("a")
		qHref, _ := a.Attr("href")
		fmt.Println(strings.TrimSpace(s.Text()) + "\t" + "https://www.ptt.cc" + qHref)
	})
}

func fetchPages(url string) {
	resp, err := http.Get(url)

	if err != nil {
		fmt.Println(err)
	}

	defer resp.Body.Close()

	doc, err := goquery.NewDocumentFromReader(io.Reader(resp.Body))

	href, _ := doc.Find("div.action-bar").Find("a.btn").Eq(3).Attr("href")
	pages := strings.Split(href, "/")[3]
	fmt.Println(pages)
}
<commit_msg>Support multipages fetch<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/koding/multiconfig"
)

type DefaultConf struct {
	Board       string
	BaseUrl     string
	CarBoardUrl string
}

func main() {

	m := multiconfig.NewWithPath("config.toml")
	defaultConf := new(DefaultConf)
	m.MustLoad(defaultConf)

	flag.Parse()
	args := flag.Args()
	var url string
	var board string

	if len(args) == 0 {
		url = defaultConf.CarBoardUrl
		fmt.Println("Fetch default car board")
		fetchSingle(url)
	} else if len(args) == 2 && args[1] == "allpages" {
		board = args[0]
		url = defaultConf.BaseUrl + board + "/index.html"
		fmt.Println("Fetch allpages " + url)
		pages := make(chan int)
		go fetchPages(url, pages)
		result := <-pages
		fmt.Println(result)
	} else if len(args) == 1 && args[0] != "allpages" {
		board = args[0]
		url = defaultConf.BaseUrl + board + "/index.html"
		fmt.Println("Fetch Single Pages " + url)
		fetchSingle(url)
	} else if len(args) == 3 {
		board = args[0]
		url = defaultConf.BaseUrl + board + "/index.html"
		fmt.Println("Fetch " + args[2] + " Pages " + url)
		pre, _ := strconv.Atoi(args[2])
		fetchMultiPages(defaultConf.BaseUrl, board, pre)
	} else {
		board = args[0]
		page := args[1]
		url = defaultConf.BaseUrl + board + "/index" + page + ".html"
		fmt.Println("Fetch single pages with page " + url)
		fetchSingle(url)
	}
}

func fetchSingle(url string) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc, _ := goquery.NewDocumentFromReader(io.Reader(resp.Body))

	doc.Find("div.title").Each(func(i int, s *goquery.Selection) {
		a := s.Find("a")
		qHref, _ := a.Attr("href")
		title := strings.TrimSpace(s.Text())
		//ch <- title + "\t" + "https://www.ptt.cc" + qHref
		fmt.Println(title + "\t" + "https://www.ptt.cc" + qHref)
	})
}

func fetchPages(url string, ch chan int) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc, _ := goquery.NewDocumentFromReader(io.Reader(resp.Body))

	href, _ := doc.Find("div.action-bar").Find("a.btn").Eq(3).Attr("href")

	pages, _ := strconv.Atoi(strings.Trim(strings.Split(strings.Split(href, "/")[3], ".")[0], "index"))
	ch <- pages + 1
}

func fetchMultiPages(baseUrl string, board string, pre int) {
	fmt.Println("Do fetch multiple pages")
	ch := make(chan int)
	url := baseUrl + board + "/index.html"
	go fetchPages(url, ch)
	p := <-ch

	// p, _ := strconv.Atoi(strings.Trim(strings.Split(pages, ".")[0], "index"))
	fmt.Println(p)
	var pagesURL = make([]string, pre+1)
	for i := pre; i >= 0; i-- {
		pagesURL[i] = baseUrl + board + "/index" + strconv.Itoa(p-i) + ".html"
		fmt.Println("\n" + pagesURL[i] + "\n")
		fetchSingle(pagesURL[i])
	}
}

func fetch(url string) *http.Response {
	resp, err := http.Get(url)

	if err != nil {
		fmt.Println(err)
	}

	return resp
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"github.com/PuerkitoBio/goquery"
	"github.com/olekukonko/tablewriter"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
)

var (
	Board   = flag.String("b", "car", "Specific ptt board")
	Page    = flag.Int("p", 2, "Default pages to fetched")
	Feature = flag.String("f", "hot", "Special features")
)

var (
	BaseUrl     = "https://www.ptt.cc/bbs/"
	CarBoardUrl = "https://www.ptt.cc/bbs/car/index.html"
)

func main() {
	flag.Parse()
	fetchMultiPages(*Board, *Page)
}

func fetchSingle(url string, str chan string) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc := getDocument(resp)
	tmp := ""
	doc.Find("div.r-ent").Each(func(i int, s *goquery.Selection) {
		link, _ := s.Find("a").Attr("href")
		title := s.Find("a").Text()
		push := s.Find("span").Text()
		if push == "" {
			push = "X"
		}

		if len(link) != 0 {
			tmp += push + "\t" + title + "\t" + "https://www.ptt.cc" + link + "\n"
		}
	})
	str <- tmp
}

func fetchPages(url string, ch chan int) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc := getDocument(resp)
	href, _ := doc.Find("div.action-bar").Find("a.btn").Eq(3).Attr("href")
	pages, _ := strconv.Atoi(strings.Trim(strings.Split(strings.Split(href, "/")[3], ".")[0], "index"))
	ch <- pages
}

func fetchMultiPages(board string, pre int) {
	ch := make(chan int)
	url := BaseUrl + board + "/index.html"
	go fetchPages(url, ch)
	p := <-ch

	var output string

	var pagesURL = make([]string, pre+1)
	for i := pre; i >= 0; i-- {
		pagesURL[i] = BaseUrl + board + "/index" + strconv.Itoa(p+1-i) + ".html"
		s := make(chan string)
		go fetchSingle(pagesURL[i], s)
		result := <-s
		output += result
	}

	printOutput(output)
}

func  printOutput(output string) {

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"Push", "Title", "URL"})
	table.SetBorder(false)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	o := [][]string{}

	rows := strings.Split(output, "\n")
	for i := 0; i < len(rows); i++ {
		row := strings.Split(rows[i], "\t") // row[0], row[1], row[2]
		if len(row) == 3 {
			arr := []string{row[0], row[1], row[2]}
			o = append(o, arr)
		}
	}

	for _, v := range o {
		table.Append(v)
	}

	table.Render() // Send output
}

func getDocument(resp *http.Response) *goquery.Document {
	r := io.Reader(resp.Body)
	doc, _ := goquery.NewDocumentFromReader(r)
	return doc
}

func fetch(url string) *http.Response {
	resp, _ := http.Get(url)
	return resp
}
<commit_msg>BugFix: Remove unwanted sentences in title<commit_after>package main

import (
	"flag"
	"github.com/PuerkitoBio/goquery"
	"github.com/olekukonko/tablewriter"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
)

var (
	Board   = flag.String("b", "car", "Specific ptt board")
	Page    = flag.Int("p", 2, "Default pages to fetched")
	Feature = flag.String("f", "hot", "Special features")
)

var (
	BaseUrl     = "https://www.ptt.cc/bbs/"
	CarBoardUrl = "https://www.ptt.cc/bbs/car/index.html"
)

func main() {
	flag.Parse()
	fetchMultiPages(*Board, *Page)
}

func fetchSingle(url string, str chan string) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc := getDocument(resp)
	tmp := ""
	doc.Find("div.r-ent").Each(func(i int, s *goquery.Selection) {
		link, _ := s.Find("a").Attr("href")
		title := s.Find("div.title a").Text()
		push := s.Find("span").Text()
		if push == "" {
			push = "X"
		}

		if len(link) != 0 {
			tmp += push + "\t" + title + "\t" + "https://www.ptt.cc" + link + "\n"
		}
	})
	str <- tmp
}

func fetchPages(url string, ch chan int) {
	resp := fetch(url)
	defer resp.Body.Close()

	doc := getDocument(resp)
	href, _ := doc.Find("div.action-bar").Find("a.btn").Eq(3).Attr("href")
	pages, _ := strconv.Atoi(strings.Trim(strings.Split(strings.Split(href, "/")[3], ".")[0], "index"))
	ch <- pages
}

func fetchMultiPages(board string, pre int) {
	ch := make(chan int)
	url := BaseUrl + board + "/index.html"
	go fetchPages(url, ch)
	p := <-ch

	var output string

	var pagesURL = make([]string, pre+1)
	for i := pre; i >= 0; i-- {
		pagesURL[i] = BaseUrl + board + "/index" + strconv.Itoa(p+1-i) + ".html"
		s := make(chan string)
		go fetchSingle(pagesURL[i], s)
		result := <-s
		output += result
	}

	printOutput(output)
}

func printOutput(output string) {

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"Push", "Title", "URL"})
	table.SetBorder(false)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	o := [][]string{}

	rows := strings.Split(output, "\n")
	for i := 0; i < len(rows); i++ {
		row := strings.Split(rows[i], "\t") // row[0], row[1], row[2]
		if len(row) == 3 {
			arr := []string{row[0], row[1], row[2]}
			o = append(o, arr)
		}
	}

	for _, v := range o {
		table.Append(v)
	}

	table.Render() // Send output
}

func getDocument(resp *http.Response) *goquery.Document {
	r := io.Reader(resp.Body)
	doc, _ := goquery.NewDocumentFromReader(r)
	return doc
}

func fetch(url string) *http.Response {
	resp, _ := http.Get(url)
	return resp
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/ratanvarghese/tqtime"
	"log"
	"os"
	"strings"
	"time"
)

func main() {
	inputFormat := flag.String("inputformat", time.UnixDate, "Reference date in Gregorian input format")
	input := flag.String("input", "", "Gregorian input date, use stdin if omitted")
	help := flag.Bool("help", false, "Print command-line options")
	flag.Parse()

	if *help {
		flag.PrintDefaults()
		return
	}

	var inputReader *bufio.Reader
	if *input == "" {
		inputReader = bufio.NewReader(os.Stdin)
	} else {
		inputReader = bufio.NewReader(strings.NewReader((*input) + "\n"))
	}

	for {
		line, lineErr := inputReader.ReadString('\n')
		if lineErr != nil {
			break
		}
		t, parseErr := time.Parse(*inputFormat, strings.TrimSpace(line))
		if parseErr != nil {
			log.Fatal(parseErr.Error())
			break
		}
		long := tqtime.LongDate(t.Year(), t.YearDay())
		fmt.Println(long)
	}
}
<commit_msg>Add short format option for tqdate<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/ratanvarghese/tqtime"
	"log"
	"os"
	"strings"
	"time"
)

func main() {
	inputFormat := flag.String("inputformat", time.UnixDate, "Reference date in Gregorian input format")
	input := flag.String("input", "", "Gregorian input date, use stdin if omitted")
	help := flag.Bool("help", false, "Print command-line options")
	short := flag.Bool("short", false, "Use short output format")
	flag.Parse()

	if *help {
		flag.PrintDefaults()
		return
	}

	var inputReader *bufio.Reader
	if *input == "" {
		inputReader = bufio.NewReader(os.Stdin)
	} else {
		inputReader = bufio.NewReader(strings.NewReader((*input) + "\n"))
	}

	for {
		line, lineErr := inputReader.ReadString('\n')
		if lineErr != nil {
			break
		}
		t, parseErr := time.Parse(*inputFormat, strings.TrimSpace(line))
		if parseErr != nil {
			log.Fatal(parseErr.Error())
			break
		}

		var out string
		if *short {
			out = tqtime.ShortDate(t.Year(), t.YearDay())
		} else {
			out = tqtime.LongDate(t.Year(), t.YearDay())
		}
		fmt.Println(out)
	}
}
<|endoftext|>