<commit_before>package resize

import (
	"image"
	"testing"
)

func Test_MakeSizeSpec(t *testing.T) {

	ss := MakeSizeSpec("100s")
	if ss.IsFull() {
		t.Error("thinks it is full-size")
	}
	if !ss.IsSquare() {
		t.Error("not square")
	}
	if ss.Width() != 100 {
		t.Error("not 100 wide")
	}
	if ss.Height() != 100 {
		t.Error("not 100 high")
	}

	ss2 := MakeSizeSpec("100w")
	if ss2.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss2.IsSquare() {
		t.Error("think's it is square")
	}
	if ss2.Width() != 100 {
		t.Error("not 100 wide")
	}
	if ss2.Height() != -1 {
		t.Error("not -1 high")
	}

	ss3 := MakeSizeSpec("100h")
	if ss3.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss3.IsSquare() {
		t.Error("think's it is square")
	}
	if ss3.Width() != -1 {
		t.Error("not -1 wide")
	}
	if ss3.Height() != 100 {
		t.Error("not 100 high")
	}

	ss4 := MakeSizeSpec("100h200w")
	if ss4.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss4.IsSquare() {
		t.Error("think's it is square")
	}
	if ss4.Width() != 200 {
		t.Error("not 200 wide")
	}
	if ss4.Height() != 100 {
		t.Error("not 100 high")
	}

	ss5 := MakeSizeSpec("100w200h")
	if ss5.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss5.IsSquare() {
		t.Error("think's it is square")
	}
	if ss5.Width() != 100 {
		t.Error("not 100 wide")
	}
	if ss5.Height() != 200 {
		t.Error("not 200 high")
	}

	ss6 := MakeSizeSpec("full")
	if !ss6.IsFull() {
		t.Error("not full-size")
	}
	if ss6.IsSquare() {
		t.Error("not square")
	}
	if ss6.Width() != -1 {
		t.Error("width set on full")
	}
	if ss6.Height() != -1 {
		t.Error("height set on full")
	}

}

func Test_ToRect(t *testing.T) {
	full_sized := MakeSizeSpec("full")
	square_sized := MakeSizeSpec("100s")
	width_constrained := MakeSizeSpec("100w")
	height_constrained := MakeSizeSpec("100h")
	height_and_width_constrained_wh := MakeSizeSpec("200w100h")
	height_and_width_constrained_hw := MakeSizeSpec("200h100w")
	wider_than_tall := image.Rect(0,0,1000,500)
	taller_than_wide := image.Rect(0,0,500,1000)
	square_image := image.Rect(0,0,1000,1000)

	// full-size should be no-op
	results := full_sized.ToRect(square_image)
	if results.Dx() != square_image.Dx() {
		t.Error("bad width")
	}
	if results.Dy() != square_image.Dy() {
		t.Error("bad height")
	}

	// square == square
	results2 := square_sized.ToRect(square_image)
	if results2.Dx() != square_image.Dx() {
		t.Error("bad width")
	}
	if results2.Dy() != square_image.Dy() {
		t.Error("bad height")
	}

	// square != square (wider than taller)
	results3 := square_sized.ToRect(wider_than_tall)
	if results3.Dx() != 500 {
		t.Error("bad width")
	}
	if results3.Dy() != 500 {
		t.Error("bad height")
	}

	// square != square (taller than wider)
	results4 := square_sized.ToRect(taller_than_wide)
	if results4.Dx() != 500 {
		t.Error("bad width")
	}
	if results4.Dy() != 500 {
		t.Error("bad height")
	}

	// width constrained square image
	results5 := width_constrained.ToRect(square_image)
	if results5.Dx() != 1000 {
		t.Error("bad width")
	}
	if results5.Dy() != 1000 {
		t.Error("bad height")
	}

	// width constrained wider than tall
	results6 := width_constrained.ToRect(wider_than_tall)
	if results6.Dx() != 1000 {
		t.Error("bad width")
	}
	if results6.Dy() != 500 {
		t.Error("bad height")
	}

	// width constrained taller than wide
	results7 := width_constrained.ToRect(taller_than_wide)
	if results7.Dx() != 500 {
		t.Error("bad width")
	}
	if results7.Dy() != 1000 {
		t.Error("bad height")
	}

	// height constrained square image
	results8 := height_constrained.ToRect(square_image)
	if results8.Dx() != 1000 {
		t.Error("bad width")
	}
	if results8.Dy() != 1000 {
		t.Error("bad height")
	}

	// height constrained wider than tall
	results9 := height_constrained.ToRect(wider_than_tall)
	if results9.Dx() != 1000 {
		t.Error("bad width")
	}
	if results9.Dy() != 500 {
		t.Error("bad height")
	}

	// height constrained taller than wide
	results10 := height_constrained.ToRect(taller_than_wide)
	if results10.Dx() != 500 {
		t.Error("bad width")
	}
	if results10.Dy() != 1000 {
		t.Error("bad height")
	}

	// height and width constrained (w>h) square
	results11 := height_and_width_constrained_wh.ToRect(square_image)
	if results11.Dx() != 1000 {
		t.Error("bad width")
	}
	if results11.Dy() != 500 {
		t.Error("bad height")
	}

	// height and width constrained (w>h) taller than wide
	results12 := height_and_width_constrained_wh.ToRect(taller_than_wide)
	if results12.Dx() != 500 {
		t.Error("bad width")
	}
	if results12.Dy() != 250 {
		t.Error("bad height")
	}

	// height and width constrained (w>h) wider than tall
	results13 := height_and_width_constrained_wh.ToRect(wider_than_tall)
	if results13.Dx() != 500 {
		t.Error("bad width")
	}
	if results13.Dy() != 1000 {
		t.Error("bad height")
	}

	// height and width constrained (h>w) square
	results14 := height_and_width_constrained_hw.ToRect(square_image)
	if results14.Dx() != 500 {
		t.Error("bad width")
	}
	if results14.Dy() != 1000 {
		t.Error("bad height")
	}

	// height and width constrained (h>w) taller than wide
	results15 := height_and_width_constrained_hw.ToRect(taller_than_wide)
	if results15.Dx() != 500 {
		t.Error("bad width")
	}
	if results15.Dy() != 1000 {
		t.Error("bad height")
	}

	// height and width constrained (h>w) wider than tall
	results16 := height_and_width_constrained_hw.ToRect(wider_than_tall)
	if results16.Dx() != 500 {
		t.Error("bad width")
	}
	if results16.Dy() != 1000 {
		t.Error("bad height")
	}

}
<commit_msg>more DRY tests<commit_after>package resize

import (
	"image"
	"testing"
)

func Test_MakeSizeSpec(t *testing.T) {

	ss := MakeSizeSpec("100s")
	if ss.IsFull() {
		t.Error("thinks it is full-size")
	}
	if !ss.IsSquare() {
		t.Error("not square")
	}
	if ss.Width() != 100 {
		t.Error("not 100 wide")
	}
	if ss.Height() != 100 {
		t.Error("not 100 high")
	}

	ss2 := MakeSizeSpec("100w")
	if ss2.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss2.IsSquare() {
		t.Error("think's it is square")
	}
	if ss2.Width() != 100 {
		t.Error("not 100 wide")
	}
	if ss2.Height() != -1 {
		t.Error("not -1 high")
	}

	ss3 := MakeSizeSpec("100h")
	if ss3.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss3.IsSquare() {
		t.Error("think's it is square")
	}
	if ss3.Width() != -1 {
		t.Error("not -1 wide")
	}
	if ss3.Height() != 100 {
		t.Error("not 100 high")
	}

	ss4 := MakeSizeSpec("100h200w")
	if ss4.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss4.IsSquare() {
		t.Error("think's it is square")
	}
	if ss4.Width() != 200 {
		t.Error("not 200 wide")
	}
	if ss4.Height() != 100 {
		t.Error("not 100 high")
	}

	ss5 := MakeSizeSpec("100w200h")
	if ss5.IsFull() {
		t.Error("thinks it is full-size")
	}
	if ss5.IsSquare() {
		t.Error("think's it is square")
	}
	if ss5.Width() != 100 {
		t.Error("not 100 wide")
	}
	if ss5.Height() != 200 {
		t.Error("not 200 high")
	}

	ss6 := MakeSizeSpec("full")
	if !ss6.IsFull() {
		t.Error("not full-size")
	}
	if ss6.IsSquare() {
		t.Error("not square")
	}
	if ss6.Width() != -1 {
		t.Error("width set on full")
	}
	if ss6.Height() != -1 {
		t.Error("height set on full")
	}

}

type toRectTestCase struct {
	Label string
	SizeSpec *sizeSpec
	Rect image.Rectangle
	ExpectedWidth int
	ExpectedHeight int
}

func Test_ToRect(t *testing.T) {
	full_sized := MakeSizeSpec("full")
	square_sized := MakeSizeSpec("100s")
	width_constrained := MakeSizeSpec("100w")
	height_constrained := MakeSizeSpec("100h")
	height_and_width_constrained_wh := MakeSizeSpec("200w100h")
	height_and_width_constrained_hw := MakeSizeSpec("200h100w")
	wider_than_tall := image.Rect(0,0,1000,500)
	taller_than_wide := image.Rect(0,0,500,1000)
	square_image := image.Rect(0,0,1000,1000)

	cases := []toRectTestCase{
		{
		Label: "full-sized should be no-op",
		SizeSpec: full_sized,
		Rect: square_image,
		ExpectedWidth: square_image.Dx(),
		ExpectedHeight: square_image.Dy(),
		},

		{
		Label: "square == square",
		SizeSpec: square_sized,
		Rect: square_image,
		ExpectedWidth: square_image.Dx(),
		ExpectedHeight: square_image.Dy(),
		},

		{
		Label: "square != square (wider than taller)",
		SizeSpec: square_sized,
		Rect: wider_than_tall,
		ExpectedWidth: 500,
		ExpectedHeight: 500,
		},

		{
		Label: "square != square (taller than wide)",
		SizeSpec: square_sized,
		Rect: taller_than_wide,
		ExpectedWidth: 500,
		ExpectedHeight: 500,
		},

		{
		Label: "width constrained square image",
		SizeSpec: width_constrained,
		Rect: square_image,
		ExpectedWidth: 1000,
		ExpectedHeight: 1000,
		},

		{
		Label: "width constrained wider than tall",
		SizeSpec: width_constrained,
		Rect: wider_than_tall,
		ExpectedWidth: 1000,
		ExpectedHeight: 500,
		},

		{
		Label: "width constrained taller than wide",
		SizeSpec: width_constrained,
		Rect: taller_than_wide,
		ExpectedWidth: 500,
		ExpectedHeight: 1000,
		},

		{
		Label: "height constrained square image",
		SizeSpec: height_constrained,
		Rect: square_image,
		ExpectedWidth: 1000,
		ExpectedHeight: 1000,
		},

		{
		Label: "height constrained wider than tall",
		SizeSpec: height_constrained,
		Rect: wider_than_tall,
		ExpectedWidth: 1000,
		ExpectedHeight: 500,
		},

		{
		Label: "height constrained taller than wide",
		SizeSpec: height_constrained,
		Rect: taller_than_wide,
		ExpectedWidth: 500,
		ExpectedHeight: 1000,
		},

		{
		Label: "height and width constrained (w>h) square",
		SizeSpec: height_and_width_constrained_wh,
		Rect: square_image,
		ExpectedWidth: 1000,
		ExpectedHeight: 500,
		},

		{
		Label: "height and width constrained (w>h) taller than wide",
		SizeSpec: height_and_width_constrained_wh,
		Rect: taller_than_wide,
		ExpectedWidth: 500,
		ExpectedHeight: 250,
		},

		{
		Label: "height and width constrained (w>h) wider than tall",
		SizeSpec: height_and_width_constrained_wh,
		Rect: wider_than_tall,
		ExpectedWidth: 500,
		ExpectedHeight: 1000,
		},

		{
		Label: "height and width constrained (h>w) square",
		SizeSpec: height_and_width_constrained_hw,
		Rect: square_image,
		ExpectedWidth: 500,
		ExpectedHeight: 1000,
		},

		{
		Label: "height and width constrained (h>w) taller than wide",
		SizeSpec: height_and_width_constrained_hw,
		Rect: taller_than_wide,
		ExpectedWidth: 500,
		ExpectedHeight: 1000,
		},

		{
		Label: "height and width constrained (h>w) wider than tall",
		SizeSpec: height_and_width_constrained_hw,
		Rect: wider_than_tall,
		ExpectedWidth: 500,
		ExpectedHeight: 1000,
		},

	}

	for i := range cases {
		c := cases[i]
		r := c.SizeSpec.ToRect(c.Rect)
		if r.Dx() != c.ExpectedWidth {
			t.Error(c.Label, "-- bad width",r.Dx()," expected",c.ExpectedWidth)
		}
		if r.Dy() != c.ExpectedHeight {
			t.Error(c.Label, "-- bad height",r.Dy()," expected",c.ExpectedHeight)
		}
	}

}
<|endoftext|>


