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

package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"testing"
	"time"
	"unicode/utf8"
)

func TestExactly16Bytes(t *testing.T) {
	var tests = []string{
		"",
		"a",
		"日本語",
		"1234567890123456",
		"12345678901234567890",
		"1234567890123本語4567890",
		"12345678901234日本語567890",
		"123456789012345日本語67890",
		"1234567890123456日本語7890",
		"1234567890123456日本語7日本語890",
	}
	for _, str := range tests {
		got := exactly16Bytes(str)
		if len(got) != 16 {
			t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
		}
		// Make sure it is full runes.
		for _, c := range got {
			if c == utf8.RuneError {
				t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
			}
		}
	}
}

// tmpDir creates a temporary directory and returns its name.
func tmpDir(t *testing.T) string {
	name, err := ioutil.TempDir("", "pack")
	if err != nil {
		t.Fatal(err)
	}
	return name
}

// Test that we can create an archive, write to it, and get the same contents back.
// Tests the rv and then the pv command on a new archive.
func TestCreate(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	name := filepath.Join(dir, "pack.a")
	ar := archive(name, os.O_RDWR, nil)
	// Add an entry by hand.
	ar.addFile(helloFile.Reset())
	ar.fd.Close()
	// Now check it.
	ar = archive(name, os.O_RDONLY, []string{helloFile.name})
	var buf bytes.Buffer
	stdout = &buf
	verbose = true
	defer func() {
		stdout = os.Stdout
		verbose = false
	}()
	ar.scan(ar.printContents)
	ar.fd.Close()
	result := buf.String()
	// Expect verbose output plus file contents.
	expect := fmt.Sprintf("%s\n%s", helloFile.name, helloFile.contents)
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
}

// Test that we can create an archive, put some files in it, and get back a correct listing.
// Tests the tv command.
func TestTableOfContents(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	name := filepath.Join(dir, "pack.a")
	ar := archive(name, os.O_RDWR, nil)
	// Add some entries by hand.
	ar.addFile(helloFile.Reset())
	ar.addFile(goodbyeFile.Reset())
	ar.fd.Close()
	// Now print it.
	ar = archive(name, os.O_RDONLY, nil)
	var buf bytes.Buffer
	stdout = &buf
	verbose = true
	defer func() {
		stdout = os.Stdout
		verbose = false
	}()
	ar.scan(ar.tableOfContents)
	ar.fd.Close()
	result := buf.String()
	// Expect verbose listing.
	expect := fmt.Sprintf("%s\n%s\n", helloFile.Entry(), goodbyeFile.Entry())
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
	// Do it again without verbose.
	verbose = false
	buf.Reset()
	ar = archive(name, os.O_RDONLY, nil)
	ar.scan(ar.tableOfContents)
	ar.fd.Close()
	result = buf.String()
	// Expect non-verbose listing.
	expect = fmt.Sprintf("%s\n%s\n", helloFile.name, goodbyeFile.name)
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
}

// Test that we can create an archive, put some files in it, and get back a file.
// Tests the x command.
func TestExtract(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	name := filepath.Join(dir, "pack.a")
	ar := archive(name, os.O_RDWR, nil)
	// Add some entries by hand.
	ar.addFile(helloFile.Reset())
	ar.addFile(goodbyeFile.Reset())
	ar.fd.Close()
	// Now extract one file. We chdir to the directory of the archive for simplicity.
	pwd, err := os.Getwd()
	if err != nil {
		t.Fatal("os.Getwd: ", err)
	}
	err = os.Chdir(dir)
	if err != nil {
		t.Fatal("os.Chdir: ", err)
	}
	defer func() {
		err := os.Chdir(pwd)
		if err != nil {
			t.Fatal("os.Chdir: ", err)
		}
	}()
	ar = archive(name, os.O_RDONLY, []string{goodbyeFile.name})
	ar.scan(ar.extractContents)
	ar.fd.Close()
	data, err := ioutil.ReadFile(goodbyeFile.name)
	if err != nil {
		t.Fatal(err)
	}
	// Expect contents of file.
	result := string(data)
	expect := goodbyeFile.contents
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
}

// Test that pack-created archives can be understood by the tools.
func TestHello(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	hello := filepath.Join(dir, "hello.go")
	prog := `
		package main
		func main() {
			println("hello world")
		}
	`
	err := ioutil.WriteFile(hello, []byte(prog), 0666)
	if err != nil {
		t.Fatal(err)
	}

	run := func(args ...string) string {
		cmd := exec.Command(args[0], args[1:]...)
		cmd.Dir = dir
		out, err := cmd.CombinedOutput()
		if err != nil {
			t.Fatalf("%v: %v\n%s", args, err, string(out))
		}
		return string(out)
	}

	out := run("go", "env")
	re, err := regexp.Compile(`\s*GOCHAR=['"]?(\w)['"]?`)
	if err != nil {
		t.Fatal(err)
	}
	fields := re.FindStringSubmatch(out)
	if fields == nil {
		t.Fatal("cannot find GOCHAR in 'go env' output:\n", out)
	}
	char := fields[1]
	run("go", "build", "cmd/pack") // writes pack binary to dir
	run("go", "tool", char+"g", "hello.go")
	run("./pack", "grc", "hello.a", "hello."+char)
	run("go", "tool", char+"l", "-o", "a.out", "hello.a")
	out = run("./a.out")
	if out != "hello world\n" {
		t.Fatal("incorrect output: %q, want %q", out, "hello world\n")
	}
}

// Fake implementation of files.

var helloFile = &FakeFile{
	name:     "hello",
	contents: "hello world", // 11 bytes, an odd number.
	mode:     0644,
}

var goodbyeFile = &FakeFile{
	name:     "goodbye",
	contents: "Sayonara, Jim", // 13 bytes, another odd number.
	mode:     0644,
}

// FakeFile implements FileLike and also os.FileInfo.
type FakeFile struct {
	name     string
	contents string
	mode     os.FileMode
	offset   int
}

// Reset prepares a FakeFile for reuse.
func (f *FakeFile) Reset() *FakeFile {
	f.offset = 0
	return f
}

// FileLike methods.

func (f *FakeFile) Name() string {
	// A bit of a cheat: we only have a basename, so that's also ok for FileInfo.
	return f.name
}

func (f *FakeFile) Stat() (os.FileInfo, error) {
	return f, nil
}

func (f *FakeFile) Read(p []byte) (int, error) {
	if f.offset >= len(f.contents) {
		return 0, io.EOF
	}
	n := copy(p, f.contents[f.offset:])
	f.offset += n
	return n, nil
}

func (f *FakeFile) Close() error {
	return nil
}

// os.FileInfo methods.

func (f *FakeFile) Size() int64 {
	return int64(len(f.contents))
}

func (f *FakeFile) Mode() os.FileMode {
	return f.mode
}

func (f *FakeFile) ModTime() time.Time {
	return time.Time{}
}

func (f *FakeFile) IsDir() bool {
	return false
}

func (f *FakeFile) Sys() interface{} {
	return nil
}

// Special helpers.

func (f *FakeFile) Entry() *Entry {
	return &Entry{
		name:  f.name,
		mtime: 0, // Defined to be zero.
		uid:   0, // Ditto.
		gid:   0, // Ditto.
		mode:  f.mode,
		size:  int64(len(f.contents)),
	}
}
<commit_msg>cmd/pack: provide executable name in TestHello<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"testing"
	"time"
	"unicode/utf8"
)

func TestExactly16Bytes(t *testing.T) {
	var tests = []string{
		"",
		"a",
		"日本語",
		"1234567890123456",
		"12345678901234567890",
		"1234567890123本語4567890",
		"12345678901234日本語567890",
		"123456789012345日本語67890",
		"1234567890123456日本語7890",
		"1234567890123456日本語7日本語890",
	}
	for _, str := range tests {
		got := exactly16Bytes(str)
		if len(got) != 16 {
			t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
		}
		// Make sure it is full runes.
		for _, c := range got {
			if c == utf8.RuneError {
				t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
			}
		}
	}
}

// tmpDir creates a temporary directory and returns its name.
func tmpDir(t *testing.T) string {
	name, err := ioutil.TempDir("", "pack")
	if err != nil {
		t.Fatal(err)
	}
	return name
}

// Test that we can create an archive, write to it, and get the same contents back.
// Tests the rv and then the pv command on a new archive.
func TestCreate(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	name := filepath.Join(dir, "pack.a")
	ar := archive(name, os.O_RDWR, nil)
	// Add an entry by hand.
	ar.addFile(helloFile.Reset())
	ar.fd.Close()
	// Now check it.
	ar = archive(name, os.O_RDONLY, []string{helloFile.name})
	var buf bytes.Buffer
	stdout = &buf
	verbose = true
	defer func() {
		stdout = os.Stdout
		verbose = false
	}()
	ar.scan(ar.printContents)
	ar.fd.Close()
	result := buf.String()
	// Expect verbose output plus file contents.
	expect := fmt.Sprintf("%s\n%s", helloFile.name, helloFile.contents)
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
}

// Test that we can create an archive, put some files in it, and get back a correct listing.
// Tests the tv command.
func TestTableOfContents(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	name := filepath.Join(dir, "pack.a")
	ar := archive(name, os.O_RDWR, nil)
	// Add some entries by hand.
	ar.addFile(helloFile.Reset())
	ar.addFile(goodbyeFile.Reset())
	ar.fd.Close()
	// Now print it.
	ar = archive(name, os.O_RDONLY, nil)
	var buf bytes.Buffer
	stdout = &buf
	verbose = true
	defer func() {
		stdout = os.Stdout
		verbose = false
	}()
	ar.scan(ar.tableOfContents)
	ar.fd.Close()
	result := buf.String()
	// Expect verbose listing.
	expect := fmt.Sprintf("%s\n%s\n", helloFile.Entry(), goodbyeFile.Entry())
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
	// Do it again without verbose.
	verbose = false
	buf.Reset()
	ar = archive(name, os.O_RDONLY, nil)
	ar.scan(ar.tableOfContents)
	ar.fd.Close()
	result = buf.String()
	// Expect non-verbose listing.
	expect = fmt.Sprintf("%s\n%s\n", helloFile.name, goodbyeFile.name)
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
}

