<commit_before>// Enhanced Markdown template processor.
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/mh-cbon/emd/cli"
	"github.com/mh-cbon/emd/deprecated"
	"github.com/mh-cbon/emd/emd"
	gostd "github.com/mh-cbon/emd/go"
	gononstd "github.com/mh-cbon/emd/go-nonstd"
	"github.com/mh-cbon/emd/provider"
	"github.com/mh-cbon/emd/std"
)

// VERSION defines the running build id.
var VERSION = "0.0.0"

var program = cli.NewProgram("emd", VERSION)

var verbose bool

func logMsg(f string, args ...interface{}) {
	if verbose {
		log.Printf(f+"\n", args...)
	}
}

func main() {
	program.Bind()
	if err := program.Run(os.Args); err != nil {
		log.Println(err)
		os.Exit(1)
	}
}

// gen sub command
type gencommand struct {
	*cli.Command
	in        string
	out       string
	data      string
	help      bool
	shortHelp bool
}

// init sub command
type initcommand struct {
	*cli.Command
	help      bool
	shortHelp bool
	out       string
	force     bool
}

func init() {
	gen := &gencommand{Command: cli.NewCommand("gen", "Process an emd file.", Generate)}
	gen.Set.StringVar(&gen.in, "in", "", "Input src file")
	gen.Set.StringVar(&gen.out, "out", "-", "Output destination, defaults to stdout")
	gen.Set.StringVar(&gen.data, "data", "", "JSON map of data")
	gen.Set.BoolVar(&gen.help, "help", false, "Show help")
	gen.Set.BoolVar(&gen.shortHelp, "h", false, "Show help")
	program.Add(gen)

	ini := &initcommand{Command: cli.NewCommand("init", "Init a basic emd file.", InitFile)}
	ini.Set.BoolVar(&ini.help, "help", false, "Show help")
	ini.Set.BoolVar(&ini.shortHelp, "h", false, "Show help")
	ini.Set.BoolVar(&ini.force, "force", false, "Force write")
	ini.Set.StringVar(&ini.out, "out", "README.e.md", "Out file")
	program.Add(ini)

	verbose = os.Getenv("VERBOSE") != ""
}

// Generate is the cli command implementation of gen.
func Generate(s cli.Commander) error {

	cmd, ok := s.(*gencommand)
	if ok == false {
		return fmt.Errorf("Invalid command type %T", s)
	}

	if cmd.help || cmd.shortHelp {
		return program.ShowCmdUsage(cmd)
	}

	out, err := getStdout(cmd.out)
	if err != nil {
		return err
	}
	if x, ok := out.(io.Closer); ok {
		defer x.Close()
	}

	projectPath, err := getProjectPath()
	if err != nil {
		return err
	}
	logMsg("projectPath %q", projectPath)

	plugins := getPlugins()
	data, err := getData(projectPath)
	if err != nil {
		return err
	}

	if cmd.data != "" {
		if err := json.Unmarshal([]byte(cmd.data), &data); err != nil {
			return fmt.Errorf("Cannot decode JSON data string: %v", err)
		}
	}

	gen := emd.NewGenerator()

	gen.SetDataMap(data)

	if cmd.in != "" {
		if err := gen.AddFileTemplate(cmd.in); err != nil {
			return err
		}
	} else {
		var b bytes.Buffer
		io.Copy(&b, os.Stdin)
		if b.Len() > 0 {
			gen.AddTemplate(b.String())

		} else {
			if s, err := os.Stat("README.e.md"); !os.IsNotExist(err) && s.IsDir() == false {
				if err := gen.AddFileTemplate("README.e.md"); err != nil {
					return err
				} else {
					gen.AddTemplate(defTemplate)
				}
			}
		}
	}

	for name, plugin := range plugins {
		if err := plugin(gen); err != nil {
			return fmt.Errorf("Failed to register %v package: %v", name, err)
		}
	}

	if err := gen.Execute(out); err != nil {
		return fmt.Errorf("Generator failed: %v", err)
	}

	return nil
}

func getProjectPath() (string, error) {
	cwd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	logMsg("cwd %q", cwd)

	projectPath, err := matchProjectPath(cwd)
	if err == nil {
		return projectPath, nil
	}

	cwd, err = filepath.EvalSymlinks(cwd)
	if err != nil {
		return "", err
	}

	projectPath, err = matchProjectPath(cwd)
	if err != nil {
		return "", err
	}

	return projectPath, nil
}
func matchProjectPath(p string) (string, error) {

	gopath := filepath.Join(os.Getenv("GOPATH"), "src")
	gopath = strings.Replace(gopath, "\\", "/", -1)
	logMsg("gopath %q", gopath)

	if strings.HasPrefix(p, gopath) {
		return p[len(gopath):], nil
	}

	// hack to try to match a path not contained in go path.
	handles := []string{"github.com", "gitlab.com", "bitbucket.com"}
	for _, h := range handles {
		u := "src/" + h + "/"
		if strings.Index(p, u) > -1 {
			k := strings.Split(p, h)
			return h + k[1], nil
		}
	}

	return "", fmt.Errorf("Invalid working directory %q", p)

}

func getData(cwd string) (map[string]interface{}, error) {
	p := provider.Default(cwd)
	if p.Match() == false {
		return nil, fmt.Errorf("Failed to identify this path %q\n", cwd)
	}
	return map[string]interface{}{
		"Name":         p.GetProjectName(),
		"User":         p.GetUserName(),
		"ProviderURL":  p.GetProviderURL(),
		"ProviderName": p.GetProviderID(),
		"URL":          p.GetURL(),
		"ProjectURL":   p.GetProjectURL(),
		"Branch":       "master",
	}, nil
}

func getPlugins() map[string]func(*emd.Generator) error {
	return map[string]func(*emd.Generator) error{
		"std":        std.Register,
		"gostd":      gostd.Register,
		"gononstd":   gononstd.Register,
		"deprecated": deprecated.Register,
	}
}

func getStdout(out string) (io.Writer, error) {
	ret := os.Stdout

	if out != "-" {
		f, err := os.Create(out)
		if err != nil {
			f, err = os.Open(out)
			if err != nil {
				return nil, fmt.Errorf("Cannot open out destination: %v", err)
			}
		}
		ret = f
	}
	return ret, nil
}

// InitFile creates a basic emd file if none exits.
func InitFile(s cli.Commander) error {

	cmd, ok := s.(*initcommand)
	if ok == false {
		return fmt.Errorf("Invalid command type %T", s)
	}

	if cmd.help || cmd.shortHelp {
		return program.ShowCmdUsage(cmd)
	}

	out := cmd.out
	if cmd.out == "" {
		out = "README.e.md"
	}
	if _, err := os.Stat(out); !cmd.force && !os.IsNotExist(err) {
		return fmt.Errorf("File exits at %q", out)
	}
	return ioutil.WriteFile(out, []byte(defTemplate), os.ModePerm)
}

var defTemplate = `# {{.Name}}

{{template "badge/goreport" .}} {{template "badge/godoc" .}}

{{pkgdoc}}

# {{toc 5}}

# Install

{{template "gh/releases" .}}

#### go
{{template "go/install" .}}
`
<commit_msg>fix emd gen input arguments detection<commit_after>// Enhanced Markdown template processor.
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/mh-cbon/emd/cli"
	"github.com/mh-cbon/emd/deprecated"
	"github.com/mh-cbon/emd/emd"
	gostd "github.com/mh-cbon/emd/go"
	gononstd "github.com/mh-cbon/emd/go-nonstd"
	"github.com/mh-cbon/emd/provider"
	"github.com/mh-cbon/emd/std"
)

// VERSION defines the running build id.
var VERSION = "0.0.0"

var program = cli.NewProgram("emd", VERSION)

var verbose bool

func logMsg(f string, args ...interface{}) {
	if verbose {
		log.Printf(f+"\n", args...)
	}
}

func main() {
	program.Bind()
	if err := program.Run(os.Args); err != nil {
		log.Println(err)
		os.Exit(1)
	}
}

// gen sub command
type gencommand struct {
	*cli.Command
	in        string
	out       string
	data      string
	help      bool
	shortHelp bool
}

// init sub command
type initcommand struct {
	*cli.Command
	help      bool
	shortHelp bool
	out       string
	force     bool
}

func init() {
	gen := &gencommand{Command: cli.NewCommand("gen", "Process an emd file.", Generate)}
	gen.Set.StringVar(&gen.in, "in", "", "Input src file")
	gen.Set.StringVar(&gen.out, "out", "-", "Output destination, defaults to stdout")
	gen.Set.StringVar(&gen.data, "data", "", "JSON map of data")
	gen.Set.BoolVar(&gen.help, "help", false, "Show help")
	gen.Set.BoolVar(&gen.shortHelp, "h", false, "Show help")
	program.Add(gen)

	ini := &initcommand{Command: cli.NewCommand("init", "Init a basic emd file.", InitFile)}
	ini.Set.BoolVar(&ini.help, "help", false, "Show help")
	ini.Set.BoolVar(&ini.shortHelp, "h", false, "Show help")
	ini.Set.BoolVar(&ini.force, "force", false, "Force write")
	ini.Set.StringVar(&ini.out, "out", "README.e.md", "Out file")
	program.Add(ini)

	verbose = os.Getenv("VERBOSE") != ""
}

// Generate is the cli command implementation of gen.
func Generate(s cli.Commander) error {

	cmd, ok := s.(*gencommand)
	if ok == false {
		return fmt.Errorf("Invalid command type %T", s)
	}

	if cmd.help || cmd.shortHelp {
		return program.ShowCmdUsage(cmd)
	}

	out, err := getStdout(cmd.out)
	if err != nil {
		return err
	}
	if x, ok := out.(io.Closer); ok {
		defer x.Close()
	}

	projectPath, err := getProjectPath()
	if err != nil {
		return err
	}
	logMsg("projectPath %q", projectPath)

	plugins := getPlugins()
	data, err := getData(projectPath)
	if err != nil {
		return err
	}

	if cmd.data != "" {
		if err := json.Unmarshal([]byte(cmd.data), &data); err != nil {
			return fmt.Errorf("Cannot decode JSON data string: %v", err)
		}
	}

	gen := emd.NewGenerator()

	gen.SetDataMap(data)

	if cmd.in != "" {
		if err := gen.AddFileTemplate(cmd.in); err != nil {
			return err
		}
	} else {
		b := tryReadOsStin()
		if b != nil && b.Len() > 0 {
			gen.AddTemplate(b.String())

		} else {
			if s, err := os.Stat("README.e.md"); !os.IsNotExist(err) && s.IsDir() == false {
				err := gen.AddFileTemplate("README.e.md")
				if err != nil {
					return err
				}
			} else {
				gen.AddTemplate(defTemplate)
			}
		}
	}

	for name, plugin := range plugins {
		if err := plugin(gen); err != nil {
			return fmt.Errorf("Failed to register %v package: %v", name, err)
		}
	}

	if err := gen.Execute(out); err != nil {
		return fmt.Errorf("Generator failed: %v", err)
	}

	return nil
}

func tryReadOsStin() *bytes.Buffer {
	copied := make(chan bool)
	timedout := make(chan bool)
	var ret bytes.Buffer
	go func() {
		io.Copy(&ret, os.Stdin)
		copied <- true
	}()
	go func() {
		<-time.After(time.Millisecond * 10)
		timedout <- ret.Len() == 0
	}()
	select {
	case empty := <-timedout:
		if empty {
			return nil
		}
		<-copied
	case <-copied:
	}
	return &ret
}

func getProjectPath() (string, error) {
	cwd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	logMsg("cwd %q", cwd)

	projectPath, err := matchProjectPath(cwd)
	if err == nil {
		return projectPath, nil
	}

	cwd, err = filepath.EvalSymlinks(cwd)
	if err != nil {
		return "", err
	}

	projectPath, err = matchProjectPath(cwd)
	if err != nil {
		return "", err
	}

	return projectPath, nil
}
func matchProjectPath(p string) (string, error) {

	gopath := filepath.Join(os.Getenv("GOPATH"), "src")
	gopath = strings.Replace(gopath, "\\", "/", -1)
	logMsg("gopath %q", gopath)

	if strings.HasPrefix(p, gopath) {
		return p[len(gopath):], nil
	}

	// hack to try to match a path not contained in go path.
	re := regexp.MustCompile(`/(src\/[^/]+[.][com|org|net]/.+)/`)

	res := re.FindAllString(p, -1)
	log.Println(res)

	handles := []string{"github.com", "gitlab.com", "bitbucket.com"}
	for _, h := range handles {
		u := "src/" + h + "/"
		if strings.Index(p, u) > -1 {
			k := strings.Split(p, h)
			return h + k[1], nil
		}
	}

	return "", fmt.Errorf("Invalid working directory %q", p)

}

func getData(cwd string) (map[string]interface{}, error) {
	p := provider.Default(cwd)
	if p.Match() == false {
		return nil, fmt.Errorf("Failed to identify this path %q\n", cwd)
	}
	return map[string]interface{}{
		"Name":         p.GetProjectName(),
		"User":         p.GetUserName(),
		"ProviderURL":  p.GetProviderURL(),
		"ProviderName": p.GetProviderID(),
		"URL":          p.GetURL(),
		"ProjectURL":   p.GetProjectURL(),
		"Branch":       "master",
	}, nil
}