<commit_before>package resources

import (
    "github.com/orc/db"
    "github.com/orc/mvc/controllers"
    "github.com/orc/mvc/models"
    "io/ioutil"
    "math/rand"
    "strconv"
    "strings"
    "time"
)

const USER_COUNT = 20

var base = new(models.ModelManager)

func random(min, max int) int {
    rand.Seed(int64(time.Now().Second()))
    return rand.Intn(max-min) + min
}

func addDate(d, m, y int) string {
    return strconv.Itoa(d) + "-" + strconv.Itoa(m) + "-" + strconv.Itoa(y)
}

func addTime(h, m, s int) string {
    return strconv.Itoa(h) + ":" + strconv.Itoa(m) + ":" + strconv.Itoa(s)
}

func Load() {
    loadUsers()
    loadEvents()
    loadEventTypes()
    loadForms()
    loadParamTypes()
}

func loadUsers() {
    base := new(controllers.BaseController)
    for i := 0; i < USER_COUNT; i++ {
        rand.Seed(int64(i))
        result, reg_id := base.Handler().HandleRegister_("user"+strconv.Itoa(i), "secret"+strconv.Itoa(i), "user")
        if result == "ok" {
            eventsRegs := controllers.GetModel("events_regs")
            eventsRegs.LoadModelData(map[string]interface{}{"reg_id": reg_id, "event_id": 1})
            db.QueryInsert_(eventsRegs, "")
        }
    }
    result, reg_id := base.Handler().HandleRegister_("admin", "password", "admin")
    if result == "ok" {
        eventsRegs := controllers.GetModel("events_regs")
        eventsRegs.LoadModelData(map[string]interface{}{"reg_id": reg_id, "event_id": 1})
        db.QueryInsert_(eventsRegs, "")
    }
}

func loadEvents() {
    eventNames, _ := ioutil.ReadFile("./resources/event-name")
    subjectNames, _ := ioutil.ReadFile("./resources/subject-name")
    eventNameSource := strings.Split(string(eventNames), "\n")
    subjectNameSource := strings.Split(string(subjectNames), "\n")
    for i := 0; i < len(eventNameSource); i++ {
        rand.Seed(int64(i))
        eventName := strings.TrimSpace(eventNameSource[rand.Intn(len(eventNameSource))])
        eventName += " по дисциплине "
        eventName += "\"" + strings.TrimSpace(subjectNameSource[rand.Intn(len(subjectNameSource))]) + "\""
        dateStart := addDate(random(1894, 2014), random(1, 12), random(1, 28))
        dateFinish := addDate(random(1894, 2014), random(1, 12), random(1, 28))
        time := addTime(random(0, 11), random(1, 60), random(1, 60))
        params := []interface{}{eventName, dateStart, dateFinish, time, ""}
        entity := base.Events()
        db.QueryInsert("events", entity.GetColumnSlice(1), params, "")
    }
}

func loadEventTypes() {
    eventTypeNames, _ := ioutil.ReadFile("./resources/event-type-name")
    eventTypeNamesSourse := strings.Split(string(eventTypeNames), "\n")
    topicality := []bool{true, false}
    for i := 0; i < len(eventTypeNamesSourse); i++ {
        //rand.Seed(int64(i))
        eventTypeName := strings.TrimSpace(eventTypeNamesSourse[i])
        params := []interface{}{eventTypeName, "", topicality[rand.Intn(2)]}
        entity := base.EventTypes()
        db.QueryInsert("event_types", entity.GetColumnSlice(1), params, "")
    }
}

func loadForms() {
    formNames, _ := ioutil.ReadFile("./resources/form-name")
    formNamesSourse := strings.Split(string(formNames), "\n")
    for i := 0; i < len(formNamesSourse); i++ {
        formName := strings.TrimSpace(formNamesSourse[i])
        entity := base.Forms()
        db.QueryInsert("forms", entity.GetColumnSlice(1), []interface{}{formName}, "")
    }
}

func loadParamTypes() {
    paramTypesNames, _ := ioutil.ReadFile("./resources/param-type-name")
    paramTypesSourse := strings.Split(string(paramTypesNames), "\n")
    for i := 0; i < len(paramTypesSourse); i++ {
        paramType := strings.TrimSpace(paramTypesSourse[i])
        entity := base.ParamTypes()
        db.QueryInsert("param_types", entity.GetColumnSlice(1), []interface{}{paramType}, "")
    }
}
<commit_msg>loadData: fix inserts<commit_after>package resources

import (
    "github.com/orc/db"
    "github.com/orc/mvc/controllers"
    "github.com/orc/mvc/models"
    "io/ioutil"
    "math/rand"
    "strconv"
    "strings"
    "time"
)

const USER_COUNT = 20

var base = new(models.ModelManager)

func random(min, max int) int {
    rand.Seed(int64(time.Now().Second()))
    return rand.Intn(max-min) + min
}

func addDate(d, m, y int) string {
    return strconv.Itoa(d) + "-" + strconv.Itoa(m) + "-" + strconv.Itoa(y)
}

func addTime(h, m, s int) string {
    return strconv.Itoa(h) + ":" + strconv.Itoa(m) + ":" + strconv.Itoa(s)
}

func Load() {
    loadUsers()
    loadEvents()
    loadEventTypes()
    loadForms()
    loadParamTypes()
}

func loadUsers() {
    base := new(controllers.BaseController)
    for i := 0; i < USER_COUNT; i++ {
        rand.Seed(int64(i))
        result, reg_id := base.Handler().HandleRegister_("user"+strconv.Itoa(i), "secret"+strconv.Itoa(i), "user")
        if result == "ok" {
            eventsRegs := controllers.GetModel("events_regs")
            eventsRegs.LoadModelData(map[string]interface{}{"reg_id": reg_id, "event_id": 1})
            db.QueryInsert_(eventsRegs, "")
        }
    }
    result, reg_id := base.Handler().HandleRegister_("admin", "password", "admin")
    if result == "ok" {
        eventsRegs := controllers.GetModel("events_regs")
        eventsRegs.LoadModelData(map[string]interface{}{"reg_id": reg_id, "event_id": 1})
        db.QueryInsert_(eventsRegs, "")
    }
}

func loadEvents() {
    eventNames, _ := ioutil.ReadFile("./resources/event-name")
    subjectNames, _ := ioutil.ReadFile("./resources/subject-name")
    eventNameSource := strings.Split(string(eventNames), "\n")
    subjectNameSource := strings.Split(string(subjectNames), "\n")
    for i := 0; i < len(eventNameSource); i++ {
        rand.Seed(int64(i))
        eventName := strings.TrimSpace(eventNameSource[rand.Intn(len(eventNameSource))])
        eventName += " по дисциплине "
        eventName += "\"" + strings.TrimSpace(subjectNameSource[rand.Intn(len(subjectNameSource))]) + "\""
        dateStart := addDate(random(1894, 2014), random(1, 12), random(1, 28))
        dateFinish := addDate(random(1894, 2014), random(1, 12), random(1, 28))
        time := addTime(random(0, 11), random(1, 60), random(1, 60))
        params := map[string]interface{}{"name": eventName, "data_start": dateStart, "date_finish": dateFinish, "time": time, "url": ""}
        entity := base.Events()
        entity.LoadModelData(params)
        db.QueryInsert_(entity, "")
    }
}

func loadEventTypes() {
    eventTypeNames, _ := ioutil.ReadFile("./resources/event-type-name")
    eventTypeNamesSourse := strings.Split(string(eventTypeNames), "\n")
    topicality := []bool{true, false}
    for i := 0; i < len(eventTypeNamesSourse); i++ {
        //rand.Seed(int64(i))
        eventTypeName := strings.TrimSpace(eventTypeNamesSourse[i])
        params := map[string]interface{}{"name": eventTypeName, "description": "", "topicality": topicality[rand.Intn(2)]}
        entity := base.EventTypes()
        entity.LoadModelData(params)
        db.QueryInsert_(entity, "")
    }
}

func loadForms() {
    formNames, _ := ioutil.ReadFile("./resources/form-name")
    formNamesSourse := strings.Split(string(formNames), "\n")
    for i := 0; i < len(formNamesSourse); i++ {
        formName := strings.TrimSpace(formNamesSourse[i])
        entity := base.Forms()
        entity.LoadModelData(map[string]interface{}{"name": formName})
        db.QueryInsert_(entity, "")
    }
}

func loadParamTypes() {
    paramTypesNames, _ := ioutil.ReadFile("./resources/param-type-name")
    paramTypesSourse := strings.Split(string(paramTypesNames), "\n")
    for i := 0; i < len(paramTypesSourse); i++ {
        paramType := strings.TrimSpace(paramTypesSourse[i])
        entity := base.ParamTypes()
        entity.LoadModelData(map[string]interface{}{"name": paramType})
        db.QueryInsert_(entity, "")
    }
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"github.com/jmoiron/sqlx"
	"github.com/lib/pq"
)

func sqlInterface(
	rootAccount *Account,
	transactions []*Transaction,
) {

	dbDir := filepath.Join(os.TempDir(), fmt.Sprintf("keep-%d", rand.Int63()))
	if out, err := exec.Command("initdb", "-D", dbDir).CombinedOutput(); err != nil {
		fail("%v: %s", err, out)
	}
	pt("db dir: %s\n", dbDir)
	defer exec.Command("rm", "-rf", dbDir).Run()

	port := 10000 + rand.Intn(50000)
	conf := `
		port = ` + fmt.Sprintf("%d", port) + `
	`
	if err := ioutil.WriteFile(filepath.Join(dbDir, "postgresql.conf"), []byte(conf), 0644); err != nil {
		fail("%v", err)
	}
	c := exec.Command("postgres", "-D", dbDir)
	c.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	if err := c.Start(); err != nil {
		fail("%v", err)
	}
	defer syscall.Kill(-c.Process.Pid, syscall.SIGKILL)
	time.Sleep(time.Second)
	pt("db started\n")

	db, err := sqlx.Open("postgres", fmt.Sprintf("postgres://localhost:%d/postgres?sslmode=disable", port))
	if err != nil {
		fail("%v", err)
	}
	defer db.Close()
	tx := db.MustBegin()
	if _, err := tx.Exec(`
		CREATE TABLE entries (
			id bigserial primary key,
			transaction bigint,
			transaction_description text,
			date timestamp with time zone,
			account text[],
			currency text,
			amount numeric,
			description text
		)
		`,
	); err != nil {
		fail("%v", err)
	}
	for _, view := range views {
		if _, err := tx.Exec(view); err != nil {
			fail("%v", err)
		}
	}
	for tid, transaction := range transactions {
		for _, entry := range transaction.Entries {
			if _, err := tx.Exec(`
				INSERT INTO entries
				(
					transaction, transaction_description,
					date, account, currency, amount, description
				)
				VALUES (
					$1, $2,
					$3, $4, $5, $6::numeric, $7
				)
				`,
				tid,
				transaction.Description,
				entry.Time,
				func() (ret pq.StringArray) {
					acc := entry.Account
					for acc != rootAccount {
						ret = append(ret, acc.Name)
						acc = acc.Parent
					}
					for i := len(ret)/2 - 1; i >= 0; i-- {
						j := len(ret) - 1 - i
						ret[i], ret[j] = ret[j], ret[i]
					}
					return
				}(),
				entry.Currency,
				entry.Amount.FloatString(3),
				entry.Description,
			); err != nil {
				fail("%v", err)
			}
		}
	}
	if err := tx.Commit(); err != nil {
		fail("%v", err)
	}
	pt("data loaded\n")

	sigs := make(chan os.Signal)
	go func() {
		for {
			<-sigs
		}
	}()
	signal.Notify(sigs, os.Interrupt)

	psql := exec.Command("psql", fmt.Sprintf("postgres://localhost:%d/postgres", port))
	psql.Stdout = os.Stdout
	psql.Stdin = os.Stdin
	psql.Stderr = os.Stderr
	if err := psql.Run(); err != nil {
		fail("%v", err)
	}
}