// Test that we can create an archive, put some files in it, and get back a file.
// Tests the x command.
func TestExtract(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	name := filepath.Join(dir, "pack.a")
	ar := archive(name, os.O_RDWR, nil)
	// Add some entries by hand.
	ar.addFile(helloFile.Reset())
	ar.addFile(goodbyeFile.Reset())
	ar.fd.Close()
	// Now extract one file. We chdir to the directory of the archive for simplicity.
	pwd, err := os.Getwd()
	if err != nil {
		t.Fatal("os.Getwd: ", err)
	}
	err = os.Chdir(dir)
	if err != nil {
		t.Fatal("os.Chdir: ", err)
	}
	defer func() {
		err := os.Chdir(pwd)
		if err != nil {
			t.Fatal("os.Chdir: ", err)
		}
	}()
	ar = archive(name, os.O_RDONLY, []string{goodbyeFile.name})
	ar.scan(ar.extractContents)
	ar.fd.Close()
	data, err := ioutil.ReadFile(goodbyeFile.name)
	if err != nil {
		t.Fatal(err)
	}
	// Expect contents of file.
	result := string(data)
	expect := goodbyeFile.contents
	if result != expect {
		t.Fatalf("expected %q got %q", expect, result)
	}
}

// Test that pack-created archives can be understood by the tools.
func TestHello(t *testing.T) {
	dir := tmpDir(t)
	defer os.RemoveAll(dir)
	hello := filepath.Join(dir, "hello.go")
	prog := `
		package main
		func main() {
			println("hello world")
		}
	`
	err := ioutil.WriteFile(hello, []byte(prog), 0666)
	if err != nil {
		t.Fatal(err)
	}

	run := func(args ...string) string {
		cmd := exec.Command(args[0], args[1:]...)
		cmd.Dir = dir
		out, err := cmd.CombinedOutput()
		if err != nil {
			t.Fatalf("%v: %v\n%s", args, err, string(out))
		}
		return string(out)
	}

	out := run("go", "env")
	re, err := regexp.Compile(`\s*GOCHAR=['"]?(\w)['"]?`)
	if err != nil {
		t.Fatal(err)
	}
	fields := re.FindStringSubmatch(out)
	if fields == nil {
		t.Fatal("cannot find GOCHAR in 'go env' output:\n", out)
	}
	char := fields[1]
	run("go", "build", "-o", "pack", "cmd/pack") // writes pack binary to dir
	run("go", "tool", char+"g", "hello.go")
	run("./pack", "grc", "hello.a", "hello."+char)
	run("go", "tool", char+"l", "-o", "a.out", "hello.a")
	out = run("./a.out")
	if out != "hello world\n" {
		t.Fatal("incorrect output: %q, want %q", out, "hello world\n")
	}
}

// Fake implementation of files.

var helloFile = &FakeFile{
	name:     "hello",
	contents: "hello world", // 11 bytes, an odd number.
	mode:     0644,
}

var goodbyeFile = &FakeFile{
	name:     "goodbye",
	contents: "Sayonara, Jim", // 13 bytes, another odd number.
	mode:     0644,
}

// FakeFile implements FileLike and also os.FileInfo.
type FakeFile struct {
	name     string
	contents string
	mode     os.FileMode
	offset   int
}

// Reset prepares a FakeFile for reuse.
func (f *FakeFile) Reset() *FakeFile {
	f.offset = 0
	return f
}

// FileLike methods.

func (f *FakeFile) Name() string {
	// A bit of a cheat: we only have a basename, so that's also ok for FileInfo.
	return f.name
}

func (f *FakeFile) Stat() (os.FileInfo, error) {
	return f, nil
}

func (f *FakeFile) Read(p []byte) (int, error) {
	if f.offset >= len(f.contents) {
		return 0, io.EOF
	}
	n := copy(p, f.contents[f.offset:])
	f.offset += n
	return n, nil
}

func (f *FakeFile) Close() error {
	return nil
}

// os.FileInfo methods.

func (f *FakeFile) Size() int64 {
	return int64(len(f.contents))
}

func (f *FakeFile) Mode() os.FileMode {
	return f.mode
}

func (f *FakeFile) ModTime() time.Time {
	return time.Time{}
}

func (f *FakeFile) IsDir() bool {
	return false
}

func (f *FakeFile) Sys() interface{} {
	return nil
}

// Special helpers.

func (f *FakeFile) Entry() *Entry {
	return &Entry{
		name:  f.name,
		mtime: 0, // Defined to be zero.
		uid:   0, // Ditto.
		gid:   0, // Ditto.
		mode:  f.mode,
		size:  int64(len(f.contents)),
	}
}
<|endoftext|>


<commit_before>package eval

import (
	"fmt"
	"math/big"
	"strings"
	"time"
	"unicode"
)

const (
	ISO8601 string = "2006-01-02T15:04:05.999Z0700"
)

func builtin(name string, args []interface{}, context Context) (val interface{}, err error) {
	switch name {
	//salesforce text functions: CASESAFEID, GETSESSIONID, HYPERLINK, IMAGE, ISPICKVAL not implemented as too specific
	// TEXT should be avoided, use FORMAT instead
	case "BEGINS":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return strings.Index(s1, s2) == 0, nil
	case "CONTAINS":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return strings.Index(s1, s2) != -1, nil
	case "FIND":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return new(big.Rat).SetInt64(int64(strings.Index(s1, s2))), nil
	case "INCLUDES":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return strings.Index(";"+s1+";", ";"+s2+";") != -1, nil
	case "LEFT":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		n1 := GetNumberAsInt(args, 1)
		return substr(s1, 0, n1), nil
	case "LEN":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		return new(big.Rat).SetInt64(int64(len([]rune(s1)))), nil
	case "LOWER":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		return strings.ToLower(s1), nil
	case "LPAD":
		n1 := GetNumberAsInt(args, 1)
		s1 := MustBeString(args, 0)
		s2 := " "
		if len(args) > 2 {
			NumOfParams(args, 3)
			s2 = MustBeString(args, 2)
		}
		s1r := []rune(s1)
		s2r := []rune(s2)
		if n1 == len(s1r) {
			return s1, nil
		} else if n1 < len(s1r) {
			return string(s1r[:n1]), nil
		}
		n1 = n1 - len(s1r)
		var sr []rune
		for i := 0; len(sr) < n1; i++ {
			sr = append(sr, s2r[i%len(s2r)])
		}
		return string(append(sr, s1r...)), nil
	case "MID":
		NumOfParams(args, 3)
		s1 := MustBeString(args, 0)
		n1 := GetNumberAsInt(args, 1)
		if n1 <= 0 {
			n1 = 1
		}
		n2 := GetNumberAsInt(args, 2)
		if n2 <= 0 {
			return "", nil
		}
		return substr(s1, n1-1, n2), nil
	case "RIGHT":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		n1 := GetNumberAsInt(args, 1)
		if n1 <= 0 {
			return "", nil
		}
		return substr(s1, -n1, -1), nil
	case "RPAD":
		n1 := GetNumberAsInt(args, 1)
		s1 := MustBeString(args, 0)
		s2 := " "
		if len(args) > 2 {
			NumOfParams(args, 3)
			s2 = MustBeString(args, 2)
		}
		s1r := []rune(s1)
		s2r := []rune(s2)
		if n1 == len(s1r) {
			return s1, nil
		} else if n1 < len(s1r) {
			return string(s1r[:n1]), nil
		}
		for i := 0; len(s1r) < n1; i++ {
			s1r = append(s1r, s2r[i%len(s2r)])
		}
		return string(s1r), nil
	case "SUBSTITUTE":
		NumOfParams(args, 3)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		s3 := MustBeString(args, 2)
		return strings.Replace(s1, s2, s3, -1), nil
	case "TEXT":
		NumOfParams(args, 1)
		v1 := args[0]
		switch v1.(type) {
		case string:
			return v1.(string), nil
		case big.Rat:
			return v1.(*big.Rat).String(), nil
		case bool:
			if v1.(bool) {
				return "true", nil
			} else {
				return "false", nil
			}
		case time.Time:
			return v1.(time.Time).Format(ISO8601), nil
		}
		panic(fmt.Sprint("unsupported type:", v1))
	case "TRIM":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		s1 = strings.TrimSpace(s1)
		s1r := []rune(s1)
		sr := make([]rune, 0, len(s1r))
		var first bool = false
		for _, r := range s1r {
			sp := unicode.IsSpace(r)
			if first && sp {
				sr = append(sr, ' ')
				first = false
			} else if !sp {
				sr = append(sr, r)
				first = true
			}
		}
		return string(sr), nil
	case "UPPER":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		return strings.ToUpper(s1), nil
	case "ISBLANK":
		NumOfParams(args, 1)
		if args[0] == nil {
			return true, nil
		} else if s, ok := args[0].(string); ok && s == "" {
			return true, nil
		}
		return false, nil
	case "VALUE":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		f, ok := new(big.Rat).SetString(s1)
		if ok {
			return f, nil
		}
		panic(fmt.Sprint("not a number", s1))
	// salesforce date functions. DATEVALUE accepts additional format parameter
	case "DATE":
		NumOfParams(args, 3)
		n1 := GetNumberAsInt(args, 0)
		n2 := GetNumberAsInt(args, 1)
		n3 := GetNumberAsInt(args, 2)
		return time.Date(n1, time.Month(n2), n3, 0, 0, 0, 0, context.cast().localTimeZone), nil
	case "DATEVALUE":
		s1 := MustBeString(args, 0)
		s2 := "2006-01-02"
		if len(args) > 1 {
			NumOfParams(args, 2)
			s2 = MustBeString(args, 1)
		}
		return context.ParseDate(s2, s1)
	case "DAY":
		NumOfParams(args, 1)
		d1 := MustBeDate(args, 0)
		return new(big.Rat).SetInt64(int64(d1.Day())), nil
	case "MONTH":
		NumOfParams(args, 1)
		d1 := MustBeDate(args, 0)
		return new(big.Rat).SetInt64(int64(d1.Month())), nil
	case "NOW":
		NumOfParams(args, 0)
		return time.Now().In(context.cast().localTimeZone), nil
	case "TODAY":
		NumOfParams(args, 0)
		return time.Now().In(context.cast().localTimeZone).Truncate(time.Hour * 24), nil
	case "YEAR":
		NumOfParams(args, 1)
		d1 := MustBeDate(args, 0)
		return new(big.Rat).SetInt64(int64(d1.Year())), nil
		// salesforce logical functions: AND, NOT, OR should not be used, use logical operators
	case "CASE":
		MinNumOfParams(args, 3)
		i := 1
		for ; i < len(args)-1; i += 2 {
			switch args[0].(type) {
			case nil:
				if args[i] == nil {
					return args[i+1], nil
				}
			case *big.Rat:
				n := MustBeNumber(args, i)
				if args[0].(*big.Rat).Cmp(n) == 0 {
					return args[i+1], nil
				}
			case string:
				s := MustBeString(args, i)
				if args[0].(string) == s {
					return args[i+1], nil
				}
			case bool:
				b1 := MustBeBool(args, i)
				if args[0].(bool) == b1 {
					return args[i+1], nil
				}
			default:
				panic(fmt.Sprint("unsupported type:", args[i]))
			}
		}
		if i < len(args) {
			return args[i], nil
		}
		panic("missing default value")
	case "IF":
		NumOfParams(args, 3)
		cond := MustBeBool(args, 0)
		if cond {
			return args[1], nil
		}
		return args[2], nil
	case "NULLVALUE":
		NumOfParams(args, 2)
		if args[0] == nil {
			return args[1], nil
		}
		return args[0], nil
	// additional convenience functions for text
	// join(delimiter, strings...) joins non empty strings listed as arguments using delimiter (empty strings are skipped)
	case "JOIN":
		var a []string
		for i, v := range args[1:] {
			if v != nil {
				s := MustBeString(args, i+1)
				if s != "" {
					a = append(a, s)
				}
			}
		}
		s1 := MustBeString(args, 0)
		return strings.Join(a, s1), nil
	}
	panic("unknown function")
}