func getPlugins() map[string]func(*emd.Generator) error {
	return map[string]func(*emd.Generator) error{
		"std":        std.Register,
		"gostd":      gostd.Register,
		"gononstd":   gononstd.Register,
		"deprecated": deprecated.Register,
	}
}

func getStdout(out string) (io.Writer, error) {
	ret := os.Stdout

	if out != "-" {
		f, err := os.Create(out)
		if err != nil {
			f, err = os.Open(out)
			if err != nil {
				return nil, fmt.Errorf("Cannot open out destination: %v", err)
			}
		}
		ret = f
	}
	return ret, nil
}

// InitFile creates a basic emd file if none exits.
func InitFile(s cli.Commander) error {

	cmd, ok := s.(*initcommand)
	if ok == false {
		return fmt.Errorf("Invalid command type %T", s)
	}

	if cmd.help || cmd.shortHelp {
		return program.ShowCmdUsage(cmd)
	}

	out := cmd.out
	if cmd.out == "" {
		out = "README.e.md"
	}
	if _, err := os.Stat(out); !cmd.force && !os.IsNotExist(err) {
		return fmt.Errorf("File exits at %q", out)
	}
	return ioutil.WriteFile(out, []byte(defTemplate), os.ModePerm)
}

var defTemplate = `# {{.Name}}

{{template "badge/goreport" .}} {{template "badge/godoc" .}}

{{pkgdoc}}

# {{toc 5}}

# Install

{{template "gh/releases" .}}

#### go
{{template "go/install" .}}
`
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"strings"
	"time"

	"code.google.com/p/go.crypto/ssh/terminal"

	"github.com/pavel-paulau/perfstat/plugins"
)

var header []string
var values []float64

func printHeader() {
	hr := 0
	for _, column := range header {
		fmt.Printf("%s ", column)
		hr += len(column) + 1
	}
	fmt.Println()
	fmt.Println(strings.Repeat("-", hr-1))
}

func printValues() {
	for i, column := range header {
		fmtStr := fmt.Sprintf("%%%dv ", len(column))
		fmt.Printf(fmtStr, values[i])
	}
	fmt.Println()
	values = []float64{}
}

func main() {
	cpu := flag.Bool("cpu", false, "enable CPU stats")
	mem := flag.Bool("mem", false, "enable memory stats")

	interval := flag.Int("interval", 1, "sampling interval in seconds")

	quiet := flag.Bool("quiet", false, "disable reporting to stdout")
	perfkeeper := flag.String("perfkeerper", "127.0.0.1:8080", "optional perfkeeper host:port")
	snapshot := flag.String("snapshot", "", "name of perfkeeper snapshot")
	source := flag.String("source", "", "name of perfkeeper snapshot")

	flag.Parse()

	activePlugins := []plugins.Plugin{}
	if *cpu == true {
		activePlugins = append(activePlugins, plugins.NewCPU())
	}
	if *mem == true {
		activePlugins = append(activePlugins, plugins.NewMem())
	}
	if len(activePlugins) == 0 {
		log.Fatalln("Please specify at least one plugin")
	}

	var keeper *keeper
	if *snapshot != "" && *source != "" {
		keeper = newKeeper(*perfkeeper, *snapshot, *source)
	} else {
		keeper = nil
	}

	for _, plugin := range activePlugins {
		header = append(header, plugin.GetColumns()...)
	}
	if !*quiet {
		printHeader()
	}

	_, y, err := terminal.GetSize(0)
	if err != nil {
		log.Fatalln(err)
	}

	iterations := 1
	for {
		for _, plugin := range activePlugins {
			values = append(values, plugin.Extract()...)
		}
		if keeper != nil {
			go keeper.store(header, values)
		}
		if !*quiet {
			printValues()
		}

		iterations++
		if iterations == y-1 {
			if !*quiet {
				printHeader()
			}
			iterations = 1
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<commit_msg>Don't crash when detached from terminal<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"strings"
	"time"

	"code.google.com/p/go.crypto/ssh/terminal"

	"github.com/pavel-paulau/perfstat/plugins"
)

var header []string
var values []float64

func printHeader() {
	hr := 0
	for _, column := range header {
		fmt.Printf("%s ", column)
		hr += len(column) + 1
	}
	fmt.Println()
	fmt.Println(strings.Repeat("-", hr-1))
}

func printValues() {
	for i, column := range header {
		fmtStr := fmt.Sprintf("%%%dv ", len(column))
		fmt.Printf(fmtStr, values[i])
	}
	fmt.Println()
	values = []float64{}
}

func main() {
	cpu := flag.Bool("cpu", false, "enable CPU stats")
	mem := flag.Bool("mem", false, "enable memory stats")

	interval := flag.Int("interval", 1, "sampling interval in seconds")

	quiet := flag.Bool("quiet", false, "disable reporting to stdout")
	perfkeeper := flag.String("perfkeerper", "127.0.0.1:8080", "optional perfkeeper host:port")
	snapshot := flag.String("snapshot", "", "name of perfkeeper snapshot")
	source := flag.String("source", "", "name of perfkeeper snapshot")

	flag.Parse()

	activePlugins := []plugins.Plugin{}
	if *cpu == true {
		activePlugins = append(activePlugins, plugins.NewCPU())
	}
	if *mem == true {
		activePlugins = append(activePlugins, plugins.NewMem())
	}
	if len(activePlugins) == 0 {
		log.Fatalln("Please specify at least one plugin")
	}

	var keeper *keeper
	if *snapshot != "" && *source != "" {
		keeper = newKeeper(*perfkeeper, *snapshot, *source)
	} else {
		keeper = nil
	}

	for _, plugin := range activePlugins {
		header = append(header, plugin.GetColumns()...)
	}
	if !*quiet {
		printHeader()
	}

	_, terminalHeight, err := terminal.GetSize(0)
	if err != nil {
		terminalHeight = -1 // Detached terminal
	}

	iterations := 1
	for {
		for _, plugin := range activePlugins {
			values = append(values, plugin.Extract()...)
		}
		if keeper != nil {
			go keeper.store(header, values)
		}
		if !*quiet {
			printValues()
		}

		iterations++
		if iterations == terminalHeight-1 {
			if !*quiet {
				printHeader()
			}
			iterations = 1
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/bitly/go-nsq"
	"github.com/bitly/nsq/internal/app"
)

var (
	n                = flag.Int("n", 10, "number of samples to collect before updating")
	β                = flag.Float64("beta", 0, "prior rate")
	α                = flag.Float64("alpha", 0, "prior shape")
	lookupdHTTPAddrs = app.StringArray{}
	maxInFlight      = flag.Int("max-in-flight", 10, "max number of messages to allow in flight")
)

type TopicResponse struct {
	Data struct {
		Topics []string `json:"topics"`
	} `json:"data"`
	StatusCode float64 `json:"status_code"`
	StatusTxt  string  `json:"status_txt"`
}

type RateEstimator struct {
	α          float64
	β          float64
	eventTimes chan time.Time
}

func NewRateEstimator(α, β float64, n int) RateEstimator {
	return RateEstimator{
		α:          α,
		β:          β,
		eventTimes: make(chan time.Time, n),
	}
}

func (re RateEstimator) HandleMessage(m *nsq.Message) error {
	t := time.Now()
	select {
	case re.eventTimes <- t:
	default:
		<-re.eventTimes
		re.eventTimes <- t
		re.Update()
	}
	return nil
}

func (re RateEstimator) Update() {
	var Δhat float64
	N := len(re.eventTimes)
	ti := <-re.eventTimes
	n := 1
	for t := range re.eventTimes {
		Δ := float64(t.Sub(ti))
		Δhat += Δ / float64(n)
		ti = t
		n++
		if n == N-1 {
			break
		}
	}
	re.α += float64(n)
	re.β += float64(n) * Δhat
	log.Println(α, β)
}

func main() {

	flag.Var(&lookupdHTTPAddrs, "lookupd-http-address", "lookupd HTTP address (may be given multiple times)")
	flag.Parse()

	// get a list of topics from nsqlookupd
	var topics []string

	for _, lookupd := range lookupdHTTPAddrs {
		u, err := url.Parse(lookupd)
		if err != nil {
			log.Fatal(err)
		}
		u.Scheme = "http"
		resp, err := http.Get(u.String())
		if err != nil {
			log.Println(err)
			return
		}
		var topicInfo TopicResponse
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(&topicInfo)
		if err != nil {
			log.Println(err)
			return
		}
		topics = append(topics, topicInfo.Data.Topics...)
	}

	rateEstimators := make(map[string]*RateEstimator)
	conf := nsq.NewConfig()
	conf.MaxInFlight = *maxInFlight
	for _, topic := range topics {
		consumer, err := nsq.NewConsumer(topic, "rate_estimator#ephemeral", conf)
		if err != nil {
			log.Fatal(err)
		}
		handler := NewRateEstimator(*α, *β, *n)
		consumer.AddHandler(handler)
		rateEstimators[topic] = &handler
		err = consumer.ConnectToNSQLookupds(lookupdHTTPAddrs)
		if err != nil {
			log.Fatal(err)
		}
	}

	// wait for ctrl-c
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	<-sigChan

}
<commit_msg>converges after a while. probably too quickly<commit_after>package main

import (
	"encoding/json"
	"flag"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/bitly/go-nsq"
	"github.com/bitly/nsq/internal/app"
)

var (
	n                = flag.Int("n", 10, "number of samples to collect before updating")
	βprior           = flag.Float64("beta", 0, "prior rate")
	αprior           = flag.Float64("alpha", 0, "prior shape")
	lookupdHTTPAddrs = app.StringArray{}
	maxInFlight      = flag.Int("max-in-flight", 10, "max number of messages to allow in flight")
)

type TopicResponse struct {
	Data struct {
		Topics []string `json:"topics"`
	} `json:"data"`
	StatusCode float64 `json:"status_code"`
	StatusTxt  string  `json:"status_txt"`
}

type RateEstimator struct {
	α          float64
	β          float64
	eventTimes chan time.Time
	timeChan   chan time.Time
	topic      string
}

func NewRateEstimator(α, β float64, n int, topic string) *RateEstimator {
	re := &RateEstimator{
		α:          α,
		β:          β,
		eventTimes: make(chan time.Time, n),
		timeChan:   make(chan time.Time),
		topic:      topic,
	}
	go re.Start()
	return re
}

func (re *RateEstimator) HandleMessage(m *nsq.Message) error {
	re.timeChan <- time.Now()
	return nil
}

func (re *RateEstimator) Start() {
	for {
		t := <-re.timeChan
		select {
		case re.eventTimes <- t:
		default:
			re.Update()
			re.eventTimes <- t
		}
	}
}

func (re *RateEstimator) Update() {
	var Δhat float64
	N := len(re.eventTimes)
	ti := <-re.eventTimes
	n := 1
	for t := range re.eventTimes {
		Δ := t.Sub(ti).Seconds()
		Δhat += Δ / float64(n)
		ti = t
		n++
		if n == N {
			break
		}
	}
	re.α += float64(n)
	re.β += float64(n) * Δhat
	/*
		if re.topic == "abtest" {
			log.Println(re.topic, "  α:", re.α, "  β:", re.β, "  mean:", re.α/re.β, "  variance:", re.α/math.Pow(re.β, 2))
		}
	*/
}

func main() {

	flag.Var(&lookupdHTTPAddrs, "lookupd-http-address", "lookupd HTTP address (may be given multiple times)")
	flag.Parse()

	// get a list of topics from nsqlookupd
	var topics []string

	for _, lookupd := range lookupdHTTPAddrs {
		u, err := url.Parse(lookupd)
		if err != nil {
			log.Fatal(err)
		}
		u.Path = "/topics"
		log.Println(u.String())
		resp, err := http.Get(u.String())
		if err != nil {
			log.Println(err)
			return
		}
		var topicInfo TopicResponse
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(&topicInfo)
		if err != nil {
			log.Println(err)
			return
		}
		topics = append(topics, topicInfo.Data.Topics...)
	}

	rateEstimators := make(map[string]*RateEstimator)
	conf := nsq.NewConfig()
	conf.MaxInFlight = *maxInFlight
	for _, topic := range topics {
		consumer, err := nsq.NewConsumer(topic, "rate_estimator#ephemeral", conf)
		if err != nil {
			log.Fatal(err)
		}
		handler := NewRateEstimator(*αprior, *βprior, *n, topic)
		consumer.AddHandler(handler)
		rateEstimators[topic] = handler
		err = consumer.ConnectToNSQLookupds(lookupdHTTPAddrs)
		if err != nil {
			log.Fatal(err)
		}
	}

	// wait for ctrl-c
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	<-sigChan

}
<|endoftext|>


<commit_before>package main

import (
	"github.com/aqatl/mal/mal"
	"github.com/urfave/cli"
	"os"
	"log"
	"sort"
	"strconv"
	"path/filepath"
	"github.com/skratchdot/open-golang/open"
	"fmt"
	"strings"
	"time"
	"github.com/fatih/color"
)

var dataDir = filepath.Join(homeDir(), ".mal")
var (
	CredentialsFile = filepath.Join(dataDir, "cred.dat")
	MalCacheFile    = filepath.Join(dataDir, "cache.xml")
	ConfigFile      = filepath.Join(dataDir, "config.json")
)

func main() {
	checkDataDir()

	app := cli.NewApp()
	app.Name = "mal"
	app.Usage = "App for managing your MAL"
	app.Version = "0.1"

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "creds, prompt-credentials",
			Usage: "Prompt for username and password",
		},
		cli.BoolFlag{
			Name:  "sp, save-password",
			Usage: "save your password. Use with caution, your password can be decoded",
		},
		cli.BoolFlag{
			Name:  "r, refresh",
			Usage: "refreshes cache file",
		},
		cli.BoolFlag{
			Name:  "ver, verify",
			Usage: "verify credentials",
		},
		cli.IntFlag{
			Name:  "max",
			Usage: "visible entries threshold",
		},
		cli.StringFlag{
			Name:  "status",
			Usage: "display entries only with given status",
		},
	}

	app.Commands = []cli.Command{
		cli.Command{
			Name:     "eps",
			Aliases:  []string{"episodes"},
			Category: "Update",
			Usage: "Set the watched episodes value. " +
				"If n not specified, the number will be increased by one",
			UsageText: "mal eps <n>",
			Action:    setEntryEpisodes,
		},
		cli.Command{
			Name:      "score",
			Category:  "Update",
			Usage:     "Set your rating for selected entry",
			UsageText: "mal score <0-10>",
			Action:    setEntryScore,
		},
		cli.Command{
			Name:      "status",
			Category:  "Update",
			Usage:     "Set your status for selected entry",
			UsageText: "mal status [watching|completed|onhold|dropped|plantowatch]",
			Action:    setEntryStatus,
		},
		cli.Command{
			Name:      "sel",
			Aliases:   []string{"select"},
			Category:  "Config",
			Usage:     "Select an entry",
			UsageText: "mal sel [entry ID]",
			Action:    selectEntry,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "by-title",
					Usage: "Select entry by name instead of by ID",
				},
			},
		},
		cli.Command{
			Name:     "cfg",
			Aliases:  []string{"config", "configuration"},
			Category: "Config",
			Usage:    "Change config values",
			Subcommands: cli.Commands{
				cli.Command{
					Name:      "max",
					Aliases:   []string{"visible"},
					Usage:     "Change amount of displayed entries",
					UsageText: "mal cfg max [number]",
					Action:    configChangeMax,
				},
				cli.Command{
					Name:      "status",
					Usage:     "Status value of displayed entries",
					UsageText: "mal cfg status [all|watching|completed|onhold|dropped|plantowatch]",
					Action:    configChangeStatus,
				},
				cli.Command{
					Name:      "status-auto-update",
					Usage:     "Allows entry to be automatically set to completed when number of all episodes is reached or exceeded",
					UsageText: "mal cfg status-auto-update [off|normal|after-threshold]",
					Action:    configChangeAutoUpdateMode,
				},
			},
		},
		cli.Command{
			Name:      "web",
			Aliases:   []string{"website", "open"},
			Category:  "Action",
			Usage:     "Open url associated with selected entry",
			UsageText: "mal web",
			Action:    openWebsite,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "url",
					Usage: "Set url for current entry",
				},
				cli.BoolFlag{
					Name:  "clear",
					Usage: "Clear url for current entry",
				},
			},
			Subcommands: []cli.Command{
				cli.Command{
					Name:      "get-all",
					Usage:     "Print all set urls",
					UsageText: "mal web get-all",
					Action:    printWebsites,
				},
			},
		},
		cli.Command{
			Name:      "details",
			Category:  "Action",
			Usage:     "Print details about selected entry",
			UsageText: "mal details",
			Action:    detailsCommand,
		},
		cli.Command{
			Name: "related",
			Category: "Action",
			Usage: "Fetch entries related to the selected one",
			UsageText: "mal related",
			Action: fetchRelated,
		},
	}

	app.Action = cli.ActionFunc(defaultAction)

	if err := app.Run(os.Args); err != nil {
		log.Fatalf("Error: %v", err)
	}
}