var views = []string{
	// props
	`
	create view props as
	select distinct on (date, transaction)
	date, transaction_description
	from entries
	where account[1] = '支出'
	and account[2] in (
		'数码',
		'物品',
		'衣物服饰',
		'消耗品',
		'保健品',
		'书籍',
		'药物',
		'性用品'
	)
	order by date desc, transaction
	`,

	// consumables
	`
	create view consumables as
	select distinct on (date, transaction)
	date, transaction_description
	from entries
	where account[1] = '支出'
	and account[2] in (
		'饮食',
		'消耗品',
		'药物',
		'保健品'
	)
	order by date desc, transaction
	`,

	// monthly
	`
	create view monthly as
	select

	to_char(date_trunc('month', date), 'YYYY-MM') as month,

	COALESCE(
		'支出' || currency || (sum(amount) filter (where account[1] = '支出'))::numeric(20,2)::text
		|| E'：\n'
		|| (
			select string_agg(
				currency || amount::numeric(20,2)::text || ' ' || account,
				E'\n'
				order by amount desc
			) from (
				select account[2] as account, currency, sum(amount) as amount
				from unnest(
					array_agg(id) filter (where account[1] = '支出')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as expenses,

	COALESCE(
		'收入' || currency || (sum(amount) filter (where account[1] = '收入'))::numeric(20,2)::text
		|| E'：\n'
		|| (
			select string_agg(
				currency || amount::numeric(20, 2)::text || ' ' || account,
				E'\n'
				order by amount asc
			) from (
				select account[2] as account, currency, sum(amount) as amount
				from unnest(
					array_agg(id) filter (where account[1] = '收入')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as income,

	COALESCE(
		'净资产' || currency || (
			-sum(amount) filter (where account[1] = '收入')
			-
			sum(amount) filter (where account[1] = '支出')
		)::numeric(20,2)::text,
		'-'
	) || E'\n' as net_income,

	COALESCE(
		E'资产：\n' || (
			select string_agg(
				currency
				|| amount::numeric(20, 2)::text || ' ' || account
				|| E'\n'
				|| '= 增' || pos_amount::numeric(20, 2)::text
				|| ' 减' || neg_amount::numeric(20, 2)::text,
				E'\n'
				order by amount desc
			) from (
				select account[2] as account, currency, sum(amount) as amount,
				COALESCE(sum(amount) filter (where amount >= 0), 0) as pos_amount,
				COALESCE(-sum(amount) filter (where amount < 0), 0) as neg_amount
				from unnest(
					array_agg(id) filter (where account[1] = '资产')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as equity,

	COALESCE(
		E'负债：\n' || (
			select string_agg(
				currency
				|| amount::numeric(20, 2)::text || ' ' || account
				|| E'\n'
				|| '= 还' || pos_amount::numeric(20, 2)::text
				|| ' 借' || neg_amount::numeric(20, 2)::text,
				E'\n'
				order by amount asc
			) from (
				select account[2] as account, currency, sum(amount) as amount,
				COALESCE(sum(amount) filter (where amount >= 0), 0) as pos_amount,
				COALESCE(-sum(amount) filter (where amount < 0), 0) as neg_amount
				from unnest(
					array_agg(id) filter (where account[1] = '负债')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as liability

	from
	entries

	group by date_trunc('month', date), currency
	order by month desc, currency asc
	`,

	// this year expenses
	`
	create view this_year_expenses as
	select
	currency, sum(amount), account[2],
	jsonb_pretty(jsonb_agg(
			to_char(date, 'YYYY-MM-DD')
			|| ' '
			|| currency
			|| amount::numeric(10,2)
			|| ' '
			|| transaction_description
			order by amount desc, date desc
	))
	from entries
	where extract(year from date) = extract(year from now())
	and account[1] = '支出'
	group by account[2],currency
	order by sum desc
	`,

	//
}
<commit_msg>add generic interval statistics views<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"github.com/jmoiron/sqlx"
	"github.com/lib/pq"
)

func sqlInterface(
	rootAccount *Account,
	transactions []*Transaction,
) {

	dbDir := filepath.Join(os.TempDir(), fmt.Sprintf("keep-%d", rand.Int63()))
	if out, err := exec.Command("initdb", "-D", dbDir).CombinedOutput(); err != nil {
		fail("%v: %s", err, out)
	}
	pt("db dir: %s\n", dbDir)
	defer exec.Command("rm", "-rf", dbDir).Run()

	port := 10000 + rand.Intn(50000)
	conf := `
		port = ` + fmt.Sprintf("%d", port) + `
	`
	if err := ioutil.WriteFile(filepath.Join(dbDir, "postgresql.conf"), []byte(conf), 0644); err != nil {
		fail("%v", err)
	}
	c := exec.Command("postgres", "-D", dbDir)
	c.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	if err := c.Start(); err != nil {
		fail("%v", err)
	}
	defer syscall.Kill(-c.Process.Pid, syscall.SIGKILL)
	time.Sleep(time.Second)
	pt("db started\n")

	db, err := sqlx.Open("postgres", fmt.Sprintf("postgres://localhost:%d/postgres?sslmode=disable", port))
	if err != nil {
		fail("%v", err)
	}
	defer db.Close()
	tx := db.MustBegin()
	if _, err := tx.Exec(`
		CREATE TABLE entries (
			id bigserial primary key,
			transaction bigint,
			transaction_description text,
			date timestamp with time zone,
			account text[],
			currency text,
			amount numeric,
			description text
		)
		`,
	); err != nil {
		fail("%v", err)
	}
	for _, view := range views {
		if _, err := tx.Exec(view); err != nil {
			fail("%v", err)
		}
	}
	for tid, transaction := range transactions {
		for _, entry := range transaction.Entries {
			if _, err := tx.Exec(`
				INSERT INTO entries
				(
					transaction, transaction_description,
					date, account, currency, amount, description
				)
				VALUES (
					$1, $2,
					$3, $4, $5, $6::numeric, $7
				)
				`,
				tid,
				transaction.Description,
				entry.Time,
				func() (ret pq.StringArray) {
					acc := entry.Account
					for acc != rootAccount {
						ret = append(ret, acc.Name)
						acc = acc.Parent
					}
					for i := len(ret)/2 - 1; i >= 0; i-- {
						j := len(ret) - 1 - i
						ret[i], ret[j] = ret[j], ret[i]
					}
					return
				}(),
				entry.Currency,
				entry.Amount.FloatString(3),
				entry.Description,
			); err != nil {
				fail("%v", err)
			}
		}
	}
	if err := tx.Commit(); err != nil {
		fail("%v", err)
	}
	pt("data loaded\n")

	sigs := make(chan os.Signal)
	go func() {
		for {
			<-sigs
		}
	}()
	signal.Notify(sigs, os.Interrupt)

	psql := exec.Command("psql", fmt.Sprintf("postgres://localhost:%d/postgres", port))
	psql.Stdout = os.Stdout
	psql.Stdin = os.Stdin
	psql.Stderr = os.Stderr
	if err := psql.Run(); err != nil {
		fail("%v", err)
	}
}

var views = []string{
	// props
	`
	create view props as
	select distinct on (date, transaction)
	date, transaction_description
	from entries
	where account[1] = '支出'
	and account[2] in (
		'数码',
		'物品',
		'衣物服饰',
		'消耗品',
		'保健品',
		'书籍',
		'药物',
		'性用品'
	)
	order by date desc, transaction
	`,

	// consumables
	`
	create view consumables as
	select distinct on (date, transaction)
	date, transaction_description
	from entries
	where account[1] = '支出'
	and account[2] in (
		'饮食',
		'消耗品',
		'药物',
		'保健品'
	)
	order by date desc, transaction
	`,

	// monthly
	"create view monthly as" + intervalStat("date_trunc('month', date)"),

	// yearly
	"create view yearly as" + intervalStat("date_trunc('year', date)"),

	// seasonally
	"create view seasonally as" + intervalStat("date_trunc('year', date) + interval '3 month' * (extract(month from date)::int / 3)"),

	// this year expenses
	`
	create view this_year_expenses as
	select
	currency, sum(amount), account[2],
	jsonb_pretty(jsonb_agg(
			to_char(date, 'YYYY-MM-DD')
			|| ' '
			|| currency
			|| amount::numeric(10,2)
			|| ' '
			|| transaction_description
			order by amount desc, date desc
	))
	from entries
	where extract(year from date) = extract(year from now())
	and account[1] = '支出'
	group by account[2],currency
	order by sum desc
	`,

	//
}

func intervalStat(groupBy string) string {
	return `
	select
	to_char(` + groupBy + `, 'YYYY-MM') as span,

	COALESCE(
		'支出' || currency || (sum(amount) filter (where account[1] = '支出'))::numeric(20,2)::text
		|| E'：\n'
		|| (
			select string_agg(
				currency || amount::numeric(20,2)::text || ' ' || account,
				E'\n'
				order by amount desc
			) from (
				select account[2] as account, currency, sum(amount) as amount
				from unnest(
					array_agg(id) filter (where account[1] = '支出')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as expenses,

	COALESCE(
		'收入' || currency || (sum(amount) filter (where account[1] = '收入'))::numeric(20,2)::text
		|| E'：\n'
		|| (
			select string_agg(
				currency || amount::numeric(20, 2)::text || ' ' || account,
				E'\n'
				order by amount asc
			) from (
				select account[2] as account, currency, sum(amount) as amount
				from unnest(
					array_agg(id) filter (where account[1] = '收入')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as income,

	COALESCE(
		'净资产' || currency || (
			-sum(amount) filter (where account[1] = '收入')
			-
			sum(amount) filter (where account[1] = '支出')
		)::numeric(20,2)::text,
		'-'
	) || E'\n' as net_income,

	COALESCE(
		E'资产：\n' || (
			select string_agg(
				currency
				|| amount::numeric(20, 2)::text || ' ' || account
				|| E'\n'
				|| '= 增' || pos_amount::numeric(20, 2)::text
				|| ' 减' || neg_amount::numeric(20, 2)::text,
				E'\n'
				order by amount desc
			) from (
				select account[2] as account, currency, sum(amount) as amount,
				COALESCE(sum(amount) filter (where amount >= 0), 0) as pos_amount,
				COALESCE(-sum(amount) filter (where amount < 0), 0) as neg_amount
				from unnest(
					array_agg(id) filter (where account[1] = '资产')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as equity,

	COALESCE(
		E'负债：\n' || (
			select string_agg(
				currency
				|| amount::numeric(20, 2)::text || ' ' || account
				|| E'\n'
				|| '= 还' || pos_amount::numeric(20, 2)::text
				|| ' 借' || neg_amount::numeric(20, 2)::text,
				E'\n'
				order by amount asc
			) from (
				select account[2] as account, currency, sum(amount) as amount,
				COALESCE(sum(amount) filter (where amount >= 0), 0) as pos_amount,
				COALESCE(-sum(amount) filter (where amount < 0), 0) as neg_amount
				from unnest(
					array_agg(id) filter (where account[1] = '负债')
				) as id
				join entries e2 using (id)
				group by account[2], currency
			) t0
		),
		'-'
	) || E'\n' as liability

	from
	entries

	group by ` + groupBy + `, currency
	order by span desc, currency asc
	`
}
<|endoftext|>


<commit_before>package sup

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/user"
	"strings"
	"sync"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// Client is a wrapper over the SSH connection/sessions.
type SSHClient struct {
	Conn         *ssh.Client
	Sess         *ssh.Session
	User         string
	Host         string
	RemoteStdin  io.WriteCloser
	RemoteStdout io.Reader
	RemoteStderr io.Reader
	ConnOpened   bool
	SessOpened   bool
	Running      bool
	Env          string //export FOO="bar"; export BAR="baz";
}

type ErrConnect struct {
	User   string
	Host   string
	Reason string
}

func (e ErrConnect) Error() string {
	return fmt.Sprintf(`Connect("%v@%v"): %v`, e.User, e.Host, e.Reason)
}