func substr(s string, b int, l int) string {
	r := []rune(s)
	if len(r) == 0 || l == 0 {
		return ""
	}
	if b < 0 {
		if -b >= len(r) {
			b = 0
		} else {
			b = len(r) + b
		}
	}
	if l < 0 {
		l = len(r)
	}
	e := b + l
	if e > len(r) {
		e = len(r)
	}
	return string(r[b:e])
}
<commit_msg>abs added<commit_after>package eval

import (
	"fmt"
	"math/big"
	"strings"
	"time"
	"unicode"
)

const (
	ISO8601 string = "2006-01-02T15:04:05.999Z0700"
)

func builtin(name string, args []interface{}, context Context) (val interface{}, err error) {
	switch name {
	//salesforce text functions: CASESAFEID, GETSESSIONID, HYPERLINK, IMAGE, ISPICKVAL not implemented as too specific
	// TEXT should be avoided, use FORMAT instead
	case "BEGINS":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return strings.Index(s1, s2) == 0, nil
	case "CONTAINS":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return strings.Index(s1, s2) != -1, nil
	case "FIND":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return new(big.Rat).SetInt64(int64(strings.Index(s1, s2) + 1)), nil
	case "INCLUDES":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		return strings.Index(";"+s1+";", ";"+s2+";") != -1, nil
	case "LEFT":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		n1 := GetNumberAsInt(args, 1)
		return substr(s1, 0, n1), nil
	case "LEN":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		return new(big.Rat).SetInt64(int64(len([]rune(s1)))), nil
	case "LOWER":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		return strings.ToLower(s1), nil
	case "LPAD":
		n1 := GetNumberAsInt(args, 1)
		s1 := MustBeString(args, 0)
		s2 := " "
		if len(args) > 2 {
			NumOfParams(args, 3)
			s2 = MustBeString(args, 2)
		}
		s1r := []rune(s1)
		s2r := []rune(s2)
		if n1 == len(s1r) {
			return s1, nil
		} else if n1 < len(s1r) {
			return string(s1r[:n1]), nil
		}
		n1 = n1 - len(s1r)
		var sr []rune
		for i := 0; len(sr) < n1; i++ {
			sr = append(sr, s2r[i%len(s2r)])
		}
		return string(append(sr, s1r...)), nil
	case "MID":
		NumOfParams(args, 3)
		s1 := MustBeString(args, 0)
		n1 := GetNumberAsInt(args, 1)
		if n1 <= 0 {
			n1 = 1
		}
		n2 := GetNumberAsInt(args, 2)
		if n2 <= 0 {
			return "", nil
		}
		return substr(s1, n1-1, n2), nil
	case "RIGHT":
		NumOfParams(args, 2)
		s1 := MustBeString(args, 0)
		n1 := GetNumberAsInt(args, 1)
		if n1 <= 0 {
			return "", nil
		}
		return substr(s1, -n1, -1), nil
	case "RPAD":
		n1 := GetNumberAsInt(args, 1)
		s1 := MustBeString(args, 0)
		s2 := " "
		if len(args) > 2 {
			NumOfParams(args, 3)
			s2 = MustBeString(args, 2)
		}
		s1r := []rune(s1)
		s2r := []rune(s2)
		if n1 == len(s1r) {
			return s1, nil
		} else if n1 < len(s1r) {
			return string(s1r[:n1]), nil
		}
		for i := 0; len(s1r) < n1; i++ {
			s1r = append(s1r, s2r[i%len(s2r)])
		}
		return string(s1r), nil
	case "SUBSTITUTE":
		NumOfParams(args, 3)
		s1 := MustBeString(args, 0)
		s2 := MustBeString(args, 1)
		s3 := MustBeString(args, 2)
		return strings.Replace(s1, s2, s3, -1), nil
	case "TEXT":
		NumOfParams(args, 1)
		v1 := args[0]
		switch v1.(type) {
		case string:
			return v1.(string), nil
		case big.Rat:
			return v1.(*big.Rat).String(), nil
		case bool:
			if v1.(bool) {
				return "true", nil
			} else {
				return "false", nil
			}
		case time.Time:
			return v1.(time.Time).Format(ISO8601), nil
		}
		panic(fmt.Sprint("unsupported type:", v1))
	case "TRIM":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		s1 = strings.TrimSpace(s1)
		s1r := []rune(s1)
		sr := make([]rune, 0, len(s1r))
		var first bool = false
		for _, r := range s1r {
			sp := unicode.IsSpace(r)
			if first && sp {
				sr = append(sr, ' ')
				first = false
			} else if !sp {
				sr = append(sr, r)
				first = true
			}
		}
		return string(sr), nil
	case "UPPER":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		return strings.ToUpper(s1), nil
	case "ISBLANK":
		NumOfParams(args, 1)
		if args[0] == nil {
			return true, nil
		} else if s, ok := args[0].(string); ok && s == "" {
			return true, nil
		}
		return false, nil
	case "VALUE":
		NumOfParams(args, 1)
		s1 := MustBeString(args, 0)
		f, ok := new(big.Rat).SetString(s1)
		if ok {
			return f, nil
		}
		panic(fmt.Sprint("not a number", s1))
	// salesforce date functions. DATEVALUE accepts additional format parameter
	case "DATE":
		NumOfParams(args, 3)
		n1 := GetNumberAsInt(args, 0)
		n2 := GetNumberAsInt(args, 1)
		n3 := GetNumberAsInt(args, 2)
		return time.Date(n1, time.Month(n2), n3, 0, 0, 0, 0, context.cast().localTimeZone), nil
	case "DATEVALUE":
		s1 := MustBeString(args, 0)
		s2 := "2006-01-02"
		if len(args) > 1 {
			NumOfParams(args, 2)
			s2 = MustBeString(args, 1)
		}
		return context.ParseDate(s2, s1)
	case "DAY":
		NumOfParams(args, 1)
		d1 := MustBeDate(args, 0)
		return new(big.Rat).SetInt64(int64(d1.Day())), nil
	case "MONTH":
		NumOfParams(args, 1)
		d1 := MustBeDate(args, 0)
		return new(big.Rat).SetInt64(int64(d1.Month())), nil
	case "NOW":
		NumOfParams(args, 0)
		return time.Now().In(context.cast().localTimeZone), nil
	case "TODAY":
		NumOfParams(args, 0)
		return time.Now().In(context.cast().localTimeZone).Truncate(time.Hour * 24), nil
	case "YEAR":
		NumOfParams(args, 1)
		d1 := MustBeDate(args, 0)
		return new(big.Rat).SetInt64(int64(d1.Year())), nil
	// salesforce numerical functions: TBC
	case "ABS":
		NumOfParams(args, 1)
		n1 := MustBeNumber(args, 0)
		return n1.Abs(n1), nil
	// salesforce logical functions: AND, NOT, OR should not be used, use logical operators
	case "CASE":
		MinNumOfParams(args, 3)
		i := 1
		for ; i < len(args)-1; i += 2 {
			switch args[0].(type) {
			case nil:
				if args[i] == nil {
					return args[i+1], nil
				}
			case *big.Rat:
				n := MustBeNumber(args, i)
				if args[0].(*big.Rat).Cmp(n) == 0 {
					return args[i+1], nil
				}
			case string:
				s := MustBeString(args, i)
				if args[0].(string) == s {
					return args[i+1], nil
				}
			case bool:
				b1 := MustBeBool(args, i)
				if args[0].(bool) == b1 {
					return args[i+1], nil
				}
			default:
				panic(fmt.Sprint("unsupported type:", args[i]))
			}
		}
		if i < len(args) {
			return args[i], nil
		}
		panic("missing default value")
	case "IF":
		NumOfParams(args, 3)
		cond := MustBeBool(args, 0)
		if cond {
			return args[1], nil
		}
		return args[2], nil
	case "NULLVALUE":
		NumOfParams(args, 2)
		if args[0] == nil {
			return args[1], nil
		}
		return args[0], nil
	// additional convenience functions for text
	// join(delimiter, strings...) joins non empty strings listed as arguments using delimiter (empty strings are skipped)
	case "JOIN":
		var a []string
		for i, v := range args[1:] {
			if v != nil {
				s := MustBeString(args, i+1)
				if s != "" {
					a = append(a, s)
				}
			}
		}
		s1 := MustBeString(args, 0)
		return strings.Join(a, s1), nil
	}
	panic("unknown function")
}