func loadMAL(ctx *cli.Context) (*mal.Client, mal.AnimeList, error) {
	creds := loadCredentials(ctx)
	if ctx.GlobalBool("verify") && !mal.VerifyCredentials(creds) {
		return nil, nil, fmt.Errorf("invalid credentials")
	}
	if ctx.GlobalBool("save-password") {
		saveCredentials(creds)
	}

	c := mal.NewClient(creds)
	if c == nil {
		return nil, nil, fmt.Errorf("error creating mal.Client")
	}

	list := loadList(c, ctx)

	return c, list, nil
}

func defaultAction(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	list = list.FilterByStatus(statusFlag(ctx))
	sort.Sort(mal.AnimeSortByLastUpdated(list))

	var visibleEntries int
	if visibleEntries = ctx.Int("max"); visibleEntries == 0 {
		//`Max` flag not specified, get value from config
		visibleEntries = cfg.MaxVisibleEntries
	}
	if visibleEntries > len(list) {
		visibleEntries = len(list)
	}
	visibleList := list[:visibleEntries]
	reverseAnimeSlice(visibleList)

	PrettyList.Execute(os.Stdout, PrettyListData{visibleList, cfg.SelectedID})

	if cfg.LastUpdate != *new(time.Time) {
		fmt.Printf("\nList last updated: %v (%d days ago)\n",
			cfg.LastUpdate,
			int(time.Since(cfg.LastUpdate).Hours()/24),
		)
	}

	return nil
}

func setEntryEpisodes(ctx *cli.Context) error {
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	if cfg.SelectedID == 0 {
		return fmt.Errorf("no entry selected")
	}

	selectedEntry := list.GetByID(cfg.SelectedID)

	if selectedEntry == nil {
		return fmt.Errorf("no entry found")
	}

	if arg := ctx.Args().First(); arg != "" {
		n, err := strconv.Atoi(arg)
		if err != nil {
			return fmt.Errorf("n must be a non-negative integer")
		}
		if n < 0 {
			return fmt.Errorf("n can't be lower than 0")
		}
		selectedEntry.WatchedEpisodes = n
	} else {
		selectedEntry.WatchedEpisodes++
	}

	statusAutoUpdate(cfg, selectedEntry)

	if c.Update(selectedEntry) {
		fmt.Println("Updated successfully")
		printEntryDetails(selectedEntry)

		cacheList(list)
	}
	return nil
}

func setEntryScore(ctx *cli.Context) error {
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	selectedEntry := list.GetByID(cfg.SelectedID)

	score, err := strconv.Atoi(ctx.Args().First())
	if err != nil {
		return fmt.Errorf("error parsing score: %v", err)
	}
	parsedScore, err := mal.ParseScore(score)
	if err != nil {
		return err
	}

	selectedEntry.MyScore = parsedScore
	if c.Update(selectedEntry) {
		fmt.Println("Updated successfully")
		printEntryDetails(selectedEntry)

		cacheList(list)
	}
	return nil
}

func setEntryStatus(ctx *cli.Context) error {
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	selectedEntry := list.GetByID(cfg.SelectedID)

	status := mal.ParseStatus(ctx.Args().First())
	if status == mal.All {
		return fmt.Errorf("invalid status; possible values: watching, completed, " +
			"onhold, dropped, plantowatch")
	}

	selectedEntry.MyStatus = status
	if c.Update(selectedEntry) {
		fmt.Println("Updated successfully")
		printEntryDetails(selectedEntry)

		cacheList(list)
	}
	return nil
}

func selectEntry(ctx *cli.Context) error {
	if ctx.Bool("by-title") {
		return selectByTitle(ctx)
	}

	id, err := strconv.Atoi(ctx.Args().First())
	if err != nil {
		return fmt.Errorf("error parsing id: %v", err)
	}

	cfg := LoadConfig()
	cfg.SelectedID = id
	cfg.Save()

	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	fmt.Println("Selected entry:")
	printEntryDetails(list.GetByID(id))

	return nil
}

func selectByTitle(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	title := strings.ToLower(ctx.Args().First())

	found := make(mal.AnimeList, 0)
	for _, entry := range list {
		if strings.Contains(strings.ToLower(entry.Title), title) {
			found = append(found, entry)
		}
	}

	var selectedEntry *mal.Anime

	if len(found) > 1 {
		fmt.Printf("Found more than 1 matching entry:\n")
		fmt.Printf("%3s%8s%7s\n", "No.", "ID", "Title")
		fmt.Println(strings.Repeat("=", 80))
		for i, entry := range found {
			fmt.Printf("%3d. %6d: %s\n", i, entry.ID, entry.Title)
		}

		fmt.Printf("Enter index of the selected entry: ")
		idx := 0
		_, err := fmt.Scanln(&idx)
		if err != nil || idx < 0 || idx > len(found)-1 {
			return fmt.Errorf("invalid input %v", err)
		}

		selectedEntry = found[idx]
	} else if len(found) == 0 {
		return fmt.Errorf("no matches")
	} else {
		selectedEntry = found[0]
	}

	cfg := LoadConfig()
	cfg.SelectedID = selectedEntry.ID
	cfg.Save()

	fmt.Println("Selected entry:")
	printEntryDetails(selectedEntry)

	return nil
}

func openWebsite(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return nil
	}

	cfg := LoadConfig()

	if url := ctx.String("url"); url != "" {
		cfg.Websites[cfg.SelectedID] = url
		log.Printf("Set url %s for entry %d", url, cfg.SelectedID)
		cfg.Save()
		return nil
	}

	if ctx.Bool("clear") {
		delete(cfg.Websites, cfg.SelectedID)
		log.Printf("Cleared url for entry %d", cfg.SelectedID)
		cfg.Save()
		return nil
	}

	if url, ok := cfg.Websites[cfg.SelectedID]; ok {
		open.Start(url)
		fmt.Println("Opened website for:")
		printEntryDetails(list.GetByID(cfg.SelectedID))
	} else {
		log.Println("Nothing to open")
	}

	return nil
}

func printWebsites(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	for k, v := range cfg.Websites {
		url := fmt.Sprintf("\033[3%d;%dm%s\033[0m ", 3, 1, v)

		var title string
		if entry := list.GetByID(k); entry != nil {
			title = entry.Title
		}

		fmt.Printf("%6d (%s): %s\n", k, title, url)
	}

	return nil
}

func detailsCommand(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	if entry := list.GetByID(cfg.SelectedID); entry != nil {
		printEntryDetails(entry)
	}

	return nil
}

func fetchRelated(ctx *cli.Context) error {
	cfg := LoadConfig()
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	selEntry := list.GetByID(cfg.SelectedID)
	relateds, err := c.FetchRelated(selEntry)
	if err != nil {
		return err
	}

	for _, related := range relateds {
		title := color.HiYellowString("%s", related.Title)
		fmt.Printf("%s: %s (%s)\n", related.Relation, title, related.Url)
	}

	return nil
}

func configChangeMax(ctx *cli.Context) error {
	cfg := LoadConfig()

	max, err := strconv.Atoi(ctx.Args().First())
	if err != nil || max < 0 {
		return fmt.Errorf("invalid value")
	}

	cfg.MaxVisibleEntries = max
	cfg.Save()
	return nil
}

func configChangeStatus(ctx *cli.Context) error {
	cfg := LoadConfig()

	status := mal.ParseStatus(ctx.Args().First())

	cfg.Status = status
	cfg.Save()
	return nil
}

func configChangeAutoUpdateMode(ctx *cli.Context) error {
	arg := strings.ToLower(ctx.Args().First())
	var mode StatusAutoUpdateMode

	if arg == "off" {
		mode = Off
	} else if arg == "normal" {
		mode = Normal
	} else if arg == "after-threshold" {
		mode = AfterThreshold
	} else {
		return fmt.Errorf("invalid option; possible values: off|normal|after-threshold")
	}

	cfg := LoadConfig()
	cfg.StatusAutoUpdateMode = mode
	cfg.Save()

	return nil
}
<commit_msg>Added command to open selected entry's mal page<commit_after>package main

import (
	"github.com/aqatl/mal/mal"
	"github.com/urfave/cli"
	"os"
	"log"
	"sort"
	"strconv"
	"path/filepath"
	"github.com/skratchdot/open-golang/open"
	"fmt"
	"strings"
	"time"
	"github.com/fatih/color"
)

var dataDir = filepath.Join(homeDir(), ".mal")
var (
	CredentialsFile = filepath.Join(dataDir, "cred.dat")
	MalCacheFile    = filepath.Join(dataDir, "cache.xml")
	ConfigFile      = filepath.Join(dataDir, "config.json")
)