// parseHost parses and normalizes <user>@<host:port> from a given string.
func (c *SSHClient) parseHost(host string) error {
	c.Host = host

	// Remove extra "ssh://" schema
	if len(c.Host) > 6 && c.Host[:6] == "ssh://" {
		c.Host = c.Host[6:]
	}

	if at := strings.Index(c.Host, "@"); at != -1 {
		c.User = c.Host[:at]
		c.Host = c.Host[at+1:]
	}

	// Add default user, if not set
	if c.User == "" {
		u, err := user.Current()
		if err != nil {
			return err
		}
		c.User = u.Username
	}

	if strings.Index(c.Host, "/") != -1 {
		return ErrConnect{c.User, c.Host, "unexpected slash in the host URL"}
	}

	// Add default port, if not set
	if strings.Index(c.Host, ":") == -1 {
		c.Host += ":22"
	}

	return nil
}

var initAuthMethodOnce sync.Once
var authMethod ssh.AuthMethod

// initAuthMethod initiates SSH authentication method.
func initAuthMethod() {
	var signers []ssh.Signer

	// If there's a running SSH Agent, try to use its Private keys.
	sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err == nil {
		agent := agent.NewClient(sock)
		signers, _ = agent.Signers()
	}

	// Try to read user's SSH private keys form the standard paths.
	files := []string{
		os.Getenv("HOME") + "/.ssh/id_rsa",
		os.Getenv("HOME") + "/.ssh/id_dsa",
	}
	for _, file := range files {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			continue
		}
		signer, err := ssh.ParsePrivateKey(data)
		if err != nil {
			continue
		}
		signers = append(signers, signer)

	}
	authMethod = ssh.PublicKeys(signers...)
}

// SSHDialFunc can dial an ssh server and return a client
type SSHDialFunc func(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error)

// Connect creates SSH connection to a specified host.
// It expects the host of the form "[ssh://]host[:port]".
func (c *SSHClient) Connect(host string) error {
	return c.ConnectWith(host, ssh.Dial)
}

// ConnectWith creates a SSH connection to a specified host. It will use dialer to establish the
// connection.
// TODO: Split Signers to its own method.
func (c *SSHClient) ConnectWith(host string, dialer SSHDialFunc) error {

	fmt.Println("connecting", host)

	if c.ConnOpened {
		return fmt.Errorf("Already connected")
	}

	initAuthMethodOnce.Do(initAuthMethod)

	err := c.parseHost(host)
	if err != nil {
		return err
	}

	config := &ssh.ClientConfig{
		User: c.User,
		Auth: []ssh.AuthMethod{
			authMethod,
		},
	}

	c.Conn, err = dialer("tcp", c.Host, config)
	if err != nil {
		return ErrConnect{c.User, c.Host, err.Error()}
	}

	c.ConnOpened = true

	return nil
}

// Run runs the task.Run command remotely on c.Host.
func (c *SSHClient) Run(task *Task) error {
	if c.Running {
		return fmt.Errorf("Session already running")
	}
	if c.SessOpened {
		return fmt.Errorf("Session already connected")
	}

	sess, err := c.Conn.NewSession()
	if err != nil {
		return err
	}

	c.RemoteStdin, err = sess.StdinPipe()
	if err != nil {
		return err
	}

	c.RemoteStdout, err = sess.StdoutPipe()
	if err != nil {
		return err
	}

	c.RemoteStderr, err = sess.StderrPipe()
	if err != nil {
		return err
	}

	c.Sess = sess
	c.SessOpened = true

	// Start the remote command.
	if err := c.Sess.Start(c.Env + "set -x;" + task.Run); err != nil {
		return ErrTask{task, err.Error()}
	}

	c.Running = true
	return nil
}

// Wait waits until the remote command finishes and exits.
// It closes the SSH session.
func (c *SSHClient) Wait() error {
	if !c.Running {
		return fmt.Errorf("Trying to wait on stopped session")
	}

	err := c.Sess.Wait()
	c.Sess.Close()
	c.Running = false
	c.SessOpened = false

	return err
}

// DialThrough will create a new connection from the ssh server sc is connected to. DialThrough is an SSHDialer.
func (sc *SSHClient) DialThrough(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
	fmt.Println("Dialing", net, addr)
	conn, err := sc.Conn.Dial(net, addr)
	if err != nil {
		return nil, err
	}
	c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
	if err != nil {
		return nil, err
	}
	return ssh.NewClient(c, chans, reqs), nil

}

// Close closes the underlying SSH connection and session.
func (c *SSHClient) Close() error {
	if c.SessOpened {
		c.Sess.Close()
		c.SessOpened = false
	}
	if !c.ConnOpened {
		return fmt.Errorf("Trying to close the already closed connection")
	}

	err := c.Conn.Close()
	c.ConnOpened = false
	c.Running = false

	return err
}

func (c *SSHClient) Prefix() string {
	return c.User + "@" + c.Host
}

func (c *SSHClient) Write(p []byte) (n int, err error) {
	return c.RemoteStdin.Write(p)
}

func (c *SSHClient) WriteClose() error {
	return c.RemoteStdin.Close()
}
<commit_msg>clean out debug prints<commit_after>package sup

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/user"
	"strings"
	"sync"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// Client is a wrapper over the SSH connection/sessions.
type SSHClient struct {
	Conn         *ssh.Client
	Sess         *ssh.Session
	User         string
	Host         string
	RemoteStdin  io.WriteCloser
	RemoteStdout io.Reader
	RemoteStderr io.Reader
	ConnOpened   bool
	SessOpened   bool
	Running      bool
	Env          string //export FOO="bar"; export BAR="baz";
}

type ErrConnect struct {
	User   string
	Host   string
	Reason string
}

func (e ErrConnect) Error() string {
	return fmt.Sprintf(`Connect("%v@%v"): %v`, e.User, e.Host, e.Reason)
}

// parseHost parses and normalizes <user>@<host:port> from a given string.
func (c *SSHClient) parseHost(host string) error {
	c.Host = host

	// Remove extra "ssh://" schema
	if len(c.Host) > 6 && c.Host[:6] == "ssh://" {
		c.Host = c.Host[6:]
	}

	if at := strings.Index(c.Host, "@"); at != -1 {
		c.User = c.Host[:at]
		c.Host = c.Host[at+1:]
	}

	// Add default user, if not set
	if c.User == "" {
		u, err := user.Current()
		if err != nil {
			return err
		}
		c.User = u.Username
	}

	if strings.Index(c.Host, "/") != -1 {
		return ErrConnect{c.User, c.Host, "unexpected slash in the host URL"}
	}

	// Add default port, if not set
	if strings.Index(c.Host, ":") == -1 {
		c.Host += ":22"
	}

	return nil
}

var initAuthMethodOnce sync.Once
var authMethod ssh.AuthMethod

// initAuthMethod initiates SSH authentication method.
func initAuthMethod() {
	var signers []ssh.Signer

	// If there's a running SSH Agent, try to use its Private keys.
	sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err == nil {
		agent := agent.NewClient(sock)
		signers, _ = agent.Signers()
	}

	// Try to read user's SSH private keys form the standard paths.
	files := []string{
		os.Getenv("HOME") + "/.ssh/id_rsa",
		os.Getenv("HOME") + "/.ssh/id_dsa",
	}
	for _, file := range files {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			continue
		}
		signer, err := ssh.ParsePrivateKey(data)
		if err != nil {
			continue
		}
		signers = append(signers, signer)

	}
	authMethod = ssh.PublicKeys(signers...)
}

// SSHDialFunc can dial an ssh server and return a client
type SSHDialFunc func(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error)

// Connect creates SSH connection to a specified host.
// It expects the host of the form "[ssh://]host[:port]".
func (c *SSHClient) Connect(host string) error {
	return c.ConnectWith(host, ssh.Dial)
}

// ConnectWith creates a SSH connection to a specified host. It will use dialer to establish the
// connection.
// TODO: Split Signers to its own method.
func (c *SSHClient) ConnectWith(host string, dialer SSHDialFunc) error {

	if c.ConnOpened {
		return fmt.Errorf("Already connected")
	}

	initAuthMethodOnce.Do(initAuthMethod)

	err := c.parseHost(host)
	if err != nil {
		return err
	}

	config := &ssh.ClientConfig{
		User: c.User,
		Auth: []ssh.AuthMethod{
			authMethod,
		},
	}

	c.Conn, err = dialer("tcp", c.Host, config)
	if err != nil {
		return ErrConnect{c.User, c.Host, err.Error()}
	}

	c.ConnOpened = true

	return nil
}

// Run runs the task.Run command remotely on c.Host.
func (c *SSHClient) Run(task *Task) error {
	if c.Running {
		return fmt.Errorf("Session already running")
	}
	if c.SessOpened {
		return fmt.Errorf("Session already connected")
	}

	sess, err := c.Conn.NewSession()
	if err != nil {
		return err
	}

	c.RemoteStdin, err = sess.StdinPipe()
	if err != nil {
		return err
	}

	c.RemoteStdout, err = sess.StdoutPipe()
	if err != nil {
		return err
	}

	c.RemoteStderr, err = sess.StderrPipe()
	if err != nil {
		return err
	}

	c.Sess = sess
	c.SessOpened = true

	// Start the remote command.
	if err := c.Sess.Start(c.Env + "set -x;" + task.Run); err != nil {
		return ErrTask{task, err.Error()}
	}

	c.Running = true
	return nil
}

// Wait waits until the remote command finishes and exits.
// It closes the SSH session.
func (c *SSHClient) Wait() error {
	if !c.Running {
		return fmt.Errorf("Trying to wait on stopped session")
	}

	err := c.Sess.Wait()
	c.Sess.Close()
	c.Running = false
	c.SessOpened = false

	return err
}

// DialThrough will create a new connection from the ssh server sc is connected to. DialThrough is an SSHDialer.
func (sc *SSHClient) DialThrough(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
	conn, err := sc.Conn.Dial(net, addr)
	if err != nil {
		return nil, err
	}
	c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
	if err != nil {
		return nil, err
	}
	return ssh.NewClient(c, chans, reqs), nil

}

// Close closes the underlying SSH connection and session.
func (c *SSHClient) Close() error {
	if c.SessOpened {
		c.Sess.Close()
		c.SessOpened = false
	}
	if !c.ConnOpened {
		return fmt.Errorf("Trying to close the already closed connection")
	}

	err := c.Conn.Close()
	c.ConnOpened = false
	c.Running = false

	return err
}

func (c *SSHClient) Prefix() string {
	return c.User + "@" + c.Host
}

func (c *SSHClient) Write(p []byte) (n int, err error) {
	return c.RemoteStdin.Write(p)
}

func (c *SSHClient) WriteClose() error {
	return c.RemoteStdin.Close()
}
<|endoftext|>


<commit_before>// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The content is borrowed from Docker's own source code to provide a simple
// tls based dialer

package docker

import (
	"crypto/tls"
	"errors"
	"net"
	"strings"
	"time"
)

type tlsClientCon struct {
	*tls.Conn
	rawConn net.Conn
}

func (c *tlsClientCon) CloseWrite() error {
	// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
	// on its underlying connection.
	if cwc, ok := c.rawConn.(interface {
		CloseWrite() error
	}); ok {
		return cwc.CloseWrite()
	}
	return nil
}

func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
	// We want the Timeout and Deadline values from dialer to cover the
	// whole process: TCP connection and TLS handshake. This means that we
	// also need to start our own timers now.
	timeout := dialer.Timeout

	if !dialer.Deadline.IsZero() {
		deadlineTimeout := time.Until(dialer.Deadline)
		if timeout == 0 || deadlineTimeout < timeout {
			timeout = deadlineTimeout
		}
	}

	var errChannel chan error

	if timeout != 0 {
		errChannel = make(chan error, 2)
		time.AfterFunc(timeout, func() {
			errChannel <- errors.New("")
		})
	}

	rawConn, err := dialer.Dial(network, addr)
	if err != nil {
		return nil, err
	}

	colonPos := strings.LastIndex(addr, ":")
	if colonPos == -1 {
		colonPos = len(addr)
	}
	hostname := addr[:colonPos]

	// If no ServerName is set, infer the ServerName
	// from the hostname we're connecting to.
	if config.ServerName == "" {
		// Make a copy to avoid polluting argument or default.
		config = copyTLSConfig(config)
		config.ServerName = hostname
	}

	conn := tls.Client(rawConn, config)

	if timeout == 0 {
		err = conn.Handshake()
	} else {
		go func() {
			errChannel <- conn.Handshake()
		}()

		err = <-errChannel
	}

	if err != nil {
		rawConn.Close()
		return nil, err
	}

	// This is Docker difference with standard's crypto/tls package: returned a
	// wrapper which holds both the TLS and raw connections.
	return &tlsClientCon{conn, rawConn}, nil
}