func substr(s string, b int, l int) string {
	r := []rune(s)
	if len(r) == 0 || l == 0 {
		return ""
	}
	if b < 0 {
		if -b >= len(r) {
			b = 0
		} else {
			b = len(r) + b
		}
	}
	if l < 0 {
		l = len(r)
	}
	e := b + l
	if e > len(r) {
		e = len(r)
	}
	return string(r[b:e])
}
<|endoftext|>


<commit_before>package commands

import (
	"io"
	"strings"

	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/core/coreunix"
	path "github.com/ipfs/go-ipfs/path"
	tar "github.com/ipfs/go-ipfs/tar"
)

var TarCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Utility functions for tar files in ipfs.",
	},

	Subcommands: map[string]*cmds.Command{
		"add": tarAddCmd,
		"cat": tarCatCmd,
	},
}

var tarAddCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Import a tar file into ipfs.",
		ShortDescription: `
'ipfs tar add' will parse a tar file and create a merkledag structure to represent it.
`,
	},

	Arguments: []cmds.Argument{
		cmds.FileArg("file", true, false, "Tar file to add.").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		fi, err := req.Files().NextFile()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		node, err := tar.ImportTar(fi, nd.DAG)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		k, err := node.Key()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		fi.FileName()
		res.SetOutput(&coreunix.AddedObject{
			Name: fi.FileName(),
			Hash: k.B58String(),
		})
	},
	Type: coreunix.AddedObject{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			o := res.Output().(*coreunix.AddedObject)
			return strings.NewReader(o.Hash + "\n"), nil
		},
	},
}

var tarCatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Export a tar file from ipfs.",
		ShortDescription: `
'ipfs tar cat' will export a tar file from a previously imported one in ipfs
`,
	},

	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "Ipfs path of archive to export.").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		root, err := core.Resolve(req.Context(), nd, p)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		r, err := tar.ExportTar(req.Context(), root, nd.DAG)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(r)
	},
}
<commit_msg>Capped IPFS, added period<commit_after>package commands

import (
	"io"
	"strings"

	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/core/coreunix"
	path "github.com/ipfs/go-ipfs/path"
	tar "github.com/ipfs/go-ipfs/tar"
)

var TarCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Utility functions for tar files in IPFS.",
	},

	Subcommands: map[string]*cmds.Command{
		"add": tarAddCmd,
		"cat": tarCatCmd,
	},
}

var tarAddCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Import a tar file into ipfs.",
		ShortDescription: `
'ipfs tar add' will parse a tar file and create a merkledag structure to represent it.
`,
	},

	Arguments: []cmds.Argument{
		cmds.FileArg("file", true, false, "Tar file to add.").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		fi, err := req.Files().NextFile()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		node, err := tar.ImportTar(fi, nd.DAG)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		k, err := node.Key()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		fi.FileName()
		res.SetOutput(&coreunix.AddedObject{
			Name: fi.FileName(),
			Hash: k.B58String(),
		})
	},
	Type: coreunix.AddedObject{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			o := res.Output().(*coreunix.AddedObject)
			return strings.NewReader(o.Hash + "\n"), nil
		},
	},
}

var tarCatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Export a tar file from IPFS.",
		ShortDescription: `
'ipfs tar cat' will export a tar file from a previously imported one in IPFS.
`,
	},

	Arguments: []cmds.Argument{
		cmds.StringArg("path", true, false, "IPFS path of archive to export.").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		root, err := core.Resolve(req.Context(), nd, p)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		r, err := tar.ExportTar(req.Context(), root, nd.DAG)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(r)
	},
}
<|endoftext|>


<commit_before><commit_msg>cv: install Gerrit client to prod context.<commit_after><|endoftext|>


<commit_before><commit_msg>Revert "wip"<commit_after><|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"time"
	"github.com/nsf/termbox-go"
)

func getRoot() string {
	if len(flag.Args()) == 0 {
		return "."
	} else {
		return flag.Arg(0)
	}
}

// hf --cmd=emacs ~/go/src/github.com/hugows/ happy
var cmd = flag.String("cmd", "vim", "command to run")

// strings.Replace(tw.Text, " ", "+", -1)

func runCmdWithArgs(f string) {
	cmd := exec.Command(*cmd, f)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}

const pauseAfterKeypress = (1500 * time.Millisecond)

func main() {
	flag.Parse()

	var results Results

	root := getRoot()
	fi, err := os.Stat(root)
	if err != nil {
		fmt.Println(err)
		return
	}

	if !fi.IsDir() {
		fmt.Println(root, "is NOT a folder")
		return
	}

	err = termbox.Init()
	if err != nil {
		panic(err)
	}
	termbox.SetInputMode(termbox.InputEsc)

	fileChan := walkFiles(getRoot())

	// for filename := range fileChan {
	// results.Insert(<-fileChan)
	// }

	// 	a, b := score(filexname, flag.Arg(1))
	// 	if a >= 0 && a < 100 {

	// 		if first {
	// 			runcmd := exec.Command(*cmd, filename)
	// 			runcmd.Stdin = os.Stdin
	// 			runcmd.Stdout = os.Stdout
	// 			err := runcmd.Run()
	// 			if err != nil {
	// 				log.Fatal(err)
	// 			}
	// 			first = false
	// 		}

	// 		fmt.Printf("%30s %4d %v\n", filename, a, b)
	// 	}
	// }

	var timeLastUser time.Time
	resultsQueue := make([]string, 0, 100)
	w, h := termbox.Size()
	modeline := NewModeline(0, h-1, w)
	cmdline := new(CommandLine)

	modeline.Draw(&results)
	cmdline.Draw(0, h-2, w)
	results.SetSize(0, 0, w, h-2)
	results.CopyAll()
	results.Draw()
	termbox.Flush()

	termboxEventChan := make(chan termbox.Event)

	go func() {
		for {
			termboxEventChan <- termbox.PollEvent()
		}
	}()

	timer := time.NewTimer(1 * time.Hour)

	// Command name is:
	// os.Args[0]

	var r string

	for {
		select {
		case <-timer.C:
			for len(resultsQueue) > 0 {
				r, resultsQueue = resultsQueue[len(resultsQueue)-1], resultsQueue[:len(resultsQueue)-1]
				results.Insert(r)
			}
			resultsQueue = nil
			results.Filter(modeline.Contents())
			timer = time.NewTimer(1 * time.Hour)

		case filename, ok := <-fileChan:
			if ok {
				if time.Since(timeLastUser) > pauseAfterKeypress {
					modeline.Unpause()
					results.Insert(filename)
					results.Filter(modeline.Contents())
				} else {
					modeline.Pause()
					resultsQueue = append(resultsQueue, filename)
				}
			} else {
				fileChan = nil
			}

		case ev := <-termboxEventChan:
			timeLastUser = time.Now()
			if fileChan != nil {
				timer.Reset(pauseAfterKeypress)
			} else {
				modeline.Unpause()
			}

			switch ev.Type {
			case termbox.EventKey:
				switch ev.Key {
				case termbox.KeyEsc, termbox.KeyCtrlC:
					termbox.Close()
					return
				case termbox.KeyEnter:
					termbox.Close()
					// runCmdWithArgs(results.FormatSelected())
					return
				case termbox.KeyCtrlT:
					results.ToggleMarkAll()
				case termbox.KeyArrowUp, termbox.KeyCtrlP:
					cmdline.Update(results.SelectPrevious().displayContents)
				case termbox.KeyArrowDown, termbox.KeyCtrlN:
					cmdline.Update(results.SelectNext().displayContents)
				case termbox.KeyArrowLeft, termbox.KeyCtrlB:
					modeline.input.MoveCursorOneRuneBackward()
				case termbox.KeyArrowRight, termbox.KeyCtrlF:
					modeline.input.MoveCursorOneRuneForward()
				case termbox.KeyBackspace, termbox.KeyBackspace2:
					modeline.input.DeleteRuneBackward()
					results.Filter(modeline.Contents())
				case termbox.KeyDelete, termbox.KeyCtrlD:
					modeline.input.DeleteRuneForward()
					results.Filter(modeline.Contents())
				case termbox.KeySpace:
					results.ToggleMark()
				case termbox.KeyCtrlK:
					modeline.input.DeleteTheRestOfTheLine()
					results.Filter(modeline.Contents())
				case termbox.KeyHome, termbox.KeyCtrlA:
					modeline.input.MoveCursorToBeginningOfTheLine()
				case termbox.KeyEnd, termbox.KeyCtrlE:
					modeline.input.MoveCursorToEndOfTheLine()
				default:
					if ev.Ch != 0 {
						modeline.input.InsertRune(ev.Ch)
						results.Filter(modeline.Contents())
					}
				}
			case termbox.EventError:
				panic(ev.Err)
			}
		}

		modeline.Draw(&results)
		cmdline.Draw(0, h-2, w)
		results.Draw()
		termbox.Flush()
	}

}
<commit_msg>Make sure the beginning is unpaused<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"time"
	"github.com/nsf/termbox-go"
)

func getRoot() string {
	if len(flag.Args()) == 0 {
		return "."
	} else {
		return flag.Arg(0)
	}
}

// hf --cmd=emacs ~/go/src/github.com/hugows/ happy
var cmd = flag.String("cmd", "vim", "command to run")

// strings.Replace(tw.Text, " ", "+", -1)

func runCmdWithArgs(f string) {
	cmd := exec.Command(*cmd, f)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}

const pauseAfterKeypress = (1500 * time.Millisecond)