func main() {
	checkDataDir()

	app := cli.NewApp()
	app.Name = "mal"
	app.Usage = "App for managing your MAL"
	app.Version = "0.1"

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "creds, prompt-credentials",
			Usage: "Prompt for username and password",
		},
		cli.BoolFlag{
			Name:  "sp, save-password",
			Usage: "save your password. Use with caution, your password can be decoded",
		},
		cli.BoolFlag{
			Name:  "r, refresh",
			Usage: "refreshes cache file",
		},
		cli.BoolFlag{
			Name:  "ver, verify",
			Usage: "verify credentials",
		},
		cli.IntFlag{
			Name:  "max",
			Usage: "visible entries threshold",
		},
		cli.StringFlag{
			Name:  "status",
			Usage: "display entries only with given status",
		},
	}

	app.Commands = []cli.Command{
		cli.Command{
			Name:     "eps",
			Aliases:  []string{"episodes"},
			Category: "Update",
			Usage: "Set the watched episodes value. " +
				"If n not specified, the number will be increased by one",
			UsageText: "mal eps <n>",
			Action:    setEntryEpisodes,
		},
		cli.Command{
			Name:      "score",
			Category:  "Update",
			Usage:     "Set your rating for selected entry",
			UsageText: "mal score <0-10>",
			Action:    setEntryScore,
		},
		cli.Command{
			Name:      "status",
			Category:  "Update",
			Usage:     "Set your status for selected entry",
			UsageText: "mal status [watching|completed|onhold|dropped|plantowatch]",
			Action:    setEntryStatus,
		},
		cli.Command{
			Name:      "sel",
			Aliases:   []string{"select"},
			Category:  "Config",
			Usage:     "Select an entry",
			UsageText: "mal sel [entry ID]",
			Action:    selectEntry,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "by-title",
					Usage: "Select entry by name instead of by ID",
				},
			},
		},
		cli.Command{
			Name:     "cfg",
			Aliases:  []string{"config", "configuration"},
			Category: "Config",
			Usage:    "Change config values",
			Subcommands: cli.Commands{
				cli.Command{
					Name:      "max",
					Aliases:   []string{"visible"},
					Usage:     "Change amount of displayed entries",
					UsageText: "mal cfg max [number]",
					Action:    configChangeMax,
				},
				cli.Command{
					Name:      "status",
					Usage:     "Status value of displayed entries",
					UsageText: "mal cfg status [all|watching|completed|onhold|dropped|plantowatch]",
					Action:    configChangeStatus,
				},
				cli.Command{
					Name:      "status-auto-update",
					Usage:     "Allows entry to be automatically set to completed when number of all episodes is reached or exceeded",
					UsageText: "mal cfg status-auto-update [off|normal|after-threshold]",
					Action:    configChangeAutoUpdateMode,
				},
			},
		},
		cli.Command{
			Name:      "web",
			Aliases:   []string{"website", "open"},
			Category:  "Action",
			Usage:     "Open url associated with selected entry",
			UsageText: "mal web",
			Action:    openWebsite,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "url",
					Usage: "Set url for current entry",
				},
				cli.BoolFlag{
					Name:  "clear",
					Usage: "Clear url for current entry",
				},
			},
			Subcommands: []cli.Command{
				cli.Command{
					Name:      "get-all",
					Usage:     "Print all set urls",
					UsageText: "mal web get-all",
					Action:    printWebsites,
				},
			},
		},
		cli.Command{
			Name: "mal-page",
			Category: "Action",
			Usage: "Open MyAnimeList page of selected entry",
			UsageText: "mal mal-page",
			Action: openMalPage,
		},
		cli.Command{
			Name:      "details",
			Category:  "Action",
			Usage:     "Print details about selected entry",
			UsageText: "mal details",
			Action:    detailsCommand,
		},
		cli.Command{
			Name: "related",
			Category: "Action",
			Usage: "Fetch entries related to the selected one",
			UsageText: "mal related",
			Action: fetchRelated,
		},
	}

	app.Action = cli.ActionFunc(defaultAction)

	if err := app.Run(os.Args); err != nil {
		log.Fatalf("Error: %v", err)
	}
}

func loadMAL(ctx *cli.Context) (*mal.Client, mal.AnimeList, error) {
	creds := loadCredentials(ctx)
	if ctx.GlobalBool("verify") && !mal.VerifyCredentials(creds) {
		return nil, nil, fmt.Errorf("invalid credentials")
	}
	if ctx.GlobalBool("save-password") {
		saveCredentials(creds)
	}

	c := mal.NewClient(creds)
	if c == nil {
		return nil, nil, fmt.Errorf("error creating mal.Client")
	}

	list := loadList(c, ctx)

	return c, list, nil
}

func defaultAction(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	list = list.FilterByStatus(statusFlag(ctx))
	sort.Sort(mal.AnimeSortByLastUpdated(list))

	var visibleEntries int
	if visibleEntries = ctx.Int("max"); visibleEntries == 0 {
		//`Max` flag not specified, get value from config
		visibleEntries = cfg.MaxVisibleEntries
	}
	if visibleEntries > len(list) {
		visibleEntries = len(list)
	}
	visibleList := list[:visibleEntries]
	reverseAnimeSlice(visibleList)

	PrettyList.Execute(os.Stdout, PrettyListData{visibleList, cfg.SelectedID})

	if cfg.LastUpdate != *new(time.Time) {
		fmt.Printf("\nList last updated: %v (%d days ago)\n",
			cfg.LastUpdate,
			int(time.Since(cfg.LastUpdate).Hours()/24),
		)
	}

	return nil
}

func setEntryEpisodes(ctx *cli.Context) error {
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	if cfg.SelectedID == 0 {
		return fmt.Errorf("no entry selected")
	}

	selectedEntry := list.GetByID(cfg.SelectedID)

	if selectedEntry == nil {
		return fmt.Errorf("no entry found")
	}

	if arg := ctx.Args().First(); arg != "" {
		n, err := strconv.Atoi(arg)
		if err != nil {
			return fmt.Errorf("n must be a non-negative integer")
		}
		if n < 0 {
			return fmt.Errorf("n can't be lower than 0")
		}
		selectedEntry.WatchedEpisodes = n
	} else {
		selectedEntry.WatchedEpisodes++
	}

	statusAutoUpdate(cfg, selectedEntry)

	if c.Update(selectedEntry) {
		fmt.Println("Updated successfully")
		printEntryDetails(selectedEntry)

		cacheList(list)
	}
	return nil
}

func setEntryScore(ctx *cli.Context) error {
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	selectedEntry := list.GetByID(cfg.SelectedID)

	score, err := strconv.Atoi(ctx.Args().First())
	if err != nil {
		return fmt.Errorf("error parsing score: %v", err)
	}
	parsedScore, err := mal.ParseScore(score)
	if err != nil {
		return err
	}

	selectedEntry.MyScore = parsedScore
	if c.Update(selectedEntry) {
		fmt.Println("Updated successfully")
		printEntryDetails(selectedEntry)

		cacheList(list)
	}
	return nil
}

func setEntryStatus(ctx *cli.Context) error {
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	selectedEntry := list.GetByID(cfg.SelectedID)

	status := mal.ParseStatus(ctx.Args().First())
	if status == mal.All {
		return fmt.Errorf("invalid status; possible values: watching, completed, " +
			"onhold, dropped, plantowatch")
	}

	selectedEntry.MyStatus = status
	if c.Update(selectedEntry) {
		fmt.Println("Updated successfully")
		printEntryDetails(selectedEntry)

		cacheList(list)
	}
	return nil
}

func selectEntry(ctx *cli.Context) error {
	if ctx.Bool("by-title") {
		return selectByTitle(ctx)
	}

	id, err := strconv.Atoi(ctx.Args().First())
	if err != nil {
		return fmt.Errorf("error parsing id: %v", err)
	}

	cfg := LoadConfig()
	cfg.SelectedID = id
	cfg.Save()

	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	fmt.Println("Selected entry:")
	printEntryDetails(list.GetByID(id))

	return nil
}

func selectByTitle(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	title := strings.ToLower(ctx.Args().First())

	found := make(mal.AnimeList, 0)
	for _, entry := range list {
		if strings.Contains(strings.ToLower(entry.Title), title) {
			found = append(found, entry)
		}
	}

	var selectedEntry *mal.Anime

	if len(found) > 1 {
		fmt.Printf("Found more than 1 matching entry:\n")
		fmt.Printf("%3s%8s%7s\n", "No.", "ID", "Title")
		fmt.Println(strings.Repeat("=", 80))
		for i, entry := range found {
			fmt.Printf("%3d. %6d: %s\n", i, entry.ID, entry.Title)
		}

		fmt.Printf("Enter index of the selected entry: ")
		idx := 0
		_, err := fmt.Scanln(&idx)
		if err != nil || idx < 0 || idx > len(found)-1 {
			return fmt.Errorf("invalid input %v", err)
		}

		selectedEntry = found[idx]
	} else if len(found) == 0 {
		return fmt.Errorf("no matches")
	} else {
		selectedEntry = found[0]
	}

	cfg := LoadConfig()
	cfg.SelectedID = selectedEntry.ID
	cfg.Save()

	fmt.Println("Selected entry:")
	printEntryDetails(selectedEntry)

	return nil
}

func openWebsite(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return nil
	}

	cfg := LoadConfig()

	if url := ctx.String("url"); url != "" {
		cfg.Websites[cfg.SelectedID] = url
		log.Printf("Set url %s for entry %d", url, cfg.SelectedID)
		cfg.Save()
		return nil
	}

	if ctx.Bool("clear") {
		delete(cfg.Websites, cfg.SelectedID)
		log.Printf("Cleared url for entry %d", cfg.SelectedID)
		cfg.Save()
		return nil
	}

	if url, ok := cfg.Websites[cfg.SelectedID]; ok {
		open.Start(url)
		fmt.Println("Opened website for:")
		printEntryDetails(list.GetByID(cfg.SelectedID))
	} else {
		log.Println("Nothing to open")
	}

	return nil
}

func openMalPage(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return nil
	}

	cfg := LoadConfig()
	id := cfg.SelectedID
	if id <= 0 {
		return fmt.Errorf("no entry selected")
	}

	open.Start(fmt.Sprintf(mal.AnimePage, cfg.SelectedID))
	fmt.Println("Opened website for:")
	printEntryDetails(list.GetByID(cfg.SelectedID))

	return nil
}

func printWebsites(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	for k, v := range cfg.Websites {
		url := fmt.Sprintf("\033[3%d;%dm%s\033[0m ", 3, 1, v)

		var title string
		if entry := list.GetByID(k); entry != nil {
			title = entry.Title
		}

		fmt.Printf("%6d (%s): %s\n", k, title, url)
	}

	return nil
}

func detailsCommand(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	cfg := LoadConfig()

	if entry := list.GetByID(cfg.SelectedID); entry != nil {
		printEntryDetails(entry)
	}

	return nil
}

func fetchRelated(ctx *cli.Context) error {
	cfg := LoadConfig()
	c, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}

	selEntry := list.GetByID(cfg.SelectedID)
	relateds, err := c.FetchRelated(selEntry)
	if err != nil {
		return err
	}

	for _, related := range relateds {
		title := color.HiYellowString("%s", related.Title)
		fmt.Printf("%s: %s (%s)\n", related.Relation, title, related.Url)
	}

	return nil
}

func configChangeMax(ctx *cli.Context) error {
	cfg := LoadConfig()

	max, err := strconv.Atoi(ctx.Args().First())
	if err != nil || max < 0 {
		return fmt.Errorf("invalid value")
	}

	cfg.MaxVisibleEntries = max
	cfg.Save()
	return nil
}

func configChangeStatus(ctx *cli.Context) error {
	cfg := LoadConfig()

	status := mal.ParseStatus(ctx.Args().First())

	cfg.Status = status
	cfg.Save()
	return nil
}

func configChangeAutoUpdateMode(ctx *cli.Context) error {
	arg := strings.ToLower(ctx.Args().First())
	var mode StatusAutoUpdateMode

	if arg == "off" {
		mode = Off
	} else if arg == "normal" {
		mode = Normal
	} else if arg == "after-threshold" {
		mode = AfterThreshold
	} else {
		return fmt.Errorf("invalid option; possible values: off|normal|after-threshold")
	}

	cfg := LoadConfig()
	cfg.StatusAutoUpdateMode = mode
	cfg.Save()

	return nil
}
<|endoftext|>


<commit_before>package main

import (
  "fmt"
  "net/http"
  "os"
  "github.com/memgo/api/meetup"
  "encoding/json"
  "github.com/pmylund/go-cache"
  "github.com/gorilla/mux"
  "time"
  "log"
  "strings"
)

var c *cache.Cache

func main() {
	port := "8080"
	ip := "0.0.0.0"

	if len(os.Getenv("PORT")) > 0 {
		port = os.Getenv("PORT")
	}
	if len(os.Getenv("IP")) > 0 {
		ip = os.Getenv("IP")
	}
  c = cache.New(5*time.Minute, 30*time.Second)

  r := mux.NewRouter()
  r.Handle("/", http.RedirectHandler("/calendar.json?keyword=memphis+ruby", 302))
  r.Handle("/favicon.ico", http.NotFoundHandler())
  r.HandleFunc("/slack/meetup", slackMeetup)
  r.HandleFunc("/calendar.json", calendarJson)
  r.HandleFunc("/{meetup}", meetupRedir)
  http.Handle("/", r)

	log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%s", ip, port), nil))
}