// this exists to silent an error message in go vet
func copyTLSConfig(cfg *tls.Config) *tls.Config {
	return &tls.Config{
		Certificates:             cfg.Certificates,
		CipherSuites:             cfg.CipherSuites,
		ClientAuth:               cfg.ClientAuth,
		ClientCAs:                cfg.ClientCAs,
		ClientSessionCache:       cfg.ClientSessionCache,
		CurvePreferences:         cfg.CurvePreferences,
		InsecureSkipVerify:       cfg.InsecureSkipVerify, //nolint:gosec
		MaxVersion:               cfg.MaxVersion,
		MinVersion:               cfg.MinVersion,
		NameToCertificate:        cfg.NameToCertificate,
		NextProtos:               cfg.NextProtos,
		PreferServerCipherSuites: cfg.PreferServerCipherSuites,
		Rand:                     cfg.Rand,
		RootCAs:                  cfg.RootCAs,
		ServerName:               cfg.ServerName,
		SessionTicketKey:         cfg.SessionTicketKey,
		SessionTicketsDisabled:   cfg.SessionTicketsDisabled,
	}
}
<commit_msg>tls: drop NameToCertificate<commit_after>// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The content is borrowed from Docker's own source code to provide a simple
// tls based dialer

package docker

import (
	"crypto/tls"
	"errors"
	"net"
	"strings"
	"time"
)

type tlsClientCon struct {
	*tls.Conn
	rawConn net.Conn
}

func (c *tlsClientCon) CloseWrite() error {
	// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
	// on its underlying connection.
	if cwc, ok := c.rawConn.(interface {
		CloseWrite() error
	}); ok {
		return cwc.CloseWrite()
	}
	return nil
}

func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
	// We want the Timeout and Deadline values from dialer to cover the
	// whole process: TCP connection and TLS handshake. This means that we
	// also need to start our own timers now.
	timeout := dialer.Timeout

	if !dialer.Deadline.IsZero() {
		deadlineTimeout := time.Until(dialer.Deadline)
		if timeout == 0 || deadlineTimeout < timeout {
			timeout = deadlineTimeout
		}
	}

	var errChannel chan error

	if timeout != 0 {
		errChannel = make(chan error, 2)
		time.AfterFunc(timeout, func() {
			errChannel <- errors.New("")
		})
	}

	rawConn, err := dialer.Dial(network, addr)
	if err != nil {
		return nil, err
	}

	colonPos := strings.LastIndex(addr, ":")
	if colonPos == -1 {
		colonPos = len(addr)
	}
	hostname := addr[:colonPos]

	// If no ServerName is set, infer the ServerName
	// from the hostname we're connecting to.
	if config.ServerName == "" {
		// Make a copy to avoid polluting argument or default.
		config = copyTLSConfig(config)
		config.ServerName = hostname
	}

	conn := tls.Client(rawConn, config)

	if timeout == 0 {
		err = conn.Handshake()
	} else {
		go func() {
			errChannel <- conn.Handshake()
		}()

		err = <-errChannel
	}

	if err != nil {
		rawConn.Close()
		return nil, err
	}

	// This is Docker difference with standard's crypto/tls package: returned a
	// wrapper which holds both the TLS and raw connections.
	return &tlsClientCon{conn, rawConn}, nil
}

// this exists to silent an error message in go vet
func copyTLSConfig(cfg *tls.Config) *tls.Config {
	return &tls.Config{
		Certificates:             cfg.Certificates,
		CipherSuites:             cfg.CipherSuites,
		ClientAuth:               cfg.ClientAuth,
		ClientCAs:                cfg.ClientCAs,
		ClientSessionCache:       cfg.ClientSessionCache,
		CurvePreferences:         cfg.CurvePreferences,
		InsecureSkipVerify:       cfg.InsecureSkipVerify, //nolint:gosec
		MaxVersion:               cfg.MaxVersion,
		MinVersion:               cfg.MinVersion,
		NextProtos:               cfg.NextProtos,
		PreferServerCipherSuites: cfg.PreferServerCipherSuites,
		Rand:                     cfg.Rand,
		RootCAs:                  cfg.RootCAs,
		ServerName:               cfg.ServerName,
		SessionTicketKey:         cfg.SessionTicketKey,
		SessionTicketsDisabled:   cfg.SessionTicketsDisabled,
	}
}
<|endoftext|>


<commit_before>package tty

import (
	"os"
	"strings"
	"unicode"
)

func Open() (*TTY, error) {
	return open()
}

func (tty *TTY) Buffered() bool {
	return tty.buffered()
}

func (tty *TTY) ReadRune() (rune, error) {
	return tty.readRune()
}

func (tty *TTY) Close() error {
	return tty.close()
}

func (tty *TTY) Size() (int, int, error) {
	return tty.size()
}

func (tty *TTY) Input() *os.File {
	return tty.input()
}

func (tty *TTY) Output() *os.File {
	return tty.output()
}

func (tty *TTY) readPassword() (string, error) {
	rs := []rune{}
loop:
	for {
		r, err := tty.readRune()
		if err != nil {
			return "", err
		}
		switch r {
		case 13:
			break loop
		case 8:
			if len(rs) > 0 {
				rs = rs[:len(rs)-1]
				tty.Output().WriteString("\b \b")
			}
		default:
			if unicode.IsPrint(r) {
				rs = append(rs, r)
				tty.Output().WriteString("*")
			}
		}
	}
	return string(rs), nil
}

func (tty *TTY) ReadPassword() (string, error) {
	defer tty.Output().WriteString("\n")
	return tty.readPassword()
}

func (tty *TTY) ReadPasswordClear() (string, error) {
	s, err := tty.readPassword()
	tty.Output().WriteString(
		strings.Repeat("\b", len(s)) +
			strings.Repeat(" ", len(s)) +
			strings.Repeat("\b", len(s)))
	return s, err
}
<commit_msg>Add ReadString<commit_after>package tty

import (
	"os"
	"strings"
	"unicode"
)

func Open() (*TTY, error) {
	return open()
}

func (tty *TTY) Buffered() bool {
	return tty.buffered()
}

func (tty *TTY) ReadRune() (rune, error) {
	return tty.readRune()
}

func (tty *TTY) Close() error {
	return tty.close()
}

func (tty *TTY) Size() (int, int, error) {
	return tty.size()
}

func (tty *TTY) Input() *os.File {
	return tty.input()
}

func (tty *TTY) Output() *os.File {
	return tty.output()
}

func (tty *TTY) readString(isPassword bool) (string, error) {
	rs := []rune{}
loop:
	for {
		r, err := tty.readRune()
		if err != nil {
			return "", err
		}
		switch r {
		case 13:
			break loop
		case 8:
			if len(rs) > 0 {
				rs = rs[:len(rs)-1]
				tty.Output().WriteString("\b \b")
			}
		default:
			if unicode.IsPrint(r) {
				rs = append(rs, r)
				if isPassword {
					tty.Output().WriteString("*")
				} else {
					tty.Output().WriteString(string(r))
				}
			}
		}
	}
	return string(rs), nil
}

func (tty *TTY) ReadString() (string, error) {
	defer tty.Output().WriteString("\n")
	return tty.readString(false)
}

func (tty *TTY) ReadPassword() (string, error) {
	defer tty.Output().WriteString("\n")
	return tty.readString(true)
}

func (tty *TTY) ReadPasswordClear() (string, error) {
	s, err := tty.readString(true)
	tty.Output().WriteString(
		strings.Repeat("\b", len(s)) +
			strings.Repeat(" ", len(s)) +
			strings.Repeat("\b", len(s)))
	return s, err
}
<|endoftext|>


<commit_before>// Package ranksel provides a bit vector
// that can answer rank and select queries.
package ranksel

import (
	"bytes"
	"encoding/gob"
	"unsafe"

	"github.com/robskie/bit"
)

// Options determines the size of the rank and
// select sampling block. Lower values translates
// to faster operations but results in larger size.
type Options struct {
	// Sr is the rank sampling block size.
	// This represents the number of bits in
	// each rank sampling block. Default is 1024.
	Sr int

	// Ss is the select sampling block size.
	// This represents the number of 1s in each
	// select sampling block. Default is 8192.
	Ss int
}

// NewOptions creates an Options
// object with default values.
func NewOptions() *Options {
	return &Options{1024, 8192}
}

// BitVector is a bitmap with added data structure described by G. Navarro and
// E. Providel's `A Structure for Plain Bitmaps: Combined Sampling` in "Fast,
// Small, Simple Rank/Select on Bitmaps" with some minor modifications.
//
// See http://dcc.uchile.cl/~gnavarro/ps/sea12.1.pdf for more details.
type BitVector struct {
	bits *bit.Array

	// ranks[i] is the number of 1s
	// from 0 to index (i*sr)-1
	ranks []int

	// indices[i] points to the
	// beginning of the uint64 (LSB)
	// that contains the (i*ss)+1th
	// set bit.
	indices []int

	popcount int

	opts *Options
}

// NewBitVector creates a new BitVector.
func NewBitVector(opts *Options) *BitVector {
	if opts == nil {
		opts = NewOptions()
	}

	b := bit.NewArray(0)
	rs := make([]int, 1)
	idx := make([]int, 1)

	return &BitVector{
		bits:    b,
		ranks:   rs,
		indices: idx,
		opts:    opts,
	}
}

// Add appends the bits given its size to the vector.
func (v *BitVector) Add(bits uint64, size int) {
	if size <= 0 || size > 64 {
		panic("ranksel: bit size must be in range [1,64]")
	}

	// Add bits
	v.bits.Add(bits, size)
	vlength := v.bits.Len()

	// Increment popcount
	popcnt := bit.PopCount(bits)
	v.popcount += popcnt

	// Update rank sampling
	lenranks := len(v.ranks)
	overflow := vlength - (lenranks * v.opts.Sr)
	if overflow > 0 {
		v.ranks = append(v.ranks, 0)

		rank := bit.Rank(bits, size-overflow-1)
		v.ranks[lenranks] = v.popcount - popcnt + rank
	}

	// Update select sampling
	lenidx := len(v.indices)
	overflow = v.popcount - (lenidx * v.opts.Ss)
	if overflow > 0 {
		v.indices = append(v.indices, 0)

		sel := bit.Select(bits, popcnt-overflow+1)
		v.indices[lenidx] = (vlength - size + sel) & ^0x3F
	}
}

// Get returns the uint64 representation of
// bits starting from index idx given the bit size.
func (v *BitVector) Get(idx, size int) uint64 {
	return v.bits.Get(idx, size)
}

// Bit returns the bit value at index i.
func (v *BitVector) Bit(i int) uint {
	if i >= v.bits.Len() {
		panic("ranksel: index out of range")
	}

	vbits := v.bits.Bits()
	if vbits[i>>6]&(1<<uint(i&63)) != 0 {
		return 1
	}
	return 0
}

// Rank1 counts the number of 1s from
// the beginning up to the ith index.
func (v *BitVector) Rank1(i int) int {
	if i >= v.bits.Len() {
		panic("ranksel: index out of range")
	}

	j := i / v.opts.Sr
	ip := (j * v.opts.Sr) >> 6
	rank := v.ranks[j]

	aidx := i & 63
	bidx := i >> 6
	vbits := v.bits.Bits()
	for _, b := range vbits[ip:bidx] {
		rank += bit.PopCount(b)
	}

	return rank + bit.Rank(vbits[bidx], aidx)
}

// Rank0 counts the number of 0s from
// the beginning up to the ith index.
func (v *BitVector) Rank0(i int) int {
	return i - v.Rank1(i) + 1
}