func main() {
	flag.Parse()

	var results Results

	root := getRoot()
	fi, err := os.Stat(root)
	if err != nil {
		fmt.Println(err)
		return
	}

	if !fi.IsDir() {
		fmt.Println(root, "is NOT a folder")
		return
	}

	err = termbox.Init()
	if err != nil {
		panic(err)
	}
	termbox.SetInputMode(termbox.InputEsc)

	fileChan := walkFiles(getRoot())

	// for filename := range fileChan {
	// results.Insert(<-fileChan)
	// }

	// 	a, b := score(filexname, flag.Arg(1))
	// 	if a >= 0 && a < 100 {

	// 		if first {
	// 			runcmd := exec.Command(*cmd, filename)
	// 			runcmd.Stdin = os.Stdin
	// 			runcmd.Stdout = os.Stdout
	// 			err := runcmd.Run()
	// 			if err != nil {
	// 				log.Fatal(err)
	// 			}
	// 			first = false
	// 		}

	// 		fmt.Printf("%30s %4d %v\n", filename, a, b)
	// 	}
	// }

	var timeLastUser time.Time
	resultsQueue := make([]string, 0, 100)
	w, h := termbox.Size()
	modeline := NewModeline(0, h-1, w)
	cmdline := new(CommandLine)

	modeline.Draw(&results)
	cmdline.Draw(0, h-2, w)
	results.SetSize(0, 0, w, h-2)
	results.CopyAll()
	results.Draw()
	termbox.Flush()

	termboxEventChan := make(chan termbox.Event)

	go func() {
		for {
			termboxEventChan <- termbox.PollEvent()
		}
	}()

	timer := time.NewTimer(1 * time.Hour)

	// Command name is:
	// os.Args[0]

	var r string
	timeLastUser = time.Now().Add(- 1*time.Hour)

	for {
		select {
		case <-timer.C:
			for len(resultsQueue) > 0 {
				r, resultsQueue = resultsQueue[len(resultsQueue)-1], resultsQueue[:len(resultsQueue)-1]
				results.Insert(r)
			}
			resultsQueue = nil
			results.Filter(modeline.Contents())
			timer = time.NewTimer(1 * time.Hour)

		case filename, ok := <-fileChan:
			if ok {
				if time.Since(timeLastUser) > pauseAfterKeypress {
					modeline.Unpause()
					results.Insert(filename)
					results.Filter(modeline.Contents())
				} else {
					modeline.Pause()
					resultsQueue = append(resultsQueue, filename)
				}
			} else {
				fileChan = nil
			}

		case ev := <-termboxEventChan:
			timeLastUser = time.Now()
			if fileChan != nil {
				timer.Reset(pauseAfterKeypress)
			} else {
				modeline.Unpause()
			}

			switch ev.Type {
			case termbox.EventKey:
				switch ev.Key {
				case termbox.KeyEsc, termbox.KeyCtrlC:
					termbox.Close()
					return
				case termbox.KeyEnter:
					termbox.Close()
					// runCmdWithArgs(results.FormatSelected())
					return
				case termbox.KeyCtrlT:
					results.ToggleMarkAll()
				case termbox.KeyArrowUp, termbox.KeyCtrlP:
					cmdline.Update(results.SelectPrevious().displayContents)
				case termbox.KeyArrowDown, termbox.KeyCtrlN:
					cmdline.Update(results.SelectNext().displayContents)
				case termbox.KeyArrowLeft, termbox.KeyCtrlB:
					modeline.input.MoveCursorOneRuneBackward()
				case termbox.KeyArrowRight, termbox.KeyCtrlF:
					modeline.input.MoveCursorOneRuneForward()
				case termbox.KeyBackspace, termbox.KeyBackspace2:
					modeline.input.DeleteRuneBackward()
					results.Filter(modeline.Contents())
				case termbox.KeyDelete, termbox.KeyCtrlD:
					modeline.input.DeleteRuneForward()
					results.Filter(modeline.Contents())
				case termbox.KeySpace:
					results.ToggleMark()
				case termbox.KeyCtrlK:
					modeline.input.DeleteTheRestOfTheLine()
					results.Filter(modeline.Contents())
				case termbox.KeyHome, termbox.KeyCtrlA:
					modeline.input.MoveCursorToBeginningOfTheLine()
				case termbox.KeyEnd, termbox.KeyCtrlE:
					modeline.input.MoveCursorToEndOfTheLine()
				default:
					if ev.Ch != 0 {
						modeline.input.InsertRune(ev.Ch)
						results.Filter(modeline.Contents())
					}
				}
			case termbox.EventError:
				panic(ev.Err)
			}
		}

		modeline.Draw(&results)
		cmdline.Draw(0, h-2, w)
		results.Draw()
		termbox.Flush()
	}

}
<|endoftext|>


<commit_before>package fs

import (
	"os"
	"path/filepath"
	"strings"

	"polydawn.net/repeatr/lib/treewalk"
)

type WalkFunc func(filenode *FilewalkNode) error

/*
	Walks a filesystem.

	This is much like the standard library's `path/filepath.Walk`, except
	it's based on `treewalk`, which means it supports both pre- and post-order
	traversals.

	All paths begin in `./`, and directory names are slash-suffixed.
	E.g. you'll see a series like `{"./", "./a/", "./a/b"}`, etc.
	This matches the behaviors described by `Normalize` in the `lib/fshash`.

	Symlinks are not followed.

	The traversal order of siblings is *not* guaranteed, and is *not* necessarily
	stable.

	Caveat: calling `node.NextChild()` during your walk results in undefined behavior.
*/
func Walk(basePath string, preVisit WalkFunc, postVisit WalkFunc) error {
	return treewalk.Walk(
		newFileWalkNode(basePath, "./"),
		func(node treewalk.Node) error {
			filenode := node.(*FilewalkNode)
			if preVisit != nil {
				if err := preVisit(filenode); err != nil {
					return err
				}
			}
			return filenode.prepareChildren(basePath)
		},
		func(node treewalk.Node) error {
			filenode := node.(*FilewalkNode)
			var err error
			if postVisit != nil {
				err = postVisit(filenode)
			}
			filenode.forgetChildren()
			return err
		},
	)
}

var _ treewalk.Node = &FilewalkNode{}

type FilewalkNode struct {
	Path string // relative to start
	Info os.FileInfo
	Err  error

	children []*FilewalkNode // note we didn't sort this
	itrIndex int             // next child offset
}

func (t *FilewalkNode) NextChild() treewalk.Node {
	if t.itrIndex >= len(t.children) {
		return nil
	}
	t.itrIndex++
	return t.children[t.itrIndex-1]
}

func newFileWalkNode(basePath, path string) (filenode *FilewalkNode) {
	filenode = &FilewalkNode{Path: path}
	filenode.Info, filenode.Err = os.Lstat(filepath.Join(basePath, path))
	if filenode.Info.IsDir() {
		if !strings.HasSuffix(filenode.Path, "/") {
			filenode.Path += "/"
		}
	}
	// don't expand the children until the previsit function
	// we don't want them all crashing into memory at once
	return
}

/*
	Expand next subtree.  Used in the pre-order visit step so we don't walk
	every dir up front.  `Walk()` wraps the user-defined pre-visit function
	to do this at the end.
*/
func (t *FilewalkNode) prepareChildren(basePath string) error {
	if !t.Info.IsDir() {
		return nil
	}
	f, err := os.Open(filepath.Join(basePath, t.Path))
	if err != nil {
		return err
	}
	names, err := f.Readdirnames(-1)
	f.Close()
	if err != nil {
		return err
	}
	t.children = make([]*FilewalkNode, len(names))
	for i, name := range names {
		t.children[i] = newFileWalkNode(basePath, "./"+filepath.Join(t.Path, name))
	}
	return nil
}

/*
	Used in the post-order visit step so we don't continuously consume more
	memory as we walk.  `Walk()` wraps the user-defined post-visit function
	to do this at the end.
*/
func (t *FilewalkNode) forgetChildren() {
	t.children = nil
}
<commit_msg>Fix nil panic from filewalk that errored in lstat.<commit_after>package fs

import (
	"os"
	"path/filepath"
	"strings"

	"polydawn.net/repeatr/lib/treewalk"
)

type WalkFunc func(filenode *FilewalkNode) error

/*
	Walks a filesystem.

	This is much like the standard library's `path/filepath.Walk`, except
	it's based on `treewalk`, which means it supports both pre- and post-order
	traversals.

	All paths begin in `./`, and directory names are slash-suffixed.
	E.g. you'll see a series like `{"./", "./a/", "./a/b"}`, etc.
	This matches the behaviors described by `Normalize` in the `lib/fshash`.

	Symlinks are not followed.

	The traversal order of siblings is *not* guaranteed, and is *not* necessarily
	stable.

	Caveat: calling `node.NextChild()` during your walk results in undefined behavior.
*/
func Walk(basePath string, preVisit WalkFunc, postVisit WalkFunc) error {
	return treewalk.Walk(
		newFileWalkNode(basePath, "./"),
		func(node treewalk.Node) error {
			filenode := node.(*FilewalkNode)
			if preVisit != nil {
				if err := preVisit(filenode); err != nil {
					return err
				}
			}
			return filenode.prepareChildren(basePath)
		},
		func(node treewalk.Node) error {
			filenode := node.(*FilewalkNode)
			var err error
			if postVisit != nil {
				err = postVisit(filenode)
			}
			filenode.forgetChildren()
			return err
		},
	)
}

var _ treewalk.Node = &FilewalkNode{}

type FilewalkNode struct {
	Path string // relative to start
	Info os.FileInfo
	Err  error

	children []*FilewalkNode // note we didn't sort this
	itrIndex int             // next child offset
}

func (t *FilewalkNode) NextChild() treewalk.Node {
	if t.itrIndex >= len(t.children) {
		return nil
	}
	t.itrIndex++
	return t.children[t.itrIndex-1]
}

func newFileWalkNode(basePath, path string) (filenode *FilewalkNode) {
	filenode = &FilewalkNode{Path: path}
	filenode.Info, filenode.Err = os.Lstat(filepath.Join(basePath, path))
	if filenode.Err == nil && filenode.Info.IsDir() {
		if !strings.HasSuffix(filenode.Path, "/") {
			filenode.Path += "/"
		}
	}
	// don't expand the children until the previsit function
	// we don't want them all crashing into memory at once
	return
}