func slackMeetup(w http.ResponseWriter, r *http.Request) {
  text := r.FormValue("text")
  trigger := "!meetup"

  text = strings.Replace(text, trigger, "", -1)

  events := filterEventsByKeyword(text)
  var response string

  if len(events) > 0 {
    e := events[0]
    t := time.Unix(e.Time, 0)
    layout := "Jan 2, 2006 at 3:04pm (MST)"
    response = fmt.Sprint(e.Name, " | ", t.Format(layout), " @ ", e.Venue.Name, " | ", e.EventUrl)
  } else {
    response = "No matching meetup found."
  }

  data := struct {
    Text string `json:"text"`
  }{response}

  output, _ := json.Marshal(data)
  log.Println(string(output))
  w.Write(output)
}

func meetupRedir(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    keyword := string(vars["meetup"])

    events := filterEventsByKeyword(keyword)

    url := "http://www.meetup.com/memphis-technology-user-groups/"
    if len(events) > 0 {
      url = events[0].EventUrl
    }

    http.Redirect(w, r, url, 302)
}

func calendarJson(w http.ResponseWriter, r *http.Request) {
  keyword := string(r.FormValue("keyword"))

  events := filterEventsByKeyword(keyword)

  type CalendarJsonResponse struct {
    PR string `json:_pull_requests_appreciated`
    Meetups []meetup.Event
  }

  data := CalendarJsonResponse{"https://github.com/memgo/api",events}
  marsh, _ := json.Marshal(data)
  w.Write(marsh)
}

func filterEventsByKeyword(keyword string) (results []meetup.Event) {
  keyword = strings.TrimSpace(strings.ToLower(keyword))
  r, found := c.Get(keyword)
  if !found {
    log.Println("Cache miss for [", keyword, "]")
    latest, err := getLatestEvents()
    if err != nil {
      log.Println(err)
      return results
    }

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

    for _, element := range latest.Results {
      if strings.Index(strings.ToLower(element.Name), keyword) > -1 {
        results = append(results, element)
      }
    }

    c.Set(keyword, results, cache.DefaultExpiration)
  } else {
    results = r.([]meetup.Event)
  }

  return results
}

func getLatestEvents() (meetup.Events, error) {
  group_id := os.Getenv("MEETUP_GROUP_ID")
  api_key := os.Getenv("MEETUP_API_KEY")
  url := fmt.Sprintf("https://api.meetup.com/2/events?group_id=%s&key=%s", group_id, api_key)
  resp, err := http.Get(url)

  events := new(meetup.Events)
  decoder := json.NewDecoder(resp.Body)
  decoder.Decode(events)

  if err != nil {
    return *events, err
  }

  return *events, nil
}
<commit_msg>Variable trigger word<commit_after>package main

import (
  "fmt"
  "net/http"
  "os"
  "github.com/memgo/api/meetup"
  "encoding/json"
  "github.com/pmylund/go-cache"
  "github.com/gorilla/mux"
  "time"
  "log"
  "strings"
)

var c *cache.Cache

func main() {
	port := "8080"
	ip := "0.0.0.0"

	if len(os.Getenv("PORT")) > 0 {
		port = os.Getenv("PORT")
	}
	if len(os.Getenv("IP")) > 0 {
		ip = os.Getenv("IP")
	}
  c = cache.New(5*time.Minute, 30*time.Second)

  r := mux.NewRouter()
  r.Handle("/", http.RedirectHandler("/calendar.json?keyword=memphis+ruby", 302))
  r.Handle("/favicon.ico", http.NotFoundHandler())
  r.HandleFunc("/slack/meetup", slackMeetup)
  r.HandleFunc("/calendar.json", calendarJson)
  r.HandleFunc("/{meetup}", meetupRedir)
  http.Handle("/", r)

	log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%s", ip, port), nil))
}

func slackMeetup(w http.ResponseWriter, r *http.Request) {
  text := r.FormValue("text")
  trigger := r.FormValue("trigger_word")

  text = strings.Replace(text, trigger, "", -1)

  events := filterEventsByKeyword(text)
  var response string

  if len(events) > 0 {
    e := events[0]
    t := time.Unix(e.Time, 0)
    layout := "Jan 2, 2006 at 3:04pm (MST)"
    response = fmt.Sprint(e.Name, " | ", t.Format(layout), " @ ", e.Venue.Name, " | ", e.EventUrl)
  } else {
    response = "No matching meetup found."
  }

  data := struct {
    Text string `json:"text"`
  }{response}

  output, _ := json.Marshal(data)
  log.Println(string(output))
  w.Write(output)
}

func meetupRedir(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    keyword := string(vars["meetup"])

    events := filterEventsByKeyword(keyword)

    url := "http://www.meetup.com/memphis-technology-user-groups/"
    if len(events) > 0 {
      url = events[0].EventUrl
    }

    http.Redirect(w, r, url, 302)
}

func calendarJson(w http.ResponseWriter, r *http.Request) {
  keyword := string(r.FormValue("keyword"))

  events := filterEventsByKeyword(keyword)

  type CalendarJsonResponse struct {
    PR string `json:_pull_requests_appreciated`
    Meetups []meetup.Event
  }

  data := CalendarJsonResponse{"https://github.com/memgo/api",events}
  marsh, _ := json.Marshal(data)
  w.Write(marsh)
}

func filterEventsByKeyword(keyword string) (results []meetup.Event) {
  keyword = strings.TrimSpace(strings.ToLower(keyword))
  r, found := c.Get(keyword)
  if !found {
    log.Println("Cache miss for [", keyword, "]")
    latest, err := getLatestEvents()
    if err != nil {
      log.Println(err)
      return results
    }

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

    for _, element := range latest.Results {
      if strings.Index(strings.ToLower(element.Name), keyword) > -1 {
        results = append(results, element)
      }
    }

    c.Set(keyword, results, cache.DefaultExpiration)
  } else {
    results = r.([]meetup.Event)
  }

  return results
}

func getLatestEvents() (meetup.Events, error) {
  group_id := os.Getenv("MEETUP_GROUP_ID")
  api_key := os.Getenv("MEETUP_API_KEY")
  url := fmt.Sprintf("https://api.meetup.com/2/events?group_id=%s&key=%s", group_id, api_key)
  resp, err := http.Get(url)

  events := new(meetup.Events)
  decoder := json.NewDecoder(resp.Body)
  decoder.Decode(events)

  if err != nil {
    return *events, err
  }

  return *events, nil
}
<|endoftext|>


<commit_before>// Package adapt provides an algorithm for adaptive hierarchical interpolation
// with local refinements.
package adapt

import (
	"runtime"
)

const (
	LEVEL_MASK = 0x3F
	LEVEL_SIZE = 6
)

// Basis is a functional basis.
type Basis interface {
	// Compute evaluates the value of a basis function at a point.
	Compute([]uint64, []float64) float64

	// Integrate computes the integral of a basis function over the whole
	// domain.
	Integrate([]uint64) float64
}

// Grid is a sparse grid.
type Grid interface {
	// Compute returns the nodes corresponding to the given indices.
	Compute([]uint64) []float64

	// Refine returns the child indices corresponding to a set of parent
	// indices.
	Refine([]uint64) []uint64

	// Parent transforms an index into its parent index in the ith dimension.
	Parent([]uint64, uint)

	// Sibling transforms an index into its sibling index in the ith dimension.
	Sibling([]uint64, uint)
}

// Interpolator is an instance of the algorithm.
type Interpolator struct {
	config Config
	basis  Basis
	grid   Grid
}

// Progress contains information about the interpolation process.
type Progress struct {
	Level     uint      // Reached level
	Iteration uint      // Iteration number
	Active    uint      // Number of active nodes
	Passive   uint      // Number of passive nodes
	Integral  []float64 // Integral over the whole domain
}

// New creates a new interpolator.
func New(grid Grid, basis Basis, config *Config) *Interpolator {
	interpolator := &Interpolator{
		config: *config,
		basis:  basis,
		grid:   grid,
	}

	config = &interpolator.config
	if config.Workers == 0 {
		config.Workers = uint(runtime.GOMAXPROCS(0))
	}
	if config.Rate == 0 {
		config.Rate = 1
	}

	return interpolator
}

// Compute constructs an interpolant for a function.
func (self *Interpolator) Compute(target Target) *Surrogate {
	config := &self.config

	ni, no := target.Dimensions()
	nw := config.Workers

	surrogate := newSurrogate(ni, no)
	tracker := newQueue(ni, config)
	history := newHash(ni)

	na, np := uint(1), uint(0)

	indices := make([]uint64, na*ni)
	nodes := self.grid.Compute(indices)

	integral := make([]float64, no)

	iteration := uint(0)
	for na > 0 && np+na <= config.MaxEvaluations && iteration < config.MaxIterations {
		progress := Progress{
			Level:     tracker.lnow,
			Iteration: iteration,
			Active:    na,
			Passive:   np,
			Integral:  integral,
		}

		target.Monitor(&progress)

		surpluses := subtract(
			invoke(target.Compute, nodes, ni, no, nw),
			approximate(self.basis, surrogate.Indices, surrogate.Surpluses, nodes, ni, no, nw),
		)

		location := Location{}
		scores := measure(self.basis, indices, ni)
		for i := uint(0); i < na; i++ {
			location = Location{
				Node:    nodes[i*ni : (i+1)*ni],
				Surplus: surpluses[i*no : (i+1)*no],
				Volume:  scores[i],
			}
			scores[i] = target.Score(&location, &progress)
		}

		tracker.push(indices, scores)
		surrogate.push(indices, surpluses)
		surrogate.step(tracker.lnow, na)

		cumulate(self.basis, indices, surpluses, ni, no, na, integral)

		indices = history.unseen(self.grid.Refine(tracker.pull()))
		if config.Balance {
			indices = append(indices, balance(self.grid, history, indices)...)
		}
		nodes = self.grid.Compute(indices)

		np += na
		na = uint(len(indices)) / ni

		iteration++
	}

	return surrogate
}

// Evaluate computes the values of an interpolant at a set of points.
func (self *Interpolator) Evaluate(surrogate *Surrogate, points []float64) []float64 {
	return approximate(self.basis, surrogate.Indices, surrogate.Surpluses, points,
		surrogate.Inputs, surrogate.Outputs, self.config.Workers)
}

// Integrate computes the integral of an interpolant over the whole domain.
func (self *Interpolator) Integrate(surrogate *Surrogate) []float64 {
	ni, no, nn := surrogate.Inputs, surrogate.Outputs, surrogate.Nodes

	integral := make([]float64, no)
	cumulate(self.basis, surrogate.Indices, surrogate.Surpluses, ni, no, nn, integral)

	return integral
}
<commit_msg>A cosmetic change<commit_after>// Package adapt provides an algorithm for adaptive hierarchical interpolation
// with local refinements.
package adapt

import (
	"runtime"
)

const (
	LEVEL_MASK = 0x3F
	LEVEL_SIZE = 6
)

// Basis is a functional basis.
type Basis interface {
	// Compute evaluates the value of a basis function at a point.
	Compute([]uint64, []float64) float64

	// Integrate computes the integral of a basis function over the whole
	// domain.
	Integrate([]uint64) float64
}

// Grid is a sparse grid.
type Grid interface {
	// Compute returns the nodes corresponding to the given indices.
	Compute([]uint64) []float64

	// Refine returns the child indices corresponding to a set of parent
	// indices.
	Refine([]uint64) []uint64

	// Parent transforms an index into its parent index in the ith dimension.
	Parent([]uint64, uint)

	// Sibling transforms an index into its sibling index in the ith dimension.
	Sibling([]uint64, uint)
}

// Interpolator is an instance of the algorithm.
type Interpolator struct {
	config Config
	basis  Basis
	grid   Grid
}

// Progress contains information about the interpolation process.
type Progress struct {
	Level     uint      // Reached level
	Iteration uint      // Iteration number
	Active    uint      // Number of active nodes
	Passive   uint      // Number of passive nodes
	Integral  []float64 // Integral over the whole domain
}

// New creates a new interpolator.
func New(grid Grid, basis Basis, config *Config) *Interpolator {
	interpolator := &Interpolator{
		config: *config,
		basis:  basis,
		grid:   grid,
	}

	config = &interpolator.config
	if config.Workers == 0 {
		config.Workers = uint(runtime.GOMAXPROCS(0))
	}
	if config.Rate == 0.0 {
		config.Rate = 1.0
	}

	return interpolator
}