// Select1 returns the index of the ith set bit.
// Panics if i is zero or greater than the number
// of set bits.
func (v *BitVector) Select1(i int) int {
	if i > v.popcount {
		panic("ranksel: input exceeds number of 1s")
	} else if i == 0 {
		panic("ranksel: input must be greater than 0")
	}

	j := (i - 1) / v.opts.Ss
	q := v.indices[j] / v.opts.Sr

	k := 0
	r := 0
	rq := v.ranks[q:]
	for k, r = range rq {
		if r >= i {
			k--
			break
		}
	}

	idx := 0
	rank := rq[k]
	vbits := v.bits.Bits()
	aidx := ((q + k) * v.opts.Sr) >> 6
	for ii, b := range vbits[aidx:] {
		rank += bit.PopCount(b)

		if rank >= i {
			overflow := rank - i
			popcnt := bit.PopCount(b)

			idx = (aidx + ii) << 6
			idx += bit.Select(b, popcnt-overflow)

			break
		}
	}

	return idx
}

// Select0 returns the index of the ith zero. Panics
// if i is zero or greater than the number of zeroes.
// This is slower than Select1 in most cases.
func (v *BitVector) Select0(i int) int {
	if i > (v.bits.Len() - v.popcount) {
		panic("ranksel: input exceeds number of 0s")
	} else if i == 0 {
		panic("ranksel: input must be greater than 0")
	}

	// Do a binary search on the rank samples to find
	// the largest rank sample that is less than i.
	// From https://en.wikipedia.org/wiki/Binary_search_algorithm
	imin := 1
	imax := len(v.ranks) - 1
	for imin < imax {
		imid := imin + ((imax - imin) >> 1)

		rmid0 := (imid * v.opts.Sr) - v.ranks[imid]
		if rmid0 < i {
			imin = imid + 1
		} else {
			imax = imid
		}
	}
	imin--

	idx := 0
	vbits := v.bits.Bits()
	aidx := (imin * v.opts.Sr) >> 6
	rank0 := (imin * v.opts.Sr) - v.ranks[imin]
	for ii, b := range vbits[aidx:] {
		b = ^b
		rank0 += bit.PopCount(b)

		if rank0 >= i {
			overflow := rank0 - i
			popcnt := bit.PopCount(b)

			idx = (aidx + ii) << 6
			idx += bit.Select(b, popcnt-overflow)

			break
		}
	}

	return idx
}

// GobEncode encodes this vector into gob streams.
func (v *BitVector) GobEncode() ([]byte, error) {
	buf := &bytes.Buffer{}
	enc := gob.NewEncoder(buf)

	enc.Encode(v.bits)
	enc.Encode(v.ranks)
	enc.Encode(v.indices)
	enc.Encode(v.popcount)
	enc.Encode(v.opts)

	return buf.Bytes(), nil
}

// GobDecode populates this vector from gob streams.
func (v *BitVector) GobDecode(data []byte) error {
	buf := bytes.NewReader(data)
	dec := gob.NewDecoder(buf)

	dec.Decode(v.bits)
	dec.Decode(&v.ranks)
	dec.Decode(&v.indices)
	dec.Decode(&v.popcount)
	dec.Decode(v.opts)

	return nil
}

// Len returns the number of bits stored.
func (v *BitVector) Len() int {
	return v.bits.Len()
}

// PopCount returns the total number of 1s.
func (v *BitVector) PopCount() int {
	return v.popcount
}

// Size returns the vector size in bytes.
func (v *BitVector) Size() int {
	sizeofInt := int(unsafe.Sizeof(int(0)))

	size := v.bits.Size()
	size += len(v.ranks) * sizeofInt
	size += len(v.indices) * sizeofInt

	return size
}

// String returns a hexadecimal
// string representation of the vector.
func (v *BitVector) String() string {
	return v.bits.String()
}
<commit_msg>Added error checks in gob encode/decode methods<commit_after>// Package ranksel provides a bit vector
// that can answer rank and select queries.
package ranksel

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"unsafe"

	"github.com/robskie/bit"
)

// Options determines the size of the rank and
// select sampling block. Lower values translates
// to faster operations but results in larger size.
type Options struct {
	// Sr is the rank sampling block size.
	// This represents the number of bits in
	// each rank sampling block. Default is 1024.
	Sr int

	// Ss is the select sampling block size.
	// This represents the number of 1s in each
	// select sampling block. Default is 8192.
	Ss int
}

// NewOptions creates an Options
// object with default values.
func NewOptions() *Options {
	return &Options{1024, 8192}
}

// BitVector is a bitmap with added data structure described by G. Navarro and
// E. Providel's `A Structure for Plain Bitmaps: Combined Sampling` in "Fast,
// Small, Simple Rank/Select on Bitmaps" with some minor modifications.
//
// See http://dcc.uchile.cl/~gnavarro/ps/sea12.1.pdf for more details.
type BitVector struct {
	bits *bit.Array

	// ranks[i] is the number of 1s
	// from 0 to index (i*sr)-1
	ranks []int

	// indices[i] points to the
	// beginning of the uint64 (LSB)
	// that contains the (i*ss)+1th
	// set bit.
	indices []int

	popcount int

	opts *Options
}

// NewBitVector creates a new BitVector.
func NewBitVector(opts *Options) *BitVector {
	if opts == nil {
		opts = NewOptions()
	}

	b := bit.NewArray(0)
	rs := make([]int, 1)
	idx := make([]int, 1)

	return &BitVector{
		bits:    b,
		ranks:   rs,
		indices: idx,
		opts:    opts,
	}
}

// Add appends the bits given its size to the vector.
func (v *BitVector) Add(bits uint64, size int) {
	if size <= 0 || size > 64 {
		panic("ranksel: bit size must be in range [1,64]")
	}

	// Add bits
	v.bits.Add(bits, size)
	vlength := v.bits.Len()

	// Increment popcount
	popcnt := bit.PopCount(bits)
	v.popcount += popcnt

	// Update rank sampling
	lenranks := len(v.ranks)
	overflow := vlength - (lenranks * v.opts.Sr)
	if overflow > 0 {
		v.ranks = append(v.ranks, 0)

		rank := bit.Rank(bits, size-overflow-1)
		v.ranks[lenranks] = v.popcount - popcnt + rank
	}

	// Update select sampling
	lenidx := len(v.indices)
	overflow = v.popcount - (lenidx * v.opts.Ss)
	if overflow > 0 {
		v.indices = append(v.indices, 0)

		sel := bit.Select(bits, popcnt-overflow+1)
		v.indices[lenidx] = (vlength - size + sel) & ^0x3F
	}
}

// Get returns the uint64 representation of
// bits starting from index idx given the bit size.
func (v *BitVector) Get(idx, size int) uint64 {
	return v.bits.Get(idx, size)
}

// Bit returns the bit value at index i.
func (v *BitVector) Bit(i int) uint {
	if i >= v.bits.Len() {
		panic("ranksel: index out of range")
	}

	vbits := v.bits.Bits()
	if vbits[i>>6]&(1<<uint(i&63)) != 0 {
		return 1
	}
	return 0
}

// Rank1 counts the number of 1s from
// the beginning up to the ith index.
func (v *BitVector) Rank1(i int) int {
	if i >= v.bits.Len() {
		panic("ranksel: index out of range")
	}

	j := i / v.opts.Sr
	ip := (j * v.opts.Sr) >> 6
	rank := v.ranks[j]

	aidx := i & 63
	bidx := i >> 6
	vbits := v.bits.Bits()
	for _, b := range vbits[ip:bidx] {
		rank += bit.PopCount(b)
	}

	return rank + bit.Rank(vbits[bidx], aidx)
}

// Rank0 counts the number of 0s from
// the beginning up to the ith index.
func (v *BitVector) Rank0(i int) int {
	return i - v.Rank1(i) + 1
}

// Select1 returns the index of the ith set bit.
// Panics if i is zero or greater than the number
// of set bits.
func (v *BitVector) Select1(i int) int {
	if i > v.popcount {
		panic("ranksel: input exceeds number of 1s")
	} else if i == 0 {
		panic("ranksel: input must be greater than 0")
	}

	j := (i - 1) / v.opts.Ss
	q := v.indices[j] / v.opts.Sr

	k := 0
	r := 0
	rq := v.ranks[q:]
	for k, r = range rq {
		if r >= i {
			k--
			break
		}
	}

	idx := 0
	rank := rq[k]
	vbits := v.bits.Bits()
	aidx := ((q + k) * v.opts.Sr) >> 6
	for ii, b := range vbits[aidx:] {
		rank += bit.PopCount(b)

		if rank >= i {
			overflow := rank - i
			popcnt := bit.PopCount(b)

			idx = (aidx + ii) << 6
			idx += bit.Select(b, popcnt-overflow)

			break
		}
	}

	return idx
}

// Select0 returns the index of the ith zero. Panics
// if i is zero or greater than the number of zeroes.
// This is slower than Select1 in most cases.
func (v *BitVector) Select0(i int) int {
	if i > (v.bits.Len() - v.popcount) {
		panic("ranksel: input exceeds number of 0s")
	} else if i == 0 {
		panic("ranksel: input must be greater than 0")
	}

	// Do a binary search on the rank samples to find
	// the largest rank sample that is less than i.
	// From https://en.wikipedia.org/wiki/Binary_search_algorithm
	imin := 1
	imax := len(v.ranks) - 1
	for imin < imax {
		imid := imin + ((imax - imin) >> 1)

		rmid0 := (imid * v.opts.Sr) - v.ranks[imid]
		if rmid0 < i {
			imin = imid + 1
		} else {
			imax = imid
		}
	}
	imin--

	idx := 0
	vbits := v.bits.Bits()
	aidx := (imin * v.opts.Sr) >> 6
	rank0 := (imin * v.opts.Sr) - v.ranks[imin]
	for ii, b := range vbits[aidx:] {
		b = ^b
		rank0 += bit.PopCount(b)

		if rank0 >= i {
			overflow := rank0 - i
			popcnt := bit.PopCount(b)

			idx = (aidx + ii) << 6
			idx += bit.Select(b, popcnt-overflow)

			break
		}
	}

	return idx
}

func checkErr(err ...error) error {
	for _, e := range err {
		if e != nil {
			return e
		}
	}

	return nil
}

// GobEncode encodes this vector into gob streams.
func (v *BitVector) GobEncode() ([]byte, error) {
	buf := &bytes.Buffer{}
	enc := gob.NewEncoder(buf)

	err := checkErr(
		enc.Encode(v.bits),
		enc.Encode(v.ranks),
		enc.Encode(v.indices),
		enc.Encode(v.popcount),
		enc.Encode(v.opts),
	)

	if err != nil {
		err = fmt.Errorf("ranksel: encode failed (%v)", err)
	}

	return buf.Bytes(), err
}

// GobDecode populates this vector from gob streams.
func (v *BitVector) GobDecode(data []byte) error {
	buf := bytes.NewReader(data)
	dec := gob.NewDecoder(buf)

	v.opts = NewOptions()
	v.bits = bit.NewArray(0)
	err := checkErr(
		dec.Decode(v.bits),
		dec.Decode(&v.ranks),
		dec.Decode(&v.indices),
		dec.Decode(&v.popcount),
		dec.Decode(v.opts),
	)

	if err != nil {
		err = fmt.Errorf("ranksel: decode failed (%v)", err)
	}

	return err
}

// Len returns the number of bits stored.
func (v *BitVector) Len() int {
	return v.bits.Len()
}

// PopCount returns the total number of 1s.
func (v *BitVector) PopCount() int {
	return v.popcount
}

// Size returns the vector size in bytes.
func (v *BitVector) Size() int {
	sizeofInt := int(unsafe.Sizeof(int(0)))

	size := v.bits.Size()
	size += len(v.ranks) * sizeofInt
	size += len(v.indices) * sizeofInt

	return size
}

// String returns a hexadecimal
// string representation of the vector.
func (v *BitVector) String() string {
	return v.bits.String()
}
<|endoftext|>


<commit_before>package gogadgets

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/cswank/rex"
)

type Server struct {
	host        string
	master      string
	isMaster    bool
	port        int
	prefix      string
	lg          Logger
	external    chan Message
	internal    chan Message
	id          string
	updates     map[string]Message
	seen        map[string]time.Time
	statusLock  sync.Mutex
	seenLock    sync.Mutex
	clientsLock sync.Mutex
	clients     map[string]string
}