/*
	Expand next subtree.  Used in the pre-order visit step so we don't walk
	every dir up front.  `Walk()` wraps the user-defined pre-visit function
	to do this at the end.
*/
func (t *FilewalkNode) prepareChildren(basePath string) error {
	if !t.Info.IsDir() {
		return nil
	}
	f, err := os.Open(filepath.Join(basePath, t.Path))
	if err != nil {
		return err
	}
	names, err := f.Readdirnames(-1)
	f.Close()
	if err != nil {
		return err
	}
	t.children = make([]*FilewalkNode, len(names))
	for i, name := range names {
		t.children[i] = newFileWalkNode(basePath, "./"+filepath.Join(t.Path, name))
	}
	return nil
}

/*
	Used in the post-order visit step so we don't continuously consume more
	memory as we walk.  `Walk()` wraps the user-defined post-visit function
	to do this at the end.
*/
func (t *FilewalkNode) forgetChildren() {
	t.children = nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.6
// Obtains latest observation for NBDC Station 46026
// Saves observation to database
// Obtains next tide from database
// Tweets observation and tide prediction from @SFBuoy
// See README.md for setup information
// Note tide data from github.com/johnbeil/tidecrawler

package main

import (
	"database/sql"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// First two rows of text file, fixed width delimited, used for debugging
const header = "#YY  MM DD hh mm WDIR WSPD GST  WVHT   DPD   APD MWD   PRES  ATMP  WTMP  DEWP  VIS PTDY  TIDE\n#yr  mo dy hr mn degT m/s  m/s     m   sec   sec degT   hPa  degC  degC  degC  nmi  hPa    ft"

// URL for SF Buoy Observations
const noaaURL = "http://www.ndbc.noaa.gov/data/realtime2/46026.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	AirTemperature        float64
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseURL      string `json:"DatabaseUrl"`
	DatabaseUser     string `json:"DatabaseUser"`
	DatabasePassword string `json:"DatabasePassword"`
	DatabaseName     string `json:"DatabaseName"`
}

// Tide stores a tide prediction from the database
type Tide struct {
	Date         string
	Day          string
	Time         string
	PredictionFt float64
	PredictionCm int64
	HighLow      string
}

// Variable for database
var db *sql.DB

// BuoyBot execution
func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	dbinfo := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
		config.DatabaseUser, config.DatabasePassword, config.DatabaseURL, config.DatabaseName)
	var err error
	db, err = sql.Open("postgres", dbinfo)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Parse command line argument.
	arg := flag.Bool("test", false, "a boolean value")
	flag.Parse()

	// Get current observation and store in struct
	var observation Observation
	observation = getObservation()

	// Save current observation in database
	saveObservation(observation)

	// Obtain next tide from database
	tide := getTide()

	// Format tide
	tideOutput := processTide(tide)

	// Format observation given Observation and tideOutput
	observationOutput := formatObservation(observation, tideOutput)

	// Tweet observation unless test argument passed via command line.
	if *arg == true {
		fmt.Println("Test mode: Tweet disabled.")
		fmt.Println(observationOutput)
	} else {
		tweetCurrent(config, observationOutput)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation() Observation {
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.AirTemperature, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o string) {
	fmt.Println("Preparing to tweet observation...")
	api := anaconda.NewTwitterApiWithCredentials(config.Token, config.TokenSecret, config.ConsumerKey, config.ConsumerSecret)
	tweet, err := api.PostTweet(o, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println("Tweet posted:")
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// Load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	file, _ := os.Open(configpath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		log.Fatal("Error loading config.json:", err)
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Get prior observation and store in struct
	var lastObservation Observation
	lastObservation = getLastObservation()
	fmt.Printf("%+v\n", lastObservation)

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// Convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// Process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// Convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// Convert wave height from meters to feet
	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// Convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// Convert air temp from C to F
	airtempC, _ := strconv.ParseFloat(datafield[13], 64)
	airtempF := airtempC*9/5 + 32
	airtempF = RoundPlus(airtempF, 1)

	// Convert water temp from C to F
	watertempC, err := strconv.ParseFloat(datafield[14], 64)
	if err != nil {
		fmt.Println(err)
	}
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		fmt.Println(err)
		o.AveragePeriod = 0
		// log.Fatal("o.DominantWavePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		fmt.Println(err)
		o.AveragePeriod = 0
		// log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.AirTemperature = airtempF
	o.WaterTemperature = watertempF

	// Return populated observation struct
	return o
}

// Given Observation and tide string, returns formatted text for tweet
func formatObservation(o Observation, tide string) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\n", tide, "\nTemp: Air ", o.AirTemperature, "F / Water: ", o.WaterTemperature, "F")
	return output
}

//getLastObservation selects the prior observation from the database and returns an Observation struct
func getLastObservation() Observation {
	var o Observation
	err := db.QueryRow("select observationtime, winddirection, windspeed, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature from observations order by observationtime desc limit 1").Scan(&o.Date, &o.WindDirection, &o.WindSpeed, &o.SignificantWaveHeight, &o.DominantWavePeriod, &o.AveragePeriod, &o.MeanWaveDirection, &o.AirTemperature, &o.WaterTemperature)
	if err != nil {
		log.Fatal("getLastObservation function error:", err)
	}
	return o
}

// getTide selects the next tide prediction from the database and returns a Tide struct
// server time and
func getTide() Tide {
	var tide Tide
	err := db.QueryRow("select date, day, time, predictionft, highlow from tidedata where datetime >= current_timestamp - interval '8 hours' order by datetime limit 1").Scan(&tide.Date, &tide.Day, &tide.Time, &tide.PredictionFt, &tide.HighLow)
	if err != nil {
		log.Fatal("getTide function error:", err)
	}
	return tide
}

// processTide returns a formatted string given a Tide struct
func processTide(t Tide) string {
	if t.HighLow == "H" {
		t.HighLow = "High"
	} else {
		t.HighLow = "Low"
	}
	s := "Tide: " + t.HighLow + " " + strconv.FormatFloat(float64(t.PredictionFt), 'f', 1, 64) + "ft at " + t.Time
	// fmt.Println(s)
	return s
}

// Round input to nearest integer given Float64 and return Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a specified number of decimals given Int and Float64, returning Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// Given degrees returns cardinal direction or error message
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}
<commit_msg>print observation struct.<commit_after>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.6
// Obtains latest observation for NBDC Station 46026
// Saves observation to database
// Obtains next tide from database
// Tweets observation and tide prediction from @SFBuoy
// See README.md for setup information
// Note tide data from github.com/johnbeil/tidecrawler

package main

import (
	"database/sql"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// First two rows of text file, fixed width delimited, used for debugging
const header = "#YY  MM DD hh mm WDIR WSPD GST  WVHT   DPD   APD MWD   PRES  ATMP  WTMP  DEWP  VIS PTDY  TIDE\n#yr  mo dy hr mn degT m/s  m/s     m   sec   sec degT   hPa  degC  degC  degC  nmi  hPa    ft"

// URL for SF Buoy Observations
const noaaURL = "http://www.ndbc.noaa.gov/data/realtime2/46026.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	AirTemperature        float64
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseURL      string `json:"DatabaseUrl"`
	DatabaseUser     string `json:"DatabaseUser"`
	DatabasePassword string `json:"DatabasePassword"`
	DatabaseName     string `json:"DatabaseName"`
}

// Tide stores a tide prediction from the database
type Tide struct {
	Date         string
	Day          string
	Time         string
	PredictionFt float64
	PredictionCm int64
	HighLow      string
}

// Variable for database
var db *sql.DB

// BuoyBot execution
func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	dbinfo := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
		config.DatabaseUser, config.DatabasePassword, config.DatabaseURL, config.DatabaseName)
	var err error
	db, err = sql.Open("postgres", dbinfo)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Parse command line argument.
	arg := flag.Bool("test", false, "a boolean value")
	flag.Parse()

	// Get current observation and store in struct
	var observation Observation
	observation = getObservation()

	// Save current observation in database
	saveObservation(observation)

	// Obtain next tide from database
	tide := getTide()

	// Format tide
	tideOutput := processTide(tide)

	// Format observation given Observation and tideOutput
	observationOutput := formatObservation(observation, tideOutput)

	// Tweet observation unless test argument passed via command line.
	if *arg == true {
		fmt.Println("Test mode: Tweet disabled.")
		fmt.Println(observationOutput)
	} else {
		tweetCurrent(config, observationOutput)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation() Observation {
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.AirTemperature, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o string) {
	fmt.Println("Preparing to tweet observation...")
	api := anaconda.NewTwitterApiWithCredentials(config.Token, config.TokenSecret, config.ConsumerKey, config.ConsumerSecret)
	tweet, err := api.PostTweet(o, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println("Tweet posted:")
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// Load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	file, _ := os.Open(configpath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		log.Fatal("Error loading config.json:", err)
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Get prior observation and store in struct
	var lastObservation Observation
	lastObservation = getLastObservation()
	fmt.Printf("%+v\n", lastObservation)

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// Convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// Process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// Convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// Convert wave height from meters to feet

	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// Convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// Convert air temp from C to F
	airtempC, _ := strconv.ParseFloat(datafield[13], 64)
	airtempF := airtempC*9/5 + 32
	airtempF = RoundPlus(airtempF, 1)

	// Convert water temp from C to F
	watertempC, err := strconv.ParseFloat(datafield[14], 64)
	if err != nil {
		fmt.Println(err)
	}
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		fmt.Println(err)
		o.AveragePeriod = 0
		// log.Fatal("o.DominantWavePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		fmt.Println(err)
		o.AveragePeriod = 0
		// log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.AirTemperature = airtempF
	o.WaterTemperature = watertempF

	// Print loaded observations
	fmt.Printf("%+v\n", o)

	// Return populated observation struct
	return o
}

// Given Observation and tide string, returns formatted text for tweet
func formatObservation(o Observation, tide string) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\n", tide, "\nTemp: Air ", o.AirTemperature, "F / Water: ", o.WaterTemperature, "F")
	return output
}

//getLastObservation selects the prior observation from the database and returns an Observation struct
func getLastObservation() Observation {
	var o Observation
	err := db.QueryRow("select observationtime, winddirection, windspeed, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature from observations order by observationtime desc limit 1").Scan(&o.Date, &o.WindDirection, &o.WindSpeed, &o.SignificantWaveHeight, &o.DominantWavePeriod, &o.AveragePeriod, &o.MeanWaveDirection, &o.AirTemperature, &o.WaterTemperature)
	if err != nil {
		log.Fatal("getLastObservation function error:", err)
	}
	return o
}

// getTide selects the next tide prediction from the database and returns a Tide struct
// server time and
func getTide() Tide {
	var tide Tide
	err := db.QueryRow("select date, day, time, predictionft, highlow from tidedata where datetime >= current_timestamp - interval '8 hours' order by datetime limit 1").Scan(&tide.Date, &tide.Day, &tide.Time, &tide.PredictionFt, &tide.HighLow)
	if err != nil {
		log.Fatal("getTide function error:", err)
	}
	return tide
}

// processTide returns a formatted string given a Tide struct
func processTide(t Tide) string {
	if t.HighLow == "H" {
		t.HighLow = "High"
	} else {
		t.HighLow = "Low"
	}
	s := "Tide: " + t.HighLow + " " + strconv.FormatFloat(float64(t.PredictionFt), 'f', 1, 64) + "ft at " + t.Time
	// fmt.Println(s)
	return s
}

// Round input to nearest integer given Float64 and return Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a specified number of decimals given Int and Float64, returning Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// Given degrees returns cardinal direction or error message
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.1
// Obtains latest observation for NBDC Station 46026
// Saves observation to database
// Tweets observation from @SFBuoy
// See README.md for setup information

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// First two rows of text file, fixed width delimited, used for debugging
const header = "#YY  MM DD hh mm WDIR WSPD GST  WVHT   DPD   APD MWD   PRES  ATMP  WTMP  DEWP  VIS PTDY  TIDE\n#yr  mo dy hr mn degT m/s  m/s     m   sec   sec degT   hPa  degC  degC  degC  nmi  hPa    ft"

// URL for SF Buoy Observations
const noaaURL = "http://www.ndbc.noaa.gov/data/realtime2/46026.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	AirTemperature        float64
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseUrl      string `json:"DatabaseUrl"`
	DatabaseUser     string `json:"DatabaseUser"`
	DatabasePassword string `json:"DatabasePassword"`
	DatabaseName     string `json:"DatabaseName"`
}