// Compute constructs an interpolant for a function.
func (self *Interpolator) Compute(target Target) *Surrogate {
	config := &self.config

	ni, no := target.Dimensions()
	nw := config.Workers

	surrogate := newSurrogate(ni, no)
	tracker := newQueue(ni, config)
	history := newHash(ni)

	na, np := uint(1), uint(0)

	indices := make([]uint64, na*ni)
	nodes := self.grid.Compute(indices)

	integral := make([]float64, no)

	iteration := uint(0)
	for na > 0 && np+na <= config.MaxEvaluations && iteration < config.MaxIterations {
		progress := Progress{
			Level:     tracker.lnow,
			Iteration: iteration,
			Active:    na,
			Passive:   np,
			Integral:  integral,
		}

		target.Monitor(&progress)

		surpluses := subtract(
			invoke(target.Compute, nodes, ni, no, nw),
			approximate(self.basis, surrogate.Indices, surrogate.Surpluses, nodes, ni, no, nw),
		)

		location := Location{}
		scores := measure(self.basis, indices, ni)
		for i := uint(0); i < na; i++ {
			location = Location{
				Node:    nodes[i*ni : (i+1)*ni],
				Surplus: surpluses[i*no : (i+1)*no],
				Volume:  scores[i],
			}
			scores[i] = target.Score(&location, &progress)
		}

		tracker.push(indices, scores)
		surrogate.push(indices, surpluses)
		surrogate.step(tracker.lnow, na)

		cumulate(self.basis, indices, surpluses, ni, no, na, integral)

		indices = history.unseen(self.grid.Refine(tracker.pull()))
		if config.Balance {
			indices = append(indices, balance(self.grid, history, indices)...)
		}
		nodes = self.grid.Compute(indices)

		np += na
		na = uint(len(indices)) / ni

		iteration++
	}

	return surrogate
}

// Evaluate computes the values of an interpolant at a set of points.
func (self *Interpolator) Evaluate(surrogate *Surrogate, points []float64) []float64 {
	return approximate(self.basis, surrogate.Indices, surrogate.Surpluses, points,
		surrogate.Inputs, surrogate.Outputs, self.config.Workers)
}

// Integrate computes the integral of an interpolant over the whole domain.
func (self *Interpolator) Integrate(surrogate *Surrogate) []float64 {
	ni, no, nn := surrogate.Inputs, surrogate.Outputs, surrogate.Nodes

	integral := make([]float64, no)
	cumulate(self.basis, surrogate.Indices, surrogate.Surpluses, ni, no, nn, integral)

	return integral
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"reflect"
	"time"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-ninja/support"
	"github.com/ninjasphere/inject"
	"github.com/ninjasphere/redigo/redis"
	"github.com/ninjasphere/sphere-go-homecloud/homecloud"
	"github.com/ninjasphere/sphere-go-homecloud/models"
	"github.com/ninjasphere/sphere-go-homecloud/rest"
	"github.com/ninjasphere/sphere-go-homecloud/state"
)

var log = logger.GetLogger("HomeCloud")

type postConstructable interface {
	PostConstruct() error
}

const shortForm = "2006-Jan-02"

var epoch, _ = time.Parse(shortForm, "2014-Dec-01")

func waitForNTP() {
	for {
		if time.Now().After(epoch) {
			break
		}

		time.Sleep(time.Second * 2)
	}
}