func NewServer(host, master string, port int, lg Logger) *Server {
	var isMaster bool
	clients := map[string]string{}
	if master == "" {
		isMaster = true
	} else {
		clients = map[string]string{
			master: "",
		}
	}
	return &Server{
		master:   master,
		host:     host,
		isMaster: isMaster,
		port:     port,
		lg:       lg,
		updates:  map[string]Message{},
		id:       "server",
		external: make(chan Message),
		seen:     map[string]time.Time{},
		clients:  clients,
	}
}

func (s *Server) Start(i <-chan Message, o chan<- Message) {
	if !s.isMaster {
		go s.register()
	}
	go s.startServer()
	go s.cleanup()

	for {
		select {
		case msg := <-i:
			if (msg.Type == UPDATE || msg.Type == METHODUPDATE) && s.isMaster {
				s.statusLock.Lock()
				s.updates[msg.Sender] = msg
				s.statusLock.Unlock()
			}
			if !s.isSeen(msg) {
				s.send(msg)
			}
		case msg := <-s.external:
			s.setSeen(msg)
			o <- msg
			if s.isMaster && msg.Sender == "client" {
				s.send(msg)
			}
		}
	}
}

func (s *Server) send(msg Message) {
	s.clientsLock.Lock()
	for host, token := range s.clients {
		go s.doSend(host, msg, token)
	}
	s.clientsLock.Unlock()
}

func (s *Server) doSend(host string, msg Message, token string) {
	msg.Host = s.host
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.Encode(msg)
	req, err := http.NewRequest("POST", host, &buf)
	s.clientsLock.Lock()
	defer s.clientsLock.Unlock()
	if err != nil {
		delete(s.clients, host)
		log.Printf("error posting to quimby host: %s, err: %v\n", host, err)
		return
	}
	if len(token) > 0 {
		req.Header.Add("Authorization", token)
	}
	r, err := http.DefaultClient.Do(req)
	if r != nil {
		r.Body.Close()
	}

	if err != nil || r.StatusCode != http.StatusOK {
		delete(s.clients, host)
	}
}

func (s *Server) setSeen(msg Message) {
	s.seenLock.Lock()
	s.seen[msg.UUID] = time.Now()
	s.seenLock.Unlock()
}

func (s *Server) isSeen(msg Message) bool {
	s.seenLock.Lock()
	_, ok := s.seen[msg.UUID]
	s.seenLock.Unlock()
	return ok
}

func (s *Server) cleanup() {
	for {
		time.Sleep(60 * time.Second)
		now := time.Now()
		s.seenLock.Lock()
		for k, v := range s.seen {
			if v.Sub(now) > 10*time.Second {
				delete(s.seen, k)
			}
		}
		s.seenLock.Unlock()
	}
}

func (s *Server) GetUID() string {
	return s.id
}

func (s *Server) GetDirection() string {
	return "na"
}