// Tide stores a tide prediction from the database
type Tide struct {
	Date         string
	Day          string
	Time         string
	PredictionFt float64
	PredictionCm int64
	HighLow      string
}

// Variable for database
var db *sql.DB

func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	dbinfo := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
		config.DatabaseUser, config.DatabasePassword, config.DatabaseUrl, config.DatabaseName)
	var err error
	db, err = sql.Open("postgres", dbinfo)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Get latest observation and store in struct
	var observation Observation
	observation = getObservation()

	// Save latest observation in database
	saveObservation(observation)

	// Obtain next tide from database
	tide := getTide()

	// Format tide for inclusion in tweet
	tidestring := processTide(tide)

	// Tweet observation at 0000, 0600, 0900, 1200, 1500, 1800 PST
	t := time.Now()
	if t.Hour() == 0 || t.Hour() == 6 || t.Hour() == 9 || t.Hour() == 12 || t.Hour() == 15 || t.Hour() == 18 {
		tweetCurrent(config, observation)
	} else {
		fmt.Println("Not at update interval - not tweeting.")
		fmt.Println("Observation is:\n", observation)
		fmt.Println(tidestring)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation() Observation {
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.AirTemperature, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o Observation) {
	var api *anaconda.TwitterApi
	api = anaconda.NewTwitterApi(config.Token, config.TokenSecret)
	anaconda.SetConsumerKey(config.ConsumerKey)
	anaconda.SetConsumerSecret(config.ConsumerSecret)
	observationOutput := formatObservation(o)
	tweet, err := api.PostTweet(observationOutput, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	file, _ := os.Open(configpath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		log.Fatal("Error loading config.json:", err)
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// convert wave height from meters to feet
	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// convert air temp from C to F
	airtempC, _ := strconv.ParseFloat(datafield[13], 64)
	airtempF := airtempC*9/5 + 32
	airtempF = RoundPlus(airtempF, 1)

	// convert water temp from C to F
	watertempC, _ := strconv.ParseFloat(datafield[14], 64)
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.AirTemperature = airtempF
	o.WaterTemperature = watertempF

	return o
}

// Given Observation returns formatted text for tweet
func formatObservation(o Observation) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\nTemp: Air ", o.AirTemperature, "F / Water: ", o.WaterTemperature, "F")
	return output
}

// given degrees returns cardinal direction
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}

// Round input to nearest integer given Float64 and returns Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a defined number of decimals given an Int and Float64, and returns a Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// getTide selects the next tide prediction from the database and return a Tide struct
func getTide() Tide {
	var tide Tide
	err := db.QueryRow("select date, day, time, predictionft, highlow from tidedata where datetime >= current_timestamp order by datetime limit 1").Scan(&tide.Date, &tide.Day, &tide.Time, &tide.PredictionFt, &tide.HighLow)
	if err != nil {
		log.Fatal("getTide function error:", err)
	}
	return tide
}

// processTide returns a formatted string give a Tide struct
func processTide(t Tide) string {
	if t.HighLow == "H" {
		t.HighLow = "High"
	} else {
		t.HighLow = "Low"
	}
	s := "Tide: " + t.HighLow + " " + strconv.FormatFloat(float64(t.PredictionFt), 'f', 1, 64) + "ft at " + t.Time
	// fmt.Println(s)
	return s
}
<commit_msg>Refactored code to include tide in tweet and improve logging.<commit_after>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.1
// Obtains latest observation for NBDC Station 46026
// Saves observation to database
// Tweets observation from @SFBuoy
// See README.md for setup information

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// First two rows of text file, fixed width delimited, used for debugging
const header = "#YY  MM DD hh mm WDIR WSPD GST  WVHT   DPD   APD MWD   PRES  ATMP  WTMP  DEWP  VIS PTDY  TIDE\n#yr  mo dy hr mn degT m/s  m/s     m   sec   sec degT   hPa  degC  degC  degC  nmi  hPa    ft"

// URL for SF Buoy Observations
const noaaURL = "http://www.ndbc.noaa.gov/data/realtime2/46026.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	AirTemperature        float64
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseUrl      string `json:"DatabaseUrl"`
	DatabaseUser     string `json:"DatabaseUser"`
	DatabasePassword string `json:"DatabasePassword"`
	DatabaseName     string `json:"DatabaseName"`
}

// Tide stores a tide prediction from the database
type Tide struct {
	Date         string
	Day          string
	Time         string
	PredictionFt float64
	PredictionCm int64
	HighLow      string
}

// Variable for database
var db *sql.DB