func main() {

	log.Infof("Welcome home, Ninja.")

	if config.Bool(true, "homecloud.waitForNTP") {
		waitForNTP()
	}

	// The MQTT Connection
	conn, err := ninja.Connect("sphere-go-homecloud")
	if err != nil {
		log.Fatalf("Failed to connect to sphere: %s", err)
	}

	// Our redis pool
	pool := &redis.Pool{
		MaxIdle:     config.MustInt("homecloud.redis.maxIdle"),
		IdleTimeout: config.MustDuration("homecloud.redis.idleTimeout"),
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", fmt.Sprintf(":%d", config.MustInt("homecloud.redis.port")))
			if err != nil {
				return nil, err
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	// Build the object graph using dependency injection
	injectables := []interface{}{}

	injectables = append(injectables, pool, conn)
	injectables = append(injectables, &homecloud.HomeCloud{}, &homecloud.TimeSeriesManager{}, &homecloud.DeviceManager{}, &homecloud.ModuleManager{})
	injectables = append(injectables, state.NewStateManager())
	injectables = append(injectables, &rest.RestServer{}, &homecloud.WebsocketServer{})
	injectables = append(injectables, models.GetInjectables()...)

	err = inject.Populate(injectables...)

	if err != nil {
		log.Fatalf("Failed to construct the object graph: %s", err)
	}

	// Run PostConstruct on any objects that have it
	for _, node := range injectables {
		if n, ok := node.(postConstructable); ok {
			go func(c postConstructable) {
				if err := c.PostConstruct(); err != nil {
					log.Fatalf("Failed PostConstruct on object %s: %s", reflect.TypeOf(c).String(), err)
				}
			}(n)
		}
	}

	support.WaitUntilSignal()
	// So long, and thanks for all the fish.
}
<commit_msg>Wait until we can connect to redis at least once before starting up<commit_after>package main

import (
	"fmt"
	"reflect"
	"time"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-ninja/support"
	"github.com/ninjasphere/inject"
	"github.com/ninjasphere/redigo/redis"
	"github.com/ninjasphere/sphere-go-homecloud/homecloud"
	"github.com/ninjasphere/sphere-go-homecloud/models"
	"github.com/ninjasphere/sphere-go-homecloud/rest"
	"github.com/ninjasphere/sphere-go-homecloud/state"
)

var log = logger.GetLogger("HomeCloud")

type postConstructable interface {
	PostConstruct() error
}

const shortForm = "2006-Jan-02"

var epoch, _ = time.Parse(shortForm, "2014-Dec-01")

func waitForNTP() {
	for {
		if time.Now().After(epoch) {
			break
		}

		time.Sleep(time.Second * 2)
	}
}

func main() {

	log.Infof("Welcome home, Ninja.")

	if config.Bool(true, "homecloud.waitForNTP") {
		waitForNTP()
	}

	// The MQTT Connection
	conn, err := ninja.Connect("sphere-go-homecloud")
	if err != nil {
		log.Fatalf("Failed to connect to sphere: %s", err)
	}

	// Our redis pool
	pool := &redis.Pool{
		MaxIdle:     config.MustInt("homecloud.redis.maxIdle"),
		IdleTimeout: config.MustDuration("homecloud.redis.idleTimeout"),
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", fmt.Sprintf(":%d", config.MustInt("homecloud.redis.port")))
			if err != nil {
				return nil, err
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}

	// Wait until we connect to redis successfully.
	for {
		c := pool.Get()

		if c.Err() == nil {
			c.Close()
			break
		}
		log.Warningf("Failed to connect to redis: %s", c.Err())
		time.Sleep(time.Second)
	}

	// Build the object graph using dependency injection
	injectables := []interface{}{}

	injectables = append(injectables, pool, conn)
	injectables = append(injectables, &homecloud.HomeCloud{}, &homecloud.TimeSeriesManager{}, &homecloud.DeviceManager{}, &homecloud.ModuleManager{})
	injectables = append(injectables, state.NewStateManager())
	injectables = append(injectables, &rest.RestServer{}, &homecloud.WebsocketServer{})
	injectables = append(injectables, models.GetInjectables()...)

	err = inject.Populate(injectables...)

	if err != nil {
		log.Fatalf("Failed to construct the object graph: %s", err)
	}

	// Run PostConstruct on any objects that have it
	for _, node := range injectables {
		if n, ok := node.(postConstructable); ok {
			go func(c postConstructable) {
				if err := c.PostConstruct(); err != nil {
					log.Fatalf("Failed PostConstruct on object %s: %s", reflect.TypeOf(c).String(), err)
				}
			}(n)
		}
	}

	support.WaitUntilSignal()
	// So long, and thanks for all the fish.
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", hello)
	log.Fatalln(http.ListenAndServe(":8080", nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello!"))
}
<commit_msg>Converted to a weather API<commit_after>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strings"
)

type weatherData struct {
	Name string `json:"name"`
	Main struct {
		Kelvin float64 `json:"temp"`
	} `json:"main"`
}

func query(city string) (*weatherData, error) {
	resp, err := http.Get("http://api.openweathermap.org/data/2.5/weather?q=" + city)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data := new(weatherData)
	if err := json.NewDecoder(resp.Body).Decode(data); err != nil {
		return nil, err
	}
	return data, nil
}

func weather(w http.ResponseWriter, r *http.Request) {
	city := strings.SplitN(r.URL.Path, "/", 3)[2]
	data, err := query(city)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	json.NewEncoder(w).Encode(data)
}

func main() {
	http.HandleFunc("/weather/", weather)
	log.Fatalln(http.ListenAndServe(":8080", nil))
}
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/lancetw/hcfd-forecast/db"
	"github.com/lancetw/hcfd-forecast/rain"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/newrelic/go-agent"
)

const timeZone = "Asia/Taipei"

var bot *linebot.Client

func main() {
	config := newrelic.NewConfig("hcfd-forecast", "99e64c65dd2b25a7eecd35b5bfe636b5b992c9bc")
	app, err := newrelic.NewApplication(config)

	strID := os.Getenv("ChannelID")
	numID, err := strconv.ParseInt(strID, 10, 64)
	if err != nil {
		log.Fatal("Wrong environment setting about ChannelID")
	}
	bot, err = linebot.NewClient(numID, os.Getenv("ChannelSecret"), os.Getenv("MID"))
	if err != nil {
		log.Println("Bot:", bot, " err:", err)
	}

	http.HandleFunc(newrelic.WrapHandleFunc(app, "/", homeHandler))
	http.HandleFunc(newrelic.WrapHandleFunc(app, "/callback", callbackHandler))

	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "HCFD world")
}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	received, err := bot.ParseRequest(r)
	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}
	for _, result := range received.Results {
		content := result.Content()
		if content != nil && content.IsOperation && content.OpType == linebot.OpTypeAddedAsFriend {
			op, err := content.OperationContent()
			if err != nil {
				log.Println(err)
				return
			}
			from := op.Params[0]

			user, err := bot.GetUserProfile([]string{from})
			if err != nil {
				log.Println(err)
				return
			}
			_, err = bot.SendText([]string{from}, user.Contacts[0].DisplayName+" 您好，目前可用指令為：「加入」「退出」「雨量」「警報」「貓圖」「狀態」「時間」")
			if err != nil {
				log.Println(err)
			}
		}

		if content != nil && content.IsMessage && content.ContentType == linebot.ContentTypeText {
			user, err := bot.GetUserProfile([]string{content.From})
			if err != nil {
				return
			}
			text, err := content.TextContent()
			if err != nil {
				log.Println(err)
			}
			cmd := strings.Fields(text.Text)

			switch cmd[0] {
			case "加入":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, addErr := c.Do("SADD", "user", content.From)
				if addErr != nil {
					log.Println("SADD to redis error", addErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 您好，已將您加入傳送對象，未來將會傳送天氣警報資訊給您 ^＿^ ")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "退出":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, setErr := c.Do("SREM", "user", content.From)
				if setErr != nil {
					log.Println("DEL to redis error", setErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 掰掰 Q＿Q")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "服務":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				count, countErr := redis.Int(c.Do("SCARD", "user"))
				if countErr != nil {
					log.Println(countErr)
				} else {
					_, err = bot.SendText([]string{content.From}, fmt.Sprintf("目前有 %d 人加入自動警訊服務。", count))
					if err != nil {
						log.Println(err)
					}
				}

				defer c.Close()

			case "狀態":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, getErr := redis.Int(c.Do("SISMEMBER", "user", content.From))
				if getErr != nil || status == 0 {
					_, err = bot.SendText([]string{content.From}, "目前沒有登記您的編號喔！")
					if err != nil {
						log.Println(err)
					}
				} else {
					_, err = bot.SendText([]string{content.From}, "您已經是傳送對象 :D")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "時間":
				local := time.Now()
				location, timezoneErr := time.LoadLocation(timeZone)
				if timezoneErr == nil {
					local = local.In(location)
				}
				_, err = bot.SendText([]string{content.From}, local.Format("2006/01/02 15:04:05"))
				if err != nil {
					log.Println(err)
				}

			case "雨量":
				target := []string{"新竹市"}
				if len(cmd) > 1 {
					target[0] = cmd[1]
				}

				msgs, _ := rain.GetRainingInfo(target, true)
				if len(msgs) == 0 {
					_, err = bot.SendText([]string{content.From}, "目前沒有雨量資訊！")
					if err != nil {
						log.Println(err)
					}
				} else {
					for _, msg := range msgs {
						_, err = bot.SendText([]string{content.From}, msg)
						if err != nil {
							log.Println(err)
						}
					}
				}

			case "警報":
				msgs, _ := rain.GetWarningInfo(nil)
				if len(msgs) <= 0 {
					msg := "目前沒有天氣警報資訊！"
					_, err = bot.SendText([]string{content.From}, msg)
					if err != nil {
						log.Println(err)
					}
				} else {
					for _, msg := range msgs {
						_, err = bot.SendText([]string{content.From}, msg)
						if err != nil {
							log.Println(err)
						}
					}
				}

			case "重開":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status0, clearErr0 := c.Do("DEL", "token0")
				if clearErr0 != nil {
					log.Println("DEL to redis error", clearErr0, status0)
				}
				status1, clearErr1 := c.Do("DEL", "token1")
				if clearErr1 != nil {
					log.Println("DEL to redis error", clearErr1, status1)
				}

				_, err = bot.SendText([]string{content.From}, "已重開")
				if err != nil {
					log.Println(err)
				}

			case "貓圖":
				type Cat struct {
					File string `json:"file"`
				}
				cat := new(Cat)
				getJSON("http://random.cat/meow", &cat)
				if cat.File != "" {
					_, err = bot.SendImage([]string{content.From}, cat.File, cat.File)
					if err != nil {
						log.Println(err)
					}
				}

			case "妹子":
				type MeisData struct {
					ID         string `json:"id"`
					Name       string `json:"name"`
					ImgNo      string `json:"img_no"`
					Fanpage    string `json:"fanpage"`
					Creator    string `json:"creator"`
					UpdateTime string `json:"update_time"`
				}

				type Beauty struct {
					Meis map[string]MeisData `json:"meis"`
				}

				beauty := new(Beauty)
				getJSONErr := getJSON("http://beauty.zones.gamebase.com.tw/wall?json", &beauty)
				if len(beauty.Meis) > 0 {
					for fbid, data := range beauty.Meis {
						image := fmt.Sprintf("https://graph.facebook.com/%s/picture?type=large", fbid)
						_, err = bot.SendImage([]string{content.From}, image, image)
						if err != nil {
							log.Println(err)
						}

						link := fmt.Sprintf("https://www.facebook.com/profile.php?id=%s", fbid)
						description := fmt.Sprintf("%s %s", data.Name, link)
						_, err = bot.SendText([]string{content.From}, description)
						if err != nil {
							log.Println(err)
						}
						break
					}

				}

				if getJSONErr != nil {
					log.Println(getJSONErr)
				}

			default:
				_, err = bot.SendText([]string{content.From}, "指令錯誤，請重試")
				if err != nil {
					log.Println(err)
				}

			}
		}
	}
}

// getJSON func
func getJSON(url string, target interface{}) error {
	r, err := http.Get(url)
	if err != nil {
		return err
	}
	defer r.Body.Close()

	return json.NewDecoder(r.Body).Decode(target)
}
<commit_msg>disabled newrelic<commit_after>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/lancetw/hcfd-forecast/db"
	"github.com/lancetw/hcfd-forecast/rain"
	"github.com/line/line-bot-sdk-go/linebot"
)

const timeZone = "Asia/Taipei"

var bot *linebot.Client

func main() {
	strID := os.Getenv("ChannelID")
	numID, err := strconv.ParseInt(strID, 10, 64)
	if err != nil {
		log.Fatal("Wrong environment setting about ChannelID")
	}
	bot, err = linebot.NewClient(numID, os.Getenv("ChannelSecret"), os.Getenv("MID"))
	if err != nil {
		log.Println("Bot:", bot, " err:", err)
	}

	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/callback", callbackHandler)

	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "HCFD world")
}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	received, err := bot.ParseRequest(r)
	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}
	for _, result := range received.Results {
		content := result.Content()
		if content != nil && content.IsOperation && content.OpType == linebot.OpTypeAddedAsFriend {
			op, err := content.OperationContent()
			if err != nil {
				log.Println(err)
				return
			}
			from := op.Params[0]

			user, err := bot.GetUserProfile([]string{from})
			if err != nil {
				log.Println(err)
				return
			}
			_, err = bot.SendText([]string{from}, user.Contacts[0].DisplayName+" 您好，目前可用指令為：「加入」「退出」「雨量」「警報」「貓圖」「狀態」「時間」")
			if err != nil {
				log.Println(err)
			}
		}

		if content != nil && content.IsMessage && content.ContentType == linebot.ContentTypeText {
			user, err := bot.GetUserProfile([]string{content.From})
			if err != nil {
				return
			}
			text, err := content.TextContent()
			if err != nil {
				log.Println(err)
			}
			cmd := strings.Fields(text.Text)

			switch cmd[0] {
			case "加入":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, addErr := c.Do("SADD", "user", content.From)
				if addErr != nil {
					log.Println("SADD to redis error", addErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 您好，已將您加入傳送對象，未來將會傳送天氣警報資訊給您 ^＿^ ")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "退出":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, setErr := c.Do("SREM", "user", content.From)
				if setErr != nil {
					log.Println("DEL to redis error", setErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 掰掰 Q＿Q")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "服務":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				count, countErr := redis.Int(c.Do("SCARD", "user"))
				if countErr != nil {
					log.Println(countErr)
				} else {
					_, err = bot.SendText([]string{content.From}, fmt.Sprintf("目前有 %d 人加入自動警訊服務。", count))
					if err != nil {
						log.Println(err)
					}
				}

				defer c.Close()

			case "狀態":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, getErr := redis.Int(c.Do("SISMEMBER", "user", content.From))
				if getErr != nil || status == 0 {
					_, err = bot.SendText([]string{content.From}, "目前沒有登記您的編號喔！")
					if err != nil {
						log.Println(err)
					}
				} else {
					_, err = bot.SendText([]string{content.From}, "您已經是傳送對象 :D")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "時間":
				local := time.Now()
				location, timezoneErr := time.LoadLocation(timeZone)
				if timezoneErr == nil {
					local = local.In(location)
				}
				_, err = bot.SendText([]string{content.From}, local.Format("2006/01/02 15:04:05"))
				if err != nil {
					log.Println(err)
				}

			case "雨量":
				target := []string{"新竹市"}
				if len(cmd) > 1 {
					target[0] = cmd[1]
				}

				msgs, _ := rain.GetRainingInfo(target, true)
				if len(msgs) == 0 {
					_, err = bot.SendText([]string{content.From}, "目前沒有雨量資訊！")
					if err != nil {
						log.Println(err)
					}
				} else {
					for _, msg := range msgs {
						_, err = bot.SendText([]string{content.From}, msg)
						if err != nil {
							log.Println(err)
						}
					}
				}

			case "警報":
				msgs, _ := rain.GetWarningInfo(nil)
				if len(msgs) <= 0 {
					msg := "目前沒有天氣警報資訊！"
					_, err = bot.SendText([]string{content.From}, msg)
					if err != nil {
						log.Println(err)
					}
				} else {
					for _, msg := range msgs {
						_, err = bot.SendText([]string{content.From}, msg)
						if err != nil {
							log.Println(err)
						}
					}
				}

			case "重開":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status0, clearErr0 := c.Do("DEL", "token0")
				if clearErr0 != nil {
					log.Println("DEL to redis error", clearErr0, status0)
				}
				status1, clearErr1 := c.Do("DEL", "token1")
				if clearErr1 != nil {
					log.Println("DEL to redis error", clearErr1, status1)
				}

				_, err = bot.SendText([]string{content.From}, "已重開")
				if err != nil {
					log.Println(err)
				}

			case "貓圖":
				type Cat struct {
					File string `json:"file"`
				}
				cat := new(Cat)
				getJSON("http://random.cat/meow", &cat)
				if cat.File != "" {
					_, err = bot.SendImage([]string{content.From}, cat.File, cat.File)
					if err != nil {
						log.Println(err)
					}
				}

			case "妹子":
				type MeisData struct {
					ID         string `json:"id"`
					Name       string `json:"name"`
					ImgNo      string `json:"img_no"`
					Fanpage    string `json:"fanpage"`
					Creator    string `json:"creator"`
					UpdateTime string `json:"update_time"`
				}

				type Beauty struct {
					Meis map[string]MeisData `json:"meis"`
				}

				beauty := new(Beauty)
				getJSONErr := getJSON("http://beauty.zones.gamebase.com.tw/wall?json", &beauty)
				if len(beauty.Meis) > 0 {
					for fbid, data := range beauty.Meis {
						image := fmt.Sprintf("https://graph.facebook.com/%s/picture?type=large", fbid)
						_, err = bot.SendImage([]string{content.From}, image, image)
						if err != nil {
							log.Println(err)
						}

						link := fmt.Sprintf("https://www.facebook.com/profile.php?id=%s", fbid)
						description := fmt.Sprintf("%s %s", data.Name, link)
						_, err = bot.SendText([]string{content.From}, description)
						if err != nil {
							log.Println(err)
						}
						break
					}

				}

				if getJSONErr != nil {
					log.Println(getJSONErr)
				}

			default:
				_, err = bot.SendText([]string{content.From}, "指令錯誤，請重試")
				if err != nil {
					log.Println(err)
				}

			}
		}
	}
}

// getJSON func
func getJSON(url string, target interface{}) error {
	r, err := http.Get(url)
	if err != nil {
		return err
	}
	defer r.Body.Close()

	return json.NewDecoder(r.Body).Decode(target)
}
<|endoftext|>


<commit_before>package main

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

	"github.com/confluentinc/confluent-kafka-go/kafka"
	"github.com/pkg/errors"

	log "github.com/sirupsen/logrus"
)

var appLogger *log.Entry

const APP_NAME = "PoESocial" // can't contain '_'
const BROKER = "localhost:9092"
const DELEGATOR = "http://localhost:8080"
const WRITE_PROXY_LISTEN = ":9064"
const CONSUMER_GROUP_PREFIX = "spacer"
const SCHEMA_VERSION = 0

func main() {
	routes := make(map[string]string)
	// should support multiple app in one proxy
	routes["PoESocial_stat:UPDATE"] = fmt.Sprintf("%s/%s", DELEGATOR, "get_stashes")

	topic := fmt.Sprintf("^%s_*", APP_NAME)

	appLogger = log.WithFields(log.Fields{"app_name": APP_NAME, "broker": BROKER})

	// create a producer
	producer, err := kafka.NewProducer(&kafka.ConfigMap{"bootstrap.servers": BROKER})
	if err != nil {
		appLogger.Fatal("Unable to create producer:", err)
	}

	// generic logging for producer
	go func() {
		for e := range producer.Events() {
			switch ev := e.(type) {
			case *kafka.Message:
				m := ev
				if m.TopicPartition.Error != nil {
					appLogger.Fatal("delivery failed", m.TopicPartition.Error)
				} else {
					appLogger.Infof("Delivered message to topic %s [%d] at offset %v\n",
						*m.TopicPartition.Topic, m.TopicPartition.Partition, m.TopicPartition.Offset)
				}
				return
			default:
				appLogger.Info("Ignored event: %s\n", ev)
			}
		}
	}()

	// create a proxy to let functions write data back to kafka
	writeProxy, err := NewWriteProxy(producer.ProduceChannel())
	if err != nil {
		appLogger.Fatal(err)
	}
	go http.ListenAndServe(WRITE_PROXY_LISTEN, writeProxy)

	groupID := strings.Join([]string{CONSUMER_GROUP_PREFIX, APP_NAME, fmt.Sprintf("%d", SCHEMA_VERSION)}, "-")

	consumer, err := kafka.NewConsumer(&kafka.ConfigMap{
		"bootstrap.servers":               BROKER,
		"group.id":                        groupID,
		"session.timeout.ms":              6000,
		"go.application.rebalance.enable": true,
		"default.topic.config":            kafka.ConfigMap{"auto.offset.reset": "earliest"},
		"metadata.max.age.ms":             1000,
		"enable.auto.commit":              false,
	})
	if err != nil {
		appLogger.Fatal("Failed to create consumer", err)
	}
	defer consumer.Close()

	err = consumer.SubscribeTopics([]string{topic}, nil)
	if err != nil {
		appLogger.Fatal("Failed to subscribe topics %v, %s\n", topic, err)
	}

	// periodically refresh metadata to know if there's any new topic created
	go refreshMetadata(consumer, appLogger)

	// start the consumer loop
	for {
		ev := consumer.Poll(100)
		if ev == nil {
			continue
		}
		switch e := ev.(type) {
		case kafka.AssignedPartitions:
			appLogger.Info(e)
			consumer.Assign(e.Partitions)
		case kafka.RevokedPartitions:
			appLogger.Info(e)
			consumer.Unassign()
		case *kafka.Message:
			appLogger.Info("%% Message on ", e.TopicPartition)

			routePath := fmt.Sprintf("%s:UPDATE", *e.TopicPartition.Topic)
			appLogger.Info("Looking up route ", routePath)

			if _, ok := routes[routePath]; !ok {
				appLogger.Info("Route not found")
				continue
			}

			err := invoke(routes[routePath], []byte(string(e.Value)))
			if err != nil {
				appLogger.Error("Invocation Error: %v\n", err)
				continue
			}
			_, err = consumer.CommitMessage(e)
			if err != nil {
				appLogger.Error("Commit Error: %v %v\n", e, err)
			}
		case kafka.PartitionEOF:
			appLogger.Info("%% Reached", e)
		case kafka.Error:
			appLogger.Fatal("%% Error", e)
		default:
			appLogger.Info("Unknown", e)
		}
	}
}

func invoke(route string, data []byte) error {
	appLogger.Infof("Invoking %s\n", route)
	resp, err := http.Post(route, "application/json", bytes.NewReader(data))

	if err != nil {
		return errors.Wrap(err, "post event handler failed")
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return errors.New(fmt.Sprintf("Function not ok: %d", resp.StatusCode))

	}
	return nil
}

type WriteProxy struct {
	produceChan chan *kafka.Message
}

func NewWriteProxy(produceChan chan *kafka.Message) (*WriteProxy, error) {
	proxy := WriteProxy{produceChan}
	return &proxy, nil
}

func (p WriteProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		appLogger.Error("read body failed", err)
		w.WriteHeader(400)
		return
	}
	var write WriteRequest
	err = json.Unmarshal(body, &write)
	if err != nil {
		appLogger.Errorf("decode body failed %v\n", err)
		w.WriteHeader(400)
		return
	}
	topic := fmt.Sprintf("%s_%s", APP_NAME, write.Object)
	for key, value := range write.Data {
		fmt.Println("Writing", write.Object, key)
		p.produceChan <- &kafka.Message{
			TopicPartition: kafka.TopicPartition{Topic: &topic, Partition: kafka.PartitionAny},
			Key:            []byte(key),
			Value:          value,
		}
	}
	fmt.Fprintf(w, "ok")
}

type WriteRequest struct {
	Object string                     `json:"object"`
	Data   map[string]json.RawMessage `json:"data"`
}

func refreshMetadata(consumer *kafka.Consumer, logger *log.Entry) {
	for {
		metadata, err := consumer.GetMetadata(nil, true, 100)
		if err != nil {
			// somethimes it just timed out, ignore
			logger.Warn("Unable to refresh metadata: ", err)
			continue
		}
		keys := []string{}
		for k, _ := range metadata.Topics {
			keys = append(keys, k)
		}
		// logger.Info("metadata: ", keys)
		time.Sleep(5 * time.Second)
	}
}
<commit_msg>properly handle user error returned by function<commit_after>package main

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

	"github.com/confluentinc/confluent-kafka-go/kafka"
	"github.com/pkg/errors"

	log "github.com/sirupsen/logrus"
)

var appLogger *log.Entry

const APP_NAME = "PoESocial" // can't contain '_'
const BROKER = "localhost:9092"
const DELEGATOR = "http://localhost:8080"
const WRITE_PROXY_LISTEN = ":9064"
const CONSUMER_GROUP_PREFIX = "spacer"
const SCHEMA_VERSION = 0

func main() {
	routes := make(map[string]string)
	// should support multiple app in one proxy
	routes["PoESocial_stat:UPDATE"] = fmt.Sprintf("%s/%s", DELEGATOR, "get_stashes")

	topic := fmt.Sprintf("^%s_*", APP_NAME)

	appLogger = log.WithFields(log.Fields{"app_name": APP_NAME, "broker": BROKER})

	// create a producer
	producer, err := kafka.NewProducer(&kafka.ConfigMap{"bootstrap.servers": BROKER})
	if err != nil {
		appLogger.Fatal("Unable to create producer:", err)
	}

	// generic logging for producer
	go func() {
		for e := range producer.Events() {
			switch ev := e.(type) {
			case *kafka.Message:
				m := ev
				if m.TopicPartition.Error != nil {
					appLogger.Fatal("delivery failed", m.TopicPartition.Error)
				} else {
					appLogger.Infof("Delivered message to topic %s [%d] at offset %v\n",
						*m.TopicPartition.Topic, m.TopicPartition.Partition, m.TopicPartition.Offset)
				}
				return
			default:
				appLogger.Info("Ignored event: %s\n", ev)
			}
		}
	}()

	// create a proxy to let functions write data back to kafka
	writeProxy, err := NewWriteProxy(producer.ProduceChannel())
	if err != nil {
		appLogger.Fatal(err)
	}
	go http.ListenAndServe(WRITE_PROXY_LISTEN, writeProxy)

	groupID := strings.Join([]string{CONSUMER_GROUP_PREFIX, APP_NAME, fmt.Sprintf("%d", SCHEMA_VERSION)}, "-")

	consumer, err := kafka.NewConsumer(&kafka.ConfigMap{
		"bootstrap.servers":               BROKER,
		"group.id":                        groupID,
		"session.timeout.ms":              6000,
		"go.application.rebalance.enable": true,
		"default.topic.config":            kafka.ConfigMap{"auto.offset.reset": "earliest"},
		"metadata.max.age.ms":             1000,
		"enable.auto.commit":              false,
	})
	if err != nil {
		appLogger.Fatal("Failed to create consumer", err)
	}
	defer consumer.Close()

	err = consumer.SubscribeTopics([]string{topic}, nil)
	if err != nil {
		appLogger.Fatal("Failed to subscribe topics %v, %s\n", topic, err)
	}

	// periodically refresh metadata to know if there's any new topic created
	go refreshMetadata(consumer, appLogger)

	// start the consumer loop
	for {
		ev := consumer.Poll(100)
		if ev == nil {
			continue
		}
		switch e := ev.(type) {
		case kafka.AssignedPartitions:
			appLogger.Info(e)
			consumer.Assign(e.Partitions)
		case kafka.RevokedPartitions:
			appLogger.Info(e)
			consumer.Unassign()
		case *kafka.Message:
			appLogger.Info("%% Message on ", e.TopicPartition)

			routePath := fmt.Sprintf("%s:UPDATE", *e.TopicPartition.Topic)
			appLogger.Info("Looking up route ", routePath)

			if _, ok := routes[routePath]; !ok {
				appLogger.Info("Route not found")
				continue
			}

			err := invoke(routes[routePath], []byte(string(e.Value)))
			if err != nil {
				appLogger.Error("Invocation Error: %v\n", err)
				continue
			}
			_, err = consumer.CommitMessage(e)
			if err != nil {
				appLogger.Error("Commit Error: %v %v\n", e, err)
			}
		case kafka.PartitionEOF:
			appLogger.Info("%% Reached", e)
		case kafka.Error:
			appLogger.Fatal("%% Error", e)
		default:
			appLogger.Info("Unknown", e)
		}
	}
}

func invoke(route string, data []byte) error {
	appLogger.Infof("Invoking %s\n", route)
	resp, err := http.Post(route, "application/json", bytes.NewReader(data))

	if err != nil {
		return errors.Wrap(err, "post event handler failed")
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return errors.New(fmt.Sprintf("Function not ok: %d", resp.StatusCode))
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return errors.Wrap(err, "unable to read function return value")
	}

	// TODO: function return {error: ...} 也要當作出錯
	var ret map[string]json.RawMessage
	err := json.Unmarshal(body, &ret)
	if err != nil {
		return errors.Wrap(err, "Failed to decode JSON")
	}

	if msg, ok := ret["error"]; ok {
		return errors.New(fmt.Sprintf("Function returned error: %s", msg))
	}
	return nil
}

type WriteProxy struct {
	produceChan chan *kafka.Message
}

func NewWriteProxy(produceChan chan *kafka.Message) (*WriteProxy, error) {
	proxy := WriteProxy{produceChan}
	return &proxy, nil
}

func (p WriteProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		appLogger.Error("read body failed", err)
		w.WriteHeader(400)
		return
	}
	var write WriteRequest
	err = json.Unmarshal(body, &write)
	if err != nil {
		appLogger.Errorf("decode body failed %v\n", err)
		w.WriteHeader(400)
		return
	}
	topic := fmt.Sprintf("%s_%s", APP_NAME, write.Object)
	for key, value := range write.Data {
		fmt.Println("Writing", write.Object, key)
		p.produceChan <- &kafka.Message{
			TopicPartition: kafka.TopicPartition{Topic: &topic, Partition: kafka.PartitionAny},
			Key:            []byte(key),
			Value:          value,
		}
	}
	fmt.Fprintf(w, "ok")
}

type WriteRequest struct {
	Object string                     `json:"object"`
	Data   map[string]json.RawMessage `json:"data"`
}

func refreshMetadata(consumer *kafka.Consumer, logger *log.Entry) {
	for {
		metadata, err := consumer.GetMetadata(nil, true, 100)
		if err != nil {
			// somethimes it just timed out, ignore
			logger.Warn("Unable to refresh metadata: ", err)
			continue
		}
		keys := []string{}
		for k, _ := range metadata.Topics {
			keys = append(keys, k)
		}
		// logger.Info("metadata: ", keys)
		time.Sleep(5 * time.Second)
	}
}
<|endoftext|>


<commit_before>package panoptikos

import (
	"appengine"
	"github.com/ChristianSiegert/panoptikos/sanitizer"
	"html/template"
	"io/ioutil"
	"net/http"
	"strings"
)

type Page struct {
	CssFilename    string
	IsDevAppServer bool
	JsFilename     string
}

var page Page
var cachedTemplate *template.Template

var isDevAppServer = appengine.IsDevAppServer()

func init() {
	page.IsDevAppServer = isDevAppServer
	page.CssFilename = cssFilename
	page.JsFilename = jsFilename

	var err error
	if cachedTemplate, err = loadTemplate(); err != nil {
		http.HandleFunc("/", handleInitError(err))
		return
	}

	http.HandleFunc("/", handleRequest)
	http.HandleFunc("/_ah/warmup", handleWarmUpRequest)
}

func loadTemplate() (*template.Template, error) {
	fileContent, err := ioutil.ReadFile("views/layouts/default.html")

	if err != nil {
		return nil, err
	}

	whitespaceStrippedFileContent := sanitizer.RemoveWhitespace(string(fileContent))
	template_, err := template.New("default").Parse(whitespaceStrippedFileContent)

	if err != nil {
		return nil, err
	}

	return template_, nil
}

func handleRequest(responseWriter http.ResponseWriter, request *http.Request) {
	if request.URL.Path == "/" {
		if err := cachedTemplate.Execute(responseWriter, page); err != nil {
			context := appengine.NewContext(request)
			context.Errorf("panoptikos: Couldn't execute cached template: %s", err)
			http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
			return
		}

		return
	}

	// Redirect legacy URLs to home page to prevent 404 Not Found errors
	if request.URL.Path == "/feedback" ||
		request.URL.Path == "/feeds" ||
		request.URL.Path == "/preferences" ||
		strings.Index(request.URL.Path, "/pictures") == 0 ||
		strings.Index(request.URL.Path, "/referrals/by-source") == 0 ||
		strings.Index(request.URL.Path, "/sources/select") == 0 {
		http.Redirect(responseWriter, request, "/", http.StatusMovedPermanently)
		return
	}

	http.NotFound(responseWriter, request)
}

func handleWarmUpRequest(responseWriter http.ResponseWriter, request *http.Request) {
	context := appengine.NewContext(request)
	context.Debugf("panoptikos: Received warm-up request.")
}

func handleInitError(err error) func(http.ResponseWriter, *http.Request) {
	return func(responseWriter http.ResponseWriter, request *http.Request) {
		context := appengine.NewContext(request)
		context.Errorf("panoptikos: Initializing the app failed: %s", err)
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
	}
}
<commit_msg>Removed handling of URL '/_ah/warmup' because Google App Engine never called it. URL '/feeds/atom/' is now handled explicitly to avoid expensive RegEx testing in function 'handleRequest'.<commit_after>package panoptikos

import (
	"appengine"
	"github.com/ChristianSiegert/panoptikos/sanitizer"
	"html/template"
	"io/ioutil"
	"net/http"
	"strings"
)

type Page struct {
	CssFilename    string
	IsDevAppServer bool
	JsFilename     string
}

var page Page
var cachedTemplate *template.Template

var isDevAppServer = appengine.IsDevAppServer()

func init() {
	page.IsDevAppServer = isDevAppServer
	page.CssFilename = cssFilename
	page.JsFilename = jsFilename

	var err error
	if cachedTemplate, err = loadTemplate(); err != nil {
		http.HandleFunc("/", handleInitError(err))
		return
	}

	http.HandleFunc("/", handleRequest)

	// The most requested URL that doesn't exist anymore. Handling it outside of
	// handleRequest avoids expensing RegEx testing that is going on in
	// handleRequest.
	http.HandleFunc("/feeds/atom/", handleFeedRequest)
}

func loadTemplate() (*template.Template, error) {
	fileContent, err := ioutil.ReadFile("views/layouts/default.html")

	if err != nil {
		return nil, err
	}

	whitespaceStrippedFileContent := sanitizer.RemoveWhitespace(string(fileContent))
	template_, err := template.New("default").Parse(whitespaceStrippedFileContent)

	if err != nil {
		return nil, err
	}

	return template_, nil
}

func handleRequest(responseWriter http.ResponseWriter, request *http.Request) {
	if request.URL.Path == "/" {
		if err := cachedTemplate.Execute(responseWriter, page); err != nil {
			context := appengine.NewContext(request)
			context.Errorf("panoptikos: Couldn't execute cached template: %s", err)
			http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
			return
		}

		return
	}

	// Redirect legacy URLs to home page to prevent 404 Not Found errors
	if request.URL.Path == "/feedback" ||
		request.URL.Path == "/feeds" ||
		request.URL.Path == "/preferences" ||
		strings.Index(request.URL.Path, "/pictures") == 0 ||
		strings.Index(request.URL.Path, "/referrals/by-source") == 0 ||
		strings.Index(request.URL.Path, "/sources/select") == 0 {
		http.Redirect(responseWriter, request, "/", http.StatusMovedPermanently)
		return
	}

	http.NotFound(responseWriter, request)
}

func handleFeedRequest(responseWriter http.ResponseWriter, request *http.Request) {
	http.NotFound(responseWriter, request)
}

func handleInitError(err error) func(http.ResponseWriter, *http.Request) {
	return func(responseWriter http.ResponseWriter, request *http.Request) {
		context := appengine.NewContext(request)
		context.Errorf("panoptikos: Initializing the app failed: %s", err)
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
	}
}
<|endoftext|>