func (s *Server) startServer() {
	r := rex.New("main")
	r.Get("/gadgets", http.HandlerFunc(s.status))
	r.Get("/gadgets/values", http.HandlerFunc(s.values))
	r.Get("/gadgets/locations/{location}/devices/{device}/status", http.HandlerFunc(s.deviceValue))
	r.Put("/gadgets", http.HandlerFunc(s.update))
	r.Post("/gadgets", http.HandlerFunc(s.update))
	if s.isMaster {
		r.Post("/clients", http.HandlerFunc(s.setClient))
		r.Get("/clients", http.HandlerFunc(s.getClients))
		r.Delete("/clients", http.HandlerFunc(s.removeClient))
	}

	s.lg.Printf("listening on 0.0.0.0:%d\n", s.port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
	if err != nil {
		s.lg.Fatal(err)
	}
}

func (s *Server) getClients(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.clientsLock.Lock()
	if err := enc.Encode(s.clients); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
	s.clientsLock.Unlock()
}

func (s *Server) setClient(w http.ResponseWriter, r *http.Request) {
	var a map[string]string
	dec := json.NewDecoder(r.Body)
	err := dec.Decode(&a)
	if err != nil || len(a["address"]) == 0 || len(a["token"]) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	s.clientsLock.Lock()
	s.clients[a["address"]] = a["token"]
	s.clientsLock.Unlock()
}

func (s *Server) removeClient(w http.ResponseWriter, r *http.Request) {
	s.clientsLock.Lock()
	delete(s.clients, r.URL.Host)
	s.clientsLock.Unlock()
}

func (s *Server) status(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	if err := enc.Encode(s.updates); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
	s.statusLock.Unlock()
}

func (s *Server) deviceValue(w http.ResponseWriter, r *http.Request) {
	vars := rex.Vars(r, "main")
	k := fmt.Sprintf("%s %s", vars["location"], vars["device"])
	s.statusLock.Lock()
	m, ok := s.updates[k]
	s.statusLock.Unlock()
	if !ok {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(m.Value); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
}

func (s *Server) values(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	v := map[string]map[string]Value{}

	for _, msg := range s.updates {
		l, ok := v[msg.Location]
		if !ok {
			l = map[string]Value{}
		}
		l[msg.Name] = msg.Value
		v[msg.Location] = l
	}
	s.statusLock.Unlock()
	if err := enc.Encode(v); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
}

func (s *Server) update(w http.ResponseWriter, r *http.Request) {
	var msg Message
	dec := json.NewDecoder(r.Body)
	if err := dec.Decode(&msg); err != nil {
		lg.Println(err)
		return
	}
	if msg.UUID == "" {
		msg.UUID = GetUUID()
	}
	s.external <- msg
}

//
func (s *Server) register() {
	var tries int
	addr := fmt.Sprintf("%s/clients", s.master)
	a := map[string]string{"address": fmt.Sprintf("%s/gadgets", s.host), "token": "n/a"}
	for {
		buf := &bytes.Buffer{}
		enc := json.NewEncoder(buf)
		enc.Encode(&a)
		r, err := http.Post(addr, "application/json", buf)
		if err == nil && r.StatusCode == http.StatusOK {
			return
		}
		tries = increment(tries)
		time.Sleep(time.Duration(tries) * 100 * time.Millisecond)
	}
}

func increment(i int) int {
	if i == 100 {
		return i
	}
	return i + 1
}
<commit_msg>discard request body<commit_after>package gogadgets

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/cswank/rex"
)

type Server struct {
	host        string
	master      string
	isMaster    bool
	port        int
	prefix      string
	lg          Logger
	external    chan Message
	internal    chan Message
	id          string
	updates     map[string]Message
	seen        map[string]time.Time
	statusLock  sync.Mutex
	seenLock    sync.Mutex
	clientsLock sync.Mutex
	clients     map[string]string
}

func NewServer(host, master string, port int, lg Logger) *Server {
	var isMaster bool
	clients := map[string]string{}
	if master == "" {
		isMaster = true
	} else {
		clients = map[string]string{
			master: "",
		}
	}
	return &Server{
		master:   master,
		host:     host,
		isMaster: isMaster,
		port:     port,
		lg:       lg,
		updates:  map[string]Message{},
		id:       "server",
		external: make(chan Message),
		seen:     map[string]time.Time{},
		clients:  clients,
	}
}

func (s *Server) Start(i <-chan Message, o chan<- Message) {
	if !s.isMaster {
		go s.register()
	}
	go s.startServer()
	go s.cleanup()

	for {
		select {
		case msg := <-i:
			if (msg.Type == UPDATE || msg.Type == METHODUPDATE) && s.isMaster {
				s.statusLock.Lock()
				s.updates[msg.Sender] = msg
				s.statusLock.Unlock()
			}
			if !s.isSeen(msg) {
				s.send(msg)
			}
		case msg := <-s.external:
			s.setSeen(msg)
			o <- msg
			if s.isMaster && msg.Sender == "client" {
				s.send(msg)
			}
		}
	}
}

func (s *Server) send(msg Message) {
	s.clientsLock.Lock()
	for host, token := range s.clients {
		go s.doSend(host, msg, token)
	}
	s.clientsLock.Unlock()
}

func (s *Server) doSend(host string, msg Message, token string) {
	msg.Host = s.host
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.Encode(msg)
	req, err := http.NewRequest("POST", host, &buf)
	s.clientsLock.Lock()
	defer s.clientsLock.Unlock()
	if err != nil {
		delete(s.clients, host)
		log.Printf("error posting to quimby host: %s, err: %v\n", host, err)
		return
	}
	if len(token) > 0 {
		req.Header.Add("Authorization", token)
	}
	r, err := http.DefaultClient.Do(req)
	if r != nil {
		r.Body.Close()
	}

	if err != nil || r.StatusCode != http.StatusOK {
		delete(s.clients, host)
	}
}

func (s *Server) setSeen(msg Message) {
	s.seenLock.Lock()
	s.seen[msg.UUID] = time.Now()
	s.seenLock.Unlock()
}

func (s *Server) isSeen(msg Message) bool {
	s.seenLock.Lock()
	_, ok := s.seen[msg.UUID]
	s.seenLock.Unlock()
	return ok
}

func (s *Server) cleanup() {
	for {
		time.Sleep(60 * time.Second)
		now := time.Now()
		s.seenLock.Lock()
		for k, v := range s.seen {
			if v.Sub(now) > 10*time.Second {
				delete(s.seen, k)
			}
		}
		s.seenLock.Unlock()
	}
}

func (s *Server) GetUID() string {
	return s.id
}

func (s *Server) GetDirection() string {
	return "na"
}

func (s *Server) startServer() {
	r := rex.New("main")
	r.Get("/gadgets", http.HandlerFunc(s.status))
	r.Get("/gadgets/values", http.HandlerFunc(s.values))
	r.Get("/gadgets/locations/{location}/devices/{device}/status", http.HandlerFunc(s.deviceValue))
	r.Put("/gadgets", http.HandlerFunc(s.update))
	r.Post("/gadgets", http.HandlerFunc(s.update))
	if s.isMaster {
		r.Post("/clients", http.HandlerFunc(s.setClient))
		r.Get("/clients", http.HandlerFunc(s.getClients))
		r.Delete("/clients", http.HandlerFunc(s.removeClient))
	}

	s.lg.Printf("listening on 0.0.0.0:%d\n", s.port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
	if err != nil {
		s.lg.Fatal(err)
	}
}

func (s *Server) getClients(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.clientsLock.Lock()
	if err := enc.Encode(s.clients); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
	s.clientsLock.Unlock()
}

func (s *Server) setClient(w http.ResponseWriter, r *http.Request) {
	var a map[string]string
	dec := json.NewDecoder(r.Body)
	err := dec.Decode(&a)
	if err != nil || len(a["address"]) == 0 || len(a["token"]) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	s.clientsLock.Lock()
	s.clients[a["address"]] = a["token"]
	s.clientsLock.Unlock()
}

func (s *Server) removeClient(w http.ResponseWriter, r *http.Request) {
	s.clientsLock.Lock()
	delete(s.clients, r.URL.Host)
	s.clientsLock.Unlock()
}

func (s *Server) status(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	if err := enc.Encode(s.updates); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
	s.statusLock.Unlock()
}

func (s *Server) deviceValue(w http.ResponseWriter, r *http.Request) {
	vars := rex.Vars(r, "main")
	k := fmt.Sprintf("%s %s", vars["location"], vars["device"])
	s.statusLock.Lock()
	m, ok := s.updates[k]
	s.statusLock.Unlock()
	if !ok {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(m.Value); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
}

func (s *Server) values(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	v := map[string]map[string]Value{}

	for _, msg := range s.updates {
		l, ok := v[msg.Location]
		if !ok {
			l = map[string]Value{}
		}
		l[msg.Name] = msg.Value
		v[msg.Location] = l
	}
	s.statusLock.Unlock()
	if err := enc.Encode(v); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
}

func (s *Server) update(w http.ResponseWriter, r *http.Request) {
	var msg Message
	dec := json.NewDecoder(r.Body)
	if err := dec.Decode(&msg); err != nil {
		lg.Println(err)
		return
	}
	if msg.UUID == "" {
		msg.UUID = GetUUID()
	}
	s.external <- msg
}

//
func (s *Server) register() {
	var tries int
	addr := fmt.Sprintf("%s/clients", s.master)
	a := map[string]string{"address": fmt.Sprintf("%s/gadgets", s.host), "token": "n/a"}
	for {
		buf := &bytes.Buffer{}
		enc := json.NewEncoder(buf)
		enc.Encode(&a)
		r, err := http.Post(addr, "application/json", buf)
		if err == nil && r.StatusCode == http.StatusOK {
			return
		}
		tries = increment(tries)
		time.Sleep(time.Duration(tries) * 100 * time.Millisecond)
	}
}

func increment(i int) int {
	if i == 100 {
		return i
	}
	return i + 1
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/smartystreets/goconvey/reporting"
	"strconv"
	"strings"
)

func parsePackageResults(raw string) *PackageResult {
	parser := newOutputParser(raw)
	return parser.Parse()
}

func newOutputParser(raw string) *outputParser {
	self := &outputParser{}
	self.raw = strings.TrimSpace(raw)
	self.lines = strings.Split(self.raw, "\n")
	self.result = &PackageResult{}
	self.tests = []*TestResult{}
	self.result.TestResults = []TestResult{}
	return self
}

func (self *outputParser) Parse() *PackageResult {
	self.gatherTestFunctionsAndMetadata()
	self.parseTestFunctions()
	self.attachTestFunctionsToResult()
	return self.result
}

func (self *outputParser) gatherTestFunctionsAndMetadata() {
	for _, self.line = range self.lines {
		self.processNextLine()
	}
}
func (self *outputParser) processNextLine() {
	if isNewTest(self.line) {
		self.registerTestFunction()

	} else if isTestResult(self.line) {
		self.recordTestMetadata()

	} else if isPackageReport(self.line) {
		self.recordPackageMetadata()

	} else {
		self.saveLineForParsingLater()
	}
}

func isNewTest(line string) bool {
	return strings.HasPrefix(line, "=== ")
}
func isTestResult(line string) bool {
	return strings.HasPrefix(line, "--- ")
}
func isPackageReport(line string) bool {
	return (strings.HasPrefix(line, "FAIL") ||
		strings.HasPrefix(line, "exit status") ||
		strings.HasPrefix(line, "PASS") ||
		strings.HasPrefix(line, "ok  \t"))
}

func (self *outputParser) registerTestFunction() {
	self.test = &TestResult{}
	self.test.Stories = []reporting.ScopeResult{}
	self.test.rawLines = []string{}
	self.test.TestName = self.line[len("=== RUN "):]
	self.tests = append(self.tests, self.test)
}
func (self *outputParser) recordTestMetadata() {
	self.test.Passed = strings.HasPrefix(self.line, "--- PASS: ")
	self.test.Elapsed = parseTestFunctionDuration(self.line)
}
func (self *outputParser) saveLineForParsingLater() {
	self.line = strings.TrimSpace(self.line)
	self.line = strings.Replace(self.line, "\u0009", "\t", -1)
	self.test.rawLines = append(self.test.rawLines, self.line)
}
func (self *outputParser) recordPackageMetadata() {
	if strings.HasPrefix(self.line, "FAIL\t") {
		self.parseLastLine()
		self.result.Passed = false
	} else if strings.HasPrefix(self.line, "ok  \t") {
		self.parseLastLine()
		self.result.Passed = true
	}
}
func (self *outputParser) parseLastLine() {
	fields := strings.Split(self.line, "\t")
	self.result.PackageName = strings.TrimSpace(fields[1])
	self.result.Elapsed = parseDurationInSeconds(fields[2], 3)
}

func (self *outputParser) parseTestFunctions() {
	for _, self.test = range self.tests {
		if len(self.test.rawLines) == 0 {
			continue
		} else if isJson(self.test.rawLines[0]) {
			self.deserializeScopes()
		} else {
			self.parseGoTestMessage()
		}
	}
}
func isJson(line string) bool {
	return strings.HasPrefix(line, "{")
}
func (self *outputParser) deserializeScopes() {
	// TODO: clean up!
	rawJson := strings.Join(self.test.rawLines, "")
	var scopes []reporting.ScopeResult
	if strings.HasSuffix(rawJson, ",") { // Shouldn't need this...
		rawJson = rawJson[:len(rawJson)-1]
	}
	rawJson = "[" + rawJson + "]"
	err := json.Unmarshal([]byte(rawJson), &scopes)
	if err != nil {
		fmt.Println(err) // panic?
	}
	self.test.Stories = scopes
}
func (self *outputParser) parseGoTestMessage() {
	// TODO: clean up!
	if strings.HasPrefix(self.test.rawLines[0], "panic: ") {
		for i, line := range self.test.rawLines {
			if strings.HasPrefix(line, "goroutine") && strings.Contains(line, "[running]") {
				metaLine := self.test.rawLines[i+4]
				fields := strings.Split(metaLine, " ")
				fileAndLine := strings.Split(fields[0], ":")
				self.test.File = fileAndLine[0]
				self.test.Line, _ = strconv.Atoi(fileAndLine[1])
			}
			if strings.Contains(line, "+") || (i > 0 && strings.Contains(line, "panic: ")) {
				self.test.rawLines[i] = "\t" + line
			}
		}
		self.test.Error = strings.Join(self.test.rawLines, "\n")
	} else {
		lineFields := self.test.rawLines[0]
		fields := strings.Split(lineFields, ":")
		self.test.File = strings.TrimSpace(fields[0])
		self.test.Line, _ = strconv.Atoi(fields[1])
		self.test.Message = strings.TrimSpace(fields[2])
		if len(self.test.rawLines) > 1 {
			additionalLines := strings.Join(self.test.rawLines[1:], "\n")
			self.test.Message = self.test.Message + "\n" + additionalLines
		}
	}
}

func (self *outputParser) attachTestFunctionsToResult() {
	for _, test := range self.tests {
		test.rawLines = []string{}
		self.result.TestResults = append(self.result.TestResults, *test)
	}
}

type outputParser struct {
	raw    string
	lines  []string
	result *PackageResult
	tests  []*TestResult

	// place holders for loops
	line string
	test *TestResult
}

type PackageResult struct {
	PackageName string
	Elapsed     float64
	Passed      bool
	TestResults []TestResult
}

type TestResult struct {
	TestName string
	Elapsed  float64
	Passed   bool
	File     string
	Line     int
	Message  string
	Error    string
	Stories  []reporting.ScopeResult

	rawLines []string
}
<commit_msg>Accounting for package with no test files.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/smartystreets/goconvey/reporting"
	"strconv"
	"strings"
)

func parsePackageResults(raw string) *PackageResult {
	parser := newOutputParser(raw)
	return parser.Parse()
}

func newOutputParser(raw string) *outputParser {
	self := &outputParser{}
	self.raw = strings.TrimSpace(raw)
	self.lines = strings.Split(self.raw, "\n")
	self.result = &PackageResult{}
	self.tests = []*TestResult{}
	self.result.TestResults = []TestResult{}
	return self
}

func (self *outputParser) Parse() *PackageResult {
	self.gatherTestFunctionsAndMetadata()
	self.parseTestFunctions()
	self.attachTestFunctionsToResult()
	return self.result
}

func (self *outputParser) gatherTestFunctionsAndMetadata() {
	for _, self.line = range self.lines {
		self.processNextLine()
	}
}
func (self *outputParser) processNextLine() {
	if noTestsRun(self.line) {
		self.recordEmptyPackage()

	} else if isNewTest(self.line) {
		self.registerTestFunction()

	} else if isTestResult(self.line) {
		self.recordTestMetadata()

	} else if isPackageReport(self.line) {
		self.recordPackageMetadata()

	} else {
		self.saveLineForParsingLater()
	}
}

func noTestsRun(line string) bool {
	// TEST INPUT:
	// ?   	pkg.smartystreets.net/liveaddress-zipapi	[no test files]
	return strings.HasPrefix(line, "?") && strings.Contains(line, "[no test files]")
}

func isNewTest(line string) bool {
	return strings.HasPrefix(line, "=== ")
}
func isTestResult(line string) bool {
	return strings.HasPrefix(line, "--- ")
}
func isPackageReport(line string) bool {
	return (strings.HasPrefix(line, "FAIL") ||
		strings.HasPrefix(line, "exit status") ||
		strings.HasPrefix(line, "PASS") ||
		strings.HasPrefix(line, "ok  \t"))
}

func (self *outputParser) recordEmptyPackage() {
	fields := strings.Split(self.line, "\t")
	self.result.PackageName = fields[1]
}

func (self *outputParser) registerTestFunction() {
	self.test = &TestResult{}
	self.test.Stories = []reporting.ScopeResult{}
	self.test.rawLines = []string{}
	self.test.TestName = self.line[len("=== RUN "):]
	self.tests = append(self.tests, self.test)
}
func (self *outputParser) recordTestMetadata() {
	self.test.Passed = strings.HasPrefix(self.line, "--- PASS: ")
	self.test.Elapsed = parseTestFunctionDuration(self.line)
}
func (self *outputParser) saveLineForParsingLater() {
	self.line = strings.TrimSpace(self.line)
	self.line = strings.Replace(self.line, "\u0009", "\t", -1)
	self.test.rawLines = append(self.test.rawLines, self.line)
}
func (self *outputParser) recordPackageMetadata() {
	if strings.HasPrefix(self.line, "FAIL\t") {
		self.parseLastLine()
		self.result.Passed = false
	} else if strings.HasPrefix(self.line, "ok  \t") {
		self.parseLastLine()
		self.result.Passed = true
	}
}
func (self *outputParser) parseLastLine() {
	fields := strings.Split(self.line, "\t")
	self.result.PackageName = strings.TrimSpace(fields[1])
	self.result.Elapsed = parseDurationInSeconds(fields[2], 3)
}

func (self *outputParser) parseTestFunctions() {
	for _, self.test = range self.tests {
		if len(self.test.rawLines) == 0 {
			continue
		} else if isJson(self.test.rawLines[0]) {
			self.deserializeScopes()
		} else {
			self.parseGoTestMessage()
		}
	}
}
func isJson(line string) bool {
	return strings.HasPrefix(line, "{")
}
func (self *outputParser) deserializeScopes() {
	// TODO: clean up!
	rawJson := strings.Join(self.test.rawLines, "")
	var scopes []reporting.ScopeResult
	if strings.HasSuffix(rawJson, ",") { // Shouldn't need this...
		rawJson = rawJson[:len(rawJson)-1]
	}
	rawJson = "[" + rawJson + "]"
	err := json.Unmarshal([]byte(rawJson), &scopes)
	if err != nil {
		fmt.Println(err) // panic?
	}
	self.test.Stories = scopes
}
func (self *outputParser) parseGoTestMessage() {
	// TODO: clean up!
	if strings.HasPrefix(self.test.rawLines[0], "panic: ") {
		for i, line := range self.test.rawLines {
			if strings.HasPrefix(line, "goroutine") && strings.Contains(line, "[running]") {
				metaLine := self.test.rawLines[i+4]
				fields := strings.Split(metaLine, " ")
				fileAndLine := strings.Split(fields[0], ":")
				self.test.File = fileAndLine[0]
				self.test.Line, _ = strconv.Atoi(fileAndLine[1])
			}
			if strings.Contains(line, "+") || (i > 0 && strings.Contains(line, "panic: ")) {
				self.test.rawLines[i] = "\t" + line
			}
		}
		self.test.Error = strings.Join(self.test.rawLines, "\n")
	} else {
		lineFields := self.test.rawLines[0]
		fields := strings.Split(lineFields, ":")
		self.test.File = strings.TrimSpace(fields[0])
		self.test.Line, _ = strconv.Atoi(fields[1])
		self.test.Message = strings.TrimSpace(fields[2])
		if len(self.test.rawLines) > 1 {
			additionalLines := strings.Join(self.test.rawLines[1:], "\n")
			self.test.Message = self.test.Message + "\n" + additionalLines
		}
	}
}

func (self *outputParser) attachTestFunctionsToResult() {
	for _, test := range self.tests {
		test.rawLines = []string{}
		self.result.TestResults = append(self.result.TestResults, *test)
	}
}

type outputParser struct {
	raw    string
	lines  []string
	result *PackageResult
	tests  []*TestResult

	// place holders for loops
	line string
	test *TestResult
}

type PackageResult struct {
	PackageName string
	Elapsed     float64
	Passed      bool
	TestResults []TestResult
}

type TestResult struct {
	TestName string
	Elapsed  float64
	Passed   bool
	File     string
	Line     int
	Message  string
	Error    string
	Stories  []reporting.ScopeResult

	rawLines []string
}
<|endoftext|>