func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	dbinfo := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
		config.DatabaseUser, config.DatabasePassword, config.DatabaseUrl, config.DatabaseName)
	var err error
	db, err = sql.Open("postgres", dbinfo)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Get latest observation and store in struct
	var observation Observation
	observation = getObservation()

	// Save latest observation in database
	saveObservation(observation)

	// Obtain next tide from database
	tide := getTide()

	// Format tide
	tideOutput := processTide(tide)

	// Format observation given Observation and tideOutput
	observationOutput := formatObservation(observation, tideOutput)

	// Tweet observation at 0000, 0600, 0900, 1200, 1500, 1800 PST
	t := time.Now()
	if t.Hour() == 0 || t.Hour() == 6 || t.Hour() == 9 || t.Hour() == 12 || t.Hour() == 15 || t.Hour() == 18 {
		tweetCurrent(config, observationOutput)
	} else {
		fmt.Println("Not at update interval - not tweeting.")
		fmt.Println(observationOutput)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation() Observation {
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.AirTemperature, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o string) {
	fmt.Println("Preparing to tweet observation...")
	var api *anaconda.TwitterApi
	api = anaconda.NewTwitterApi(config.Token, config.TokenSecret)
	anaconda.SetConsumerKey(config.ConsumerKey)
	anaconda.SetConsumerSecret(config.ConsumerSecret)
	tweet, err := api.PostTweet(o, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println("Tweet posted:")
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	file, _ := os.Open(configpath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		log.Fatal("Error loading config.json:", err)
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// convert wave height from meters to feet
	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// convert air temp from C to F
	airtempC, _ := strconv.ParseFloat(datafield[13], 64)
	airtempF := airtempC*9/5 + 32
	airtempF = RoundPlus(airtempF, 1)

	// convert water temp from C to F
	watertempC, _ := strconv.ParseFloat(datafield[14], 64)
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.AirTemperature = airtempF
	o.WaterTemperature = watertempF

	return o
}

// Given Observation returns formatted text for tweet
func formatObservation(o Observation, t string) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\nTemp: Air ", o.AirTemperature, "F / Water: ", o.WaterTemperature, "F\n", t)
	return output
}

// given degrees returns cardinal direction
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}

// Round input to nearest integer given Float64 and returns Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a defined number of decimals given an Int and Float64, and returns a Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// getTide selects the next tide prediction from the database and return a Tide struct
func getTide() Tide {
	var tide Tide
	err := db.QueryRow("select date, day, time, predictionft, highlow from tidedata where datetime >= current_timestamp order by datetime limit 1").Scan(&tide.Date, &tide.Day, &tide.Time, &tide.PredictionFt, &tide.HighLow)
	if err != nil {
		log.Fatal("getTide function error:", err)
	}
	return tide
}

// processTide returns a formatted string give a Tide struct
func processTide(t Tide) string {
	if t.HighLow == "H" {
		t.HighLow = "High"
	} else {
		t.HighLow = "Low"
	}
	s := "Tide: " + t.HighLow + " " + strconv.FormatFloat(float64(t.PredictionFt), 'f', 1, 64) + "ft at " + t.Time
	// fmt.Println(s)
	return s
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"

	chaincfg "github.com/btcsuite/btcd/chaincfg"
	btcutil "github.com/btcsuite/btcutil"
	keychain "github.com/btcsuite/btcutil/hdkeychain"
	cli "github.com/urfave/cli"
	addrs "github.com/whyrusleeping/hdkeyutils/addrs"
)

func main() {
	app := cli.NewApp()
	app.Usage = "A command line utility for manipulating HD wallet keys"
	app.Version = "0.0.1"
	app.Commands = []cli.Command{
		privKeyCmd,
		pubKeyCmd,
		genKeyCmd,
	}
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

var genKeyCmd = cli.Command{
	Name:            "gen",
	Usage:           "generate an HD wallet key",
	SkipFlagParsing: true,
	Action: func(c *cli.Context) error {
		f, err := exec.LookPath("hdkeygen")
		if err != nil {
			return fmt.Errorf("could not find 'hdkeygen' binary: %s", err)
		}

		cmd := exec.Command(f, c.Args()...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin

		return cmd.Run()
	},
}

var privKeyCmd = cli.Command{
	Name:  "priv",
	Usage: "utilities for working with hd private keys",
	Subcommands: []cli.Command{
		getMasterPubCmd,
		getChildPrivKeyCmd,
	},
}

var getMasterPubCmd = cli.Command{
	Name:  "getmasterpub",
	Usage: "derive the master public key from the given master private key",
	Action: func(c *cli.Context) error {
		if !c.Args().Present() {
			return fmt.Errorf("must pass in private key file")
		}

		data, err := ioutil.ReadFile(c.Args().First())
		if err != nil {
			return err
		}

		key, err := keychain.NewKeyFromString(string(data))
		if err != nil {
			return err
		}

		if !key.IsPrivate() {
			return fmt.Errorf("given key was not a private key")
		}

		pubk, err := key.Neuter()
		if err != nil {
			return err
		}

		fmt.Print(pubk.String())
		return nil
	},
}

var getChildPrivKeyCmd = cli.Command{
	Name:  "child",
	Usage: "derive a child private key",
	Description: `Derive a child private key from the given heirarchically deterministic
   private key and print it out.

   By default, it outputs in wallet import format for use by bitcoin and zcash.
   Optionally, you may pass the --format flag with a parameter of 'eth' to
   signal that it should output a raw ecdsa key for use by ethereum.`,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "format",
			Usage: "output format for key (wif or eth)",
			Value: "wif",
		},
	},
	Action: func(c *cli.Context) error {
		format := c.String("format")

		if len(c.Args()) != 2 {
			return fmt.Errorf("must pass in private key and index")
		}

		i, err := strconv.Atoi(c.Args()[1])
		if err != nil {
			return err
		}

		data, err := ioutil.ReadFile(c.Args().First())
		if err != nil {
			return err
		}

		key, err := keychain.NewKeyFromString(string(data))
		if err != nil {
			return err
		}

		if !key.IsPrivate() {
			return fmt.Errorf("given key was not a private key")
		}

		childpriv, err := key.Child(uint32(i))
		if err != nil {
			return err
		}

		privk, err := childpriv.ECPrivKey()
		if err != nil {
			return err
		}

		switch format {
		case "wif":
			wif, err := btcutil.NewWIF(privk, &chaincfg.MainNetParams, false)
			if err != nil {
				return err
			}

			fmt.Println(wif.String())
		case "eth":
			fmt.Printf("%x\n", privk.Serialize())
		}
		return nil
	},
}

var pubKeyCmd = cli.Command{
	Name:  "pub",
	Usage: "tools for working with hd public keys",
	Subcommands: []cli.Command{
		getChildPubKeyCmd,
	},
}

var getChildPubKeyCmd = cli.Command{
	Name:  "child",
	Usage: "derive a child public key",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "format",
			Usage: "output format for key (btc, zec, or eth)",
			Value: "btc",
		},
	},
	Action: func(c *cli.Context) error {
		enc := c.String("format")

		if len(c.Args()) != 2 {
			return fmt.Errorf("must pass in public key and index")
		}

		i, err := strconv.Atoi(c.Args()[1])
		if err != nil {
			return err
		}

		data, err := ioutil.ReadFile(c.Args().First())
		if err != nil {
			return err
		}

		key, err := keychain.NewKeyFromString(string(data))
		if err != nil {
			return err
		}

		if key.IsPrivate() {
			return fmt.Errorf("given key was a private key, not public")
		}

		childpub, err := key.Child(uint32(i))
		if err != nil {
			return err
		}

		ecpub, err := childpub.ECPubKey()
		if err != nil {
			return err
		}

		switch enc {
		case "btc":
			fmt.Println(addrs.EncodeBitcoinPubkey(ecpub))
		case "zec":
			fmt.Println(addrs.EncodeZcashPubkey(ecpub))
		case "eth":
			fmt.Println(addrs.EncodeEthereumPubkey(ecpub))
		default:
			return fmt.Errorf("unrecognized output format: %s", enc)
		}
		return nil
	},
}
<commit_msg>add support for testnet encodings<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"

	chaincfg "github.com/btcsuite/btcd/chaincfg"
	btcutil "github.com/btcsuite/btcutil"
	keychain "github.com/btcsuite/btcutil/hdkeychain"
	cli "github.com/urfave/cli"
	addrs "github.com/whyrusleeping/hdkeyutils/addrs"
)

func main() {
	app := cli.NewApp()
	app.Usage = "A command line utility for manipulating HD wallet keys"
	app.Version = "0.0.1"
	app.Commands = []cli.Command{
		privKeyCmd,
		pubKeyCmd,
		genKeyCmd,
	}
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

var genKeyCmd = cli.Command{
	Name:            "gen",
	Usage:           "generate an HD wallet key",
	SkipFlagParsing: true,
	Action: func(c *cli.Context) error {
		f, err := exec.LookPath("hdkeygen")
		if err != nil {
			return fmt.Errorf("could not find 'hdkeygen' binary: %s", err)
		}

		cmd := exec.Command(f, c.Args()...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin

		return cmd.Run()
	},
}

var privKeyCmd = cli.Command{
	Name:  "priv",
	Usage: "utilities for working with hd private keys",
	Subcommands: []cli.Command{
		getMasterPubCmd,
		getChildPrivKeyCmd,
	},
}

var getMasterPubCmd = cli.Command{
	Name:  "getmasterpub",
	Usage: "derive the master public key from the given master private key",
	Action: func(c *cli.Context) error {
		if !c.Args().Present() {
			return fmt.Errorf("must pass in private key file")
		}

		data, err := ioutil.ReadFile(c.Args().First())
		if err != nil {
			return err
		}

		key, err := keychain.NewKeyFromString(string(data))
		if err != nil {
			return err
		}

		if !key.IsPrivate() {
			return fmt.Errorf("given key was not a private key")
		}

		pubk, err := key.Neuter()
		if err != nil {
			return err
		}

		fmt.Print(pubk.String())
		return nil
	},
}

var getChildPrivKeyCmd = cli.Command{
	Name:  "child",
	Usage: "derive a child private key",
	Description: `Derive a child private key from the given heirarchically deterministic
   private key and print it out.

   By default, it outputs in wallet import format for use by bitcoin and zcash.
   Optionally, you may pass the --format flag with a parameter of 'eth' to
   signal that it should output a raw ecdsa key for use by ethereum.`,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "format",
			Usage: "output format for key (wif or eth)",
			Value: "wif",
		},
		cli.BoolFlag{
			Name: "testnet",
		},
	},
	Action: func(c *cli.Context) error {
		format := c.String("format")

		if len(c.Args()) != 2 {
			return fmt.Errorf("must pass in private key and index")
		}

		i, err := strconv.Atoi(c.Args()[1])
		if err != nil {
			return err
		}

		data, err := ioutil.ReadFile(c.Args().First())
		if err != nil {
			return err
		}

		key, err := keychain.NewKeyFromString(string(data))
		if err != nil {
			return err
		}

		if !key.IsPrivate() {
			return fmt.Errorf("given key was not a private key")
		}

		childpriv, err := key.Child(uint32(i))
		if err != nil {
			return err
		}

		privk, err := childpriv.ECPrivKey()
		if err != nil {
			return err
		}

		switch format {
		case "wif":
			params := &chaincfg.MainNetParams
			if c.Bool("testnet") {
				params = &chaincfg.TestNet3Params
			}
			wif, err := btcutil.NewWIF(privk, params, false)
			if err != nil {
				return err
			}

			fmt.Println(wif.String())
		case "eth":
			fmt.Printf("%x\n", privk.Serialize())
		}
		return nil
	},
}

var pubKeyCmd = cli.Command{
	Name:  "pub",
	Usage: "tools for working with hd public keys",
	Subcommands: []cli.Command{
		getChildPubKeyCmd,
	},
}

var getChildPubKeyCmd = cli.Command{
	Name:  "child",
	Usage: "derive a child public key",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "format",
			Usage: "output format for key (btc, zec, or eth)",
			Value: "btc",
		},
		cli.BoolFlag{
			Name:  "testnet",
			Usage: "print testnet addrs",
		},
	},
	Action: func(c *cli.Context) error {
		enc := c.String("format")

		if len(c.Args()) != 2 {
			return fmt.Errorf("must pass in public key and index")
		}

		i, err := strconv.Atoi(c.Args()[1])
		if err != nil {
			return err
		}

		data, err := ioutil.ReadFile(c.Args().First())
		if err != nil {
			return err
		}

		key, err := keychain.NewKeyFromString(string(data))
		if err != nil {
			return err
		}

		if key.IsPrivate() {
			return fmt.Errorf("given key was a private key, not public")
		}

		childpub, err := key.Child(uint32(i))
		if err != nil {
			return err
		}

		ecpub, err := childpub.ECPubKey()
		if err != nil {
			return err
		}

		if c.Bool("testnet") {
			addrs.BitcoinPrefix = addrs.BitcoinTestnetPrefix
			addrs.ZcashPrefix = addrs.ZcashTestnetPrefix
		}

		switch enc {
		case "btc":
			fmt.Println(addrs.EncodeBitcoinPubkey(ecpub))
		case "zec":
			fmt.Println(addrs.EncodeZcashPubkey(ecpub))
		case "eth":
			fmt.Println(addrs.EncodeEthereumPubkey(ecpub))
		default:
			return fmt.Errorf("unrecognized output format: %s", enc)
		}
		return nil
	},
}
<|endoftext|>


<commit_before>package main

import "fmt"

func main(){
    fmt.Println("hello, world")
}
<commit_msg>Only changed print statement. (#21)<commit_after>package main

import "fmt"

func main(){
    fmt.Println("Hello World!")
}
<|endoftext|>