<commit_before>/**
 * Copyright 2015 Comcast Cable Communications Management, LLC
 *
 * 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 jtl

// inbound plugins, currently only supported plugins are webhook and stdin,
// other plugins could be provided for websocket, kafka, sqs etc.

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	. "github.com/Comcast/eel/util"
)

type InboundPlugin interface {
	StartPlugin(Context)
	GetSettings() *PluginSettings
	StopPlugin(Context)
	IsActive() bool
}

type PluginSettings struct {
	Type       string
	Name       string
	Active     bool
	RestartOk  bool
	AutoStart  bool
	ExitOnErr  bool
	Parameters map[string]interface{}
	Stats      PluginStats
}

type PluginStats struct {
	MessageCount      uint64
	MessagesPerSecond uint64
	MessagesPerMinute uint64
	Misc              map[string]interface{}
	sync.RWMutex
}

type NewInboundPlugin func(*PluginSettings) InboundPlugin

type PluginConfigList []*PluginSettings

// plugins by name
var inboundPluginMap = make(map[string]InboundPlugin, 0)

// plugins by type
var inboundPluginTypeMap = make(map[string]NewInboundPlugin, 0)

var pluginConfigList PluginConfigList

// RegisterInboundPlugin registers an (external) plugin implementation by plugin type
func RegisterInboundPluginType(newPlugin NewInboundPlugin, pluginType string) {
	inboundPluginTypeMap[pluginType] = newPlugin
}

func GetInboundPluginByType(pluginType string) InboundPlugin {
	// currently only one active plugin per type allowed!!!
	for _, v := range inboundPluginMap {
		if v.GetSettings().Type == pluginType {
			return v
		}
	}
	return nil
}

func GetInboundPluginByName(name string) InboundPlugin {
	return inboundPluginMap[name]
}

func PluginConfigHandler(w http.ResponseWriter, r *http.Request) {
	ctx := Gctx.SubContext()
	w.Header().Set("Content-Type", "application/json")
	state := make(map[string]interface{}, 0)
	state["Version"] = GetConfig(ctx).Version
	state["PluginConfigs"] = pluginConfigList
	buf, err := json.MarshalIndent(state, "", "\t")
	if err != nil {
		fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
	} else {
		fmt.Fprintf(w, string(buf))
	}
}

func ManagePluginsUIHandler(w http.ResponseWriter, r *http.Request) {
	ctx := Gctx.SubContext()
	pluginsTemplate, err := template.ParseFiles("web/plugins.html")
	if err != nil {
		ctx.Log().Error("error_type", "manage_plugins", "cause", "template_parse_error", "error", err.Error())
	}
	operation := r.FormValue("operation")
	name := r.FormValue("name")
	if operation == "Start" && name != "" {
		p := GetInboundPluginByName(name)
		if p != nil && !p.IsActive() {
			p.StartPlugin(ctx)
			time.Sleep(1 * time.Second)
		}
	} else if operation == "Stop" && name != "" {
		p := GetInboundPluginByName(name)
		if p != nil && p.IsActive() {
			p.StopPlugin(ctx)
		}
	}
	psl := make([]*PluginSettings, 0)
	for _, p := range inboundPluginMap {
		psl = append(psl, p.GetSettings())
	}
	err = pluginsTemplate.Execute(w, psl)
	if err != nil {
		ctx.Log().Error("error_type", "manage_plugins", "cause", "template_exec_error", "error", err.Error())
	}
}

func ManagePluginsHandler(w http.ResponseWriter, r *http.Request) {
	ctx := Gctx.SubContext()
	path := strings.Split(r.URL.Path, "/")
	pName := path[len(path)-2]
	p := GetInboundPluginByName(pName)
	if p == nil {
		ctx.Log().Error("error_type", "manage_plugins", "cause", "unknown_plugin", "error", "unknown plugin "+pName)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, `{"error":"%s"}`, "unknown plugin "+pName)
		return
	}
	op := path[len(path)-1]
	switch op {
	case "start":
		if p.GetSettings().RestartOk == false {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_cannot_restart", "error", "plugin cannot restart "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin cannot restart "+pName)
			return
		}
		if p.GetSettings().Active == true {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_already_running", "error", "plugin already running "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin already running "+pName)
			return
		}
		p.StartPlugin(ctx)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `{"plugin":"%s", "action":"started"}`, pName)
	case "stop":
		if p.GetSettings().RestartOk == false {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_cannot_restart", "error", "plugin cannot restart "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin cannot restart "+pName)
			return
		}
		if p.GetSettings().Active == false {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_stopped", "error", "plugin stopped "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin stopped "+pName)
			return
		}
		p.StopPlugin(ctx)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `{"plugin":"%s", "action":"stopped"}`, pName)
	case "state":
		state := make(map[string]interface{}, 0)
		state["Version"] = GetConfig(ctx).Version
		state["PluginConfigs"] = p.GetSettings()
		buf, err := json.MarshalIndent(state, "", "\t")
		if err != nil {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			fmt.Fprintf(w, string(buf))
		}
	case "config":
		state := make(map[string]interface{}, 0)
		state["Version"] = GetConfig(ctx).Version
		state["PluginConfigs"] = p.GetSettings()
		buf, err := json.MarshalIndent(state, "", "\t")
		if err != nil {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			fmt.Fprintf(w, string(buf))
		}
	default:
		ctx.Log().Error("error_type", "manage_plugins", "cause", "unknown_op", "error", "unknown op "+op)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, `{"error":"%s"}`, "unknown plugin "+pName)
		return
	}
}

func GetPluginConfigList(ctx Context) PluginConfigList {
	configFile, err := os.Open(filepath.Join(filepath.Dir(ConfigPath), "plugins.json"))
	if err != nil {
		// csv-context-go may not be ready yet for logging
		fmt.Printf("{ \"error\" : \"%s\" }", err.Error())
		ctx.Log().Error("error_type", "get_plugin_config", "cause", "open_config", "error", err.Error())
		os.Exit(1)
	}
	defer configFile.Close()
	configData, err := ioutil.ReadAll(configFile)
	if err != nil {
		fmt.Printf("{ \"error\" : \"%s\" }", err.Error())
		ctx.Log().Error("error_type", "get_plugin_config", "cause", "read_config", "error", err.Error())
		os.Exit(1)
	}
	var config PluginConfigList
	err = json.Unmarshal(configData, &config)
	if err != nil {
		fmt.Printf("{ \"error\" : \"%s\" }", err.Error())
		ctx.Log().Error("error_type", "get_plugin_config", "cause", "parse_config", "error", err.Error())
		os.Exit(1)
	}
	return config
}

func LoadInboundPlugins(ctx Context) {
	// load plugin configs
	pluginConfigList = GetPluginConfigList(ctx)
	for _, e := range pluginConfigList {
		// dependency injection
		np := inboundPluginTypeMap[e.Type]
		if np == nil {
			ctx.Log().Error("error_type", "bad_plugin_config", "cause", "unknown_plugin_type", "plugin_type", e.Type)
		} else {
			inboundPluginMap[e.Name] = np(e)
		}
	}
	// launch plugins
	for k, v := range inboundPluginMap {
		if v.GetSettings().AutoStart {
			ctx.Log().Info("action", "launching_inbound_plugin", "plugin_name", k, "pugin_type", v.GetSettings().Type)
			v.StartPlugin(ctx)
		} else {
			ctx.Log().Info("action", "skipping_inactive_plugin", "plugin_name", k, "pugin_type", v.GetSettings().Type)
		}
	}
	// need sync path in inproc.go
	if GetInboundPluginByType("WEBHOOK") != nil {
		syncPath := GetInboundPluginByType("WEBHOOK").GetSettings().Parameters["EventProcPath"]
		Gctx.AddConfigValue(EelSyncPath, syncPath)
	} else {
		Gctx.AddConfigValue(EelSyncPath, "")
	}
}
<commit_msg>better error message for plugin api<commit_after>/**
 * Copyright 2015 Comcast Cable Communications Management, LLC
 *
 * 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 jtl

// inbound plugins, currently only supported plugins are webhook and stdin,
// other plugins could be provided for websocket, kafka, sqs etc.

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	. "github.com/Comcast/eel/util"
)

type InboundPlugin interface {
	StartPlugin(Context)
	GetSettings() *PluginSettings
	StopPlugin(Context)
	IsActive() bool
}

type PluginSettings struct {
	Type       string
	Name       string
	Active     bool
	RestartOk  bool
	AutoStart  bool
	ExitOnErr  bool
	Parameters map[string]interface{}
	Stats      PluginStats
}

type PluginStats struct {
	MessageCount      uint64
	MessagesPerSecond uint64
	MessagesPerMinute uint64
	Misc              map[string]interface{}
	sync.RWMutex
}

type NewInboundPlugin func(*PluginSettings) InboundPlugin

type PluginConfigList []*PluginSettings

// plugins by name
var inboundPluginMap = make(map[string]InboundPlugin, 0)

// plugins by type
var inboundPluginTypeMap = make(map[string]NewInboundPlugin, 0)

var pluginConfigList PluginConfigList

// RegisterInboundPlugin registers an (external) plugin implementation by plugin type
func RegisterInboundPluginType(newPlugin NewInboundPlugin, pluginType string) {
	inboundPluginTypeMap[pluginType] = newPlugin
}

func GetInboundPluginByType(pluginType string) InboundPlugin {
	// currently only one active plugin per type allowed!!!
	for _, v := range inboundPluginMap {
		if v.GetSettings().Type == pluginType {
			return v
		}
	}
	return nil
}

func GetInboundPluginByName(name string) InboundPlugin {
	return inboundPluginMap[name]
}

func PluginConfigHandler(w http.ResponseWriter, r *http.Request) {
	ctx := Gctx.SubContext()
	w.Header().Set("Content-Type", "application/json")
	state := make(map[string]interface{}, 0)
	state["Version"] = GetConfig(ctx).Version
	state["PluginConfigs"] = pluginConfigList
	buf, err := json.MarshalIndent(state, "", "\t")
	if err != nil {
		fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
	} else {
		fmt.Fprintf(w, string(buf))
	}
}

func ManagePluginsUIHandler(w http.ResponseWriter, r *http.Request) {
	ctx := Gctx.SubContext()
	pluginsTemplate, err := template.ParseFiles("web/plugins.html")
	if err != nil {
		ctx.Log().Error("error_type", "manage_plugins", "cause", "template_parse_error", "error", err.Error())
	}
	operation := r.FormValue("operation")
	name := r.FormValue("name")
	if operation == "Start" && name != "" {
		p := GetInboundPluginByName(name)
		if p != nil && !p.IsActive() {
			p.StartPlugin(ctx)
			time.Sleep(1 * time.Second)
		}
	} else if operation == "Stop" && name != "" {
		p := GetInboundPluginByName(name)
		if p != nil && p.IsActive() {
			p.StopPlugin(ctx)
		}
	}
	psl := make([]*PluginSettings, 0)
	for _, p := range inboundPluginMap {
		psl = append(psl, p.GetSettings())
	}
	err = pluginsTemplate.Execute(w, psl)
	if err != nil {
		ctx.Log().Error("error_type", "manage_plugins", "cause", "template_exec_error", "error", err.Error())
	}
}

func ManagePluginsHandler(w http.ResponseWriter, r *http.Request) {
	ctx := Gctx.SubContext()
	path := strings.Split(r.URL.Path, "/")
	pName := path[len(path)-2]
	p := GetInboundPluginByName(pName)
	if p == nil {
		ctx.Log().Error("error_type", "manage_plugins", "cause", "unknown_plugin", "error", "unknown plugin "+pName)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, `{"error":"%s"}`, "unknown plugin "+pName)
		return
	}
	op := path[len(path)-1]
	switch op {
	case "start":
		if p.GetSettings().RestartOk == false {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_cannot_restart", "error", "plugin cannot restart "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin cannot restart "+pName)
			return
		}
		if p.GetSettings().Active == true {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_already_running", "error", "plugin already running "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin already running "+pName)
			return
		}
		p.StartPlugin(ctx)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `{"plugin":"%s", "action":"started"}`, pName)
	case "stop":
		if p.GetSettings().RestartOk == false {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_cannot_restart", "error", "plugin cannot restart "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin cannot restart "+pName)
			return
		}
		if p.GetSettings().Active == false {
			ctx.Log().Error("error_type", "manage_plugins", "cause", "plugin_stopped", "error", "plugin already stopped "+pName)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, "plugin already stopped "+pName)
			return
		}
		p.StopPlugin(ctx)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `{"plugin":"%s", "action":"stopped"}`, pName)
	case "state":
		state := make(map[string]interface{}, 0)
		state["Version"] = GetConfig(ctx).Version
		state["PluginConfigs"] = p.GetSettings()
		buf, err := json.MarshalIndent(state, "", "\t")
		if err != nil {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			fmt.Fprintf(w, string(buf))
		}
	case "config":
		state := make(map[string]interface{}, 0)
		state["Version"] = GetConfig(ctx).Version
		state["PluginConfigs"] = p.GetSettings()
		buf, err := json.MarshalIndent(state, "", "\t")
		if err != nil {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			fmt.Fprintf(w, string(buf))
		}
	default:
		ctx.Log().Error("error_type", "manage_plugins", "cause", "unknown_op", "error", "unknown op "+op)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, `{"error":"%s"}`, "unknown plugin "+pName)
		return
	}
}

func GetPluginConfigList(ctx Context) PluginConfigList {
	configFile, err := os.Open(filepath.Join(filepath.Dir(ConfigPath), "plugins.json"))
	if err != nil {
		// csv-context-go may not be ready yet for logging
		fmt.Printf("{ \"error\" : \"%s\" }", err.Error())
		ctx.Log().Error("error_type", "get_plugin_config", "cause", "open_config", "error", err.Error())
		os.Exit(1)
	}
	defer configFile.Close()
	configData, err := ioutil.ReadAll(configFile)
	if err != nil {
		fmt.Printf("{ \"error\" : \"%s\" }", err.Error())
		ctx.Log().Error("error_type", "get_plugin_config", "cause", "read_config", "error", err.Error())
		os.Exit(1)
	}
	var config PluginConfigList
	err = json.Unmarshal(configData, &config)
	if err != nil {
		fmt.Printf("{ \"error\" : \"%s\" }", err.Error())
		ctx.Log().Error("error_type", "get_plugin_config", "cause", "parse_config", "error", err.Error())
		os.Exit(1)
	}
	return config
}

func LoadInboundPlugins(ctx Context) {
	// load plugin configs
	pluginConfigList = GetPluginConfigList(ctx)
	for _, e := range pluginConfigList {
		// dependency injection
		np := inboundPluginTypeMap[e.Type]
		if np == nil {
			ctx.Log().Error("error_type", "bad_plugin_config", "cause", "unknown_plugin_type", "plugin_type", e.Type)
		} else {
			inboundPluginMap[e.Name] = np(e)
		}
	}
	// launch plugins
	for k, v := range inboundPluginMap {
		if v.GetSettings().AutoStart {
			ctx.Log().Info("action", "launching_inbound_plugin", "plugin_name", k, "pugin_type", v.GetSettings().Type)
			v.StartPlugin(ctx)
		} else {
			ctx.Log().Info("action", "skipping_inactive_plugin", "plugin_name", k, "pugin_type", v.GetSettings().Type)
		}
	}
	// need sync path in inproc.go
	if GetInboundPluginByType("WEBHOOK") != nil {
		syncPath := GetInboundPluginByType("WEBHOOK").GetSettings().Parameters["EventProcPath"]
		Gctx.AddConfigValue(EelSyncPath, syncPath)
	} else {
		Gctx.AddConfigValue(EelSyncPath, "")
	}
}
<|endoftext|>


<commit_before>package bh

import (
	"io"
	"os"

	"github.com/golangplus/bytes"
	"github.com/golangplus/errors"

	"github.com/boltdb/bolt"
)

type DB struct {
	*bolt.DB
}

type Tx struct {
	*bolt.Tx
}

type Bucket struct {
	*bolt.Bucket
}

type Cursor struct {
	*bolt.Cursor
}

// Open creates and opens a database at the given path. If the file does
// not exist then it will be created automatically. Passing in nil options
// will cause Bolt to open the database with the default options.
func Open(path string, mode os.FileMode, options *bolt.Options) (DB, error) {
	db, err := bolt.Open(path, mode, options)
	return DB{db}, errorsp.WithStacks(err)
}

// Batch wraps bolt.DB.Batch.
func (db DB) Batch(f func(Tx) error) error {
	return errorsp.WithStacks(db.DB.Batch(func(tx *bolt.Tx) error {
		return errorsp.WithStacks(f(Tx{tx}))
	}))
}

// Begin wraps bolt.DB.Begin.
func (db DB) Begin(writable bool) (Tx, error) {
	tx, err := db.DB.Begin(writable)
	return Tx{tx}, errorsp.WithStacks(err)
}

// Close releases all database resources. All transactions must be closed
// before closing the database.
func (db DB) Close() error {
	return errorsp.WithStacks(db.DB.Close())
}

// Sync wraps bolt.DB.Sync.
func (db DB) Sync() error {
	return errorsp.WithStacks(db.DB.Sync())
}

// Update wraps bolt.DB.Update.
func (db DB) Update(f func(Tx) error) error {
	return errorsp.WithStacks(db.DB.Update(func(tx *bolt.Tx) error {
		return errorsp.WithStacks(f(Tx{tx}))
	}))
}

// View wraps bolt.DB.View.
func (db DB) View(f func(Tx) error) error {
	return errorsp.WithStacks(db.DB.View(func(tx *bolt.Tx) error {
		return errorsp.WithStacks(f(Tx{tx}))
	}))
}

func (tx Tx) Bucket(folders [][]byte, f func(Bucket) error) error {
	b := tx.Tx.Bucket(folders[0])
	if b == nil {
		return nil
	}
	if len(folders) == 1 {
		return errorsp.WithStacks(f(Bucket{b}))
	}
	return Bucket{b}.OpenBucket(folders[1:], f)
}

// Commit writes all changes to disk and updates the meta page. Returns an
// error if a disk write error occurs, or if Commit is called on a
// read-only transaction.
func (tx Tx) Commit() error {
	return errorsp.WithStacks(tx.Tx.Commit())
}

// CopyFile copies the entire database to file at the given path. A reader
// transaction is maintained during the copy so it is safe to continue
// using the database while a copy is in progress.
func (tx Tx) CopyFile(path string, mode os.FileMode) error {
	return errorsp.WithStacks(tx.Tx.CopyFile(path, mode))
}

// CreateBucketIfNotExists creates a new bucket if it doesn't already
// exist. Returns an error if the bucket name is blank, or if the bucket
// name is too long. The bucket instance is only valid for the lifetime of
// the transaction.
func (tx Tx) CreateBucketIfNotExists(folders [][]byte) (Bucket, error) {
	b, err := tx.Tx.CreateBucketIfNotExists(folders[0])
	if err != nil {
		return Bucket{}, nil
	}
	if len(folders) == 1 {
		return Bucket{b}, nil
	}
	return Bucket{b}.CreateBucketIfNotExists(folders[1:])
}

func (tx Tx) Cursor(folders [][]byte, f func(Cursor) error) error {
	if len(folders) == 0 {
		return errorsp.WithStacks(f(Cursor{tx.Tx.Cursor()}))
	}
	return tx.Bucket(folders, func(b Bucket) error {
		return errorsp.WithStacks(f(b.Cursor()))
	})
}

// DB returns a reference to the database that created the transaction.
func (tx Tx) DB() DB {
	return DB{tx.Tx.DB()}
}

// Delete deletes a key.
func (tx Tx) Delete(k [][]byte) error {
	if len(k) == 1 {
		return errorsp.WithStacks(tx.Tx.DeleteBucket(k[0]))
	}
	return tx.Bucket(k[:len(k)-1], func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.Delete(k[len(k)-1]))
	})
}

// ForEach iterates over all key values of a folder.
func (tx Tx) ForEach(folders [][]byte, f func(Bucket, bytesp.Slice, bytesp.Slice) error) error {
	return tx.Bucket(folders, func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.ForEach(func(k, v []byte) error {
			return errorsp.WithStacks(f(b, bytesp.Slice(k), bytesp.Slice(v)))
		}))
	})
}

// Rollback closes the transaction and ignores all previous updates.
// Read-only transactions must be rolled back and not committed.
func (tx Tx) Rollback() error {
	return errorsp.WithStacks(tx.Tx.Rollback())
}

// WriteTo writes the entire database to a writer. If err == nil then
// exactly tx.Size() bytes will be written into the writer.
func (tx *Tx) WriteTo(w io.Writer) (int64, error) {
	n, err := tx.Tx.WriteTo(w)
	return n, errorsp.WithStacks(err)
}

// Put sets the value for a key in the transaction.
// If the key exist then its previous value will be overwritten.
// Supplied value must remain valid for the life of the transaction.
// Returns an error if the bucket was created from a read-only transaction,
// if the key is blank, if the key is too large, or if the value is too
// large.
func (tx Tx) Put(k [][]byte, v []byte) error {
	b, err := tx.CreateBucketIfNotExists(k[:len(k)-1])
	if err != nil {
		return err
	}
	return errorsp.WithStacks(b.Bucket.Put(k[len(k)-1], v))
}

// Value tries to get a value from the transaction. If the key does not
// exist, the f is not called and nil is return.
func (tx Tx) Value(k [][]byte, f func(v bytesp.Slice) error) error {
	return tx.Bucket(k[:len(k)-1], func(b Bucket) error {
		v := b.Bucket.Get(k[len(k)-1])
		if v == nil {
			return nil
		}
		return errorsp.WithStacks(f(bytesp.Slice(v)))
	})
}

// Bucket retrieves a nested bucket by name. Returns nil if the bucket
// does not exist. The bucket instance is only valid for the lifetime of
// the transaction.
func (b Bucket) OpenBucket(folders [][]byte, f func(Bucket) error) error {
	bb := b.Bucket
	for _, fld := range folders {
		bb = bb.Bucket(fld)
		if bb == nil {
			return nil
		}
	}
	return errorsp.WithStacks(f(Bucket{bb}))
}

// CreateBucketIfNotExists creates a new bucket if it doesn't already
// exist and returns a reference to it. Returns an error if the bucket
// name is blank, or if the bucket name is too long. The bucket instance
// is only valid for the lifetime of the transaction.
func (b Bucket) CreateBucketIfNotExists(folders [][]byte) (Bucket, error) {
	bb := b.Bucket
	for _, fld := range folders {
		var err error
		if bb, err = bb.CreateBucketIfNotExists(fld); err != nil {
			return Bucket{}, errorsp.WithStacks(err)
		}
	}
	return Bucket{bb}, nil
}

// Cursor creates a cursor associated with the bucket. The cursor is only
// valid as long as the transaction is open. Do not use a cursor after the
// transaction is closed.
func (b Bucket) Cursor() Cursor {
	return Cursor{b.Bucket.Cursor()}
}

// Delete removes a key from the bucket. If the key does not exist then
// nothing is done and a nil error is returned. Returns an error if the
// bucket was created from a read-only transaction.
func (b Bucket) Delete(k [][]byte) error {
	return b.OpenBucket(k[:len(k)-1], func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.Delete(k[len(k)-1]))
	})
}

// ForEach executes a function for each key/value pair in a bucket. If the
// provided function returns an error then the iteration is stopped and
// the error is returned to the caller. The provided function must not
// modify the bucket; this will result in undefined behavior.
func (b Bucket) ForEach(folders [][]byte, f func(k, v bytesp.Slice) error) error {
	return b.OpenBucket(folders, func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.ForEach(func(k, v []byte) error {
			return errorsp.WithStacks(f(bytesp.Slice(k), bytesp.Slice(k)))
		}))
	})
}

// Get retrieves the value for a key in the bucket. f is not called if
// the key does not exist or if the key is a nested bucket.
func (b Bucket) Value(k [][]byte, f func(bytesp.Slice) error) {
	b.OpenBucket(k[:len(k)-1], func(b Bucket) error {
		if v := b.Bucket.Get(k[len(k)-1]); v != nil {
			f(bytesp.Slice(v))
		}
		return nil
	})
}

// NextSequence returns an autoincrementing integer for the bucket.
func (b Bucket) NextSequence() (uint64, error) {
	s, err := b.Bucket.NextSequence()
	return s, errorsp.WithStacks(err)
}

// Put sets the value for a key in the bucket. If the key exist then its
// previous value will be overwritten. Supplied value must remain valid
// for the life of the transaction. Returns an error if the bucket was
// created from a read-only transaction, if the key is blank, if the key
// is too large, or if the value is too large.
func (b Bucket) Put(k [][]byte, v []byte) error {
	return b.OpenBucket(k[:len(k)-1], func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.Put(k[len(k)-1], v))
	})
}

// Tx returns the tx of the bucket.
func (b Bucket) Tx() Tx {
	return Tx{b.Bucket.Tx()}
}

// Bucket returns the bucket that this cursor was created from.
func (c Cursor) Bucket() Bucket {
	return Bucket{c.Cursor.Bucket()}
}

// Delete removes the current key/value under the cursor from the bucket.
// Delete fails if current key/value is a bucket or if the transaction is
// not writable.
func (c Cursor) Delete() error {
	return errorsp.WithStacks(c.Cursor.Delete())
}
<commit_msg>add error<commit_after>package bh

import (
	"io"
	"os"

	"github.com/golangplus/bytes"
	"github.com/golangplus/errors"

	"github.com/boltdb/bolt"
)

type DB struct {
	*bolt.DB
}

type Tx struct {
	*bolt.Tx
}

type Bucket struct {
	*bolt.Bucket
}

type Cursor struct {
	*bolt.Cursor
}

// Open creates and opens a database at the given path. If the file does
// not exist then it will be created automatically. Passing in nil options
// will cause Bolt to open the database with the default options.
func Open(path string, mode os.FileMode, options *bolt.Options) (DB, error) {
	db, err := bolt.Open(path, mode, options)
	return DB{db}, errorsp.WithStacks(err)
}

// Batch wraps bolt.DB.Batch.
func (db DB) Batch(f func(Tx) error) error {
	return errorsp.WithStacks(db.DB.Batch(func(tx *bolt.Tx) error {
		return errorsp.WithStacks(f(Tx{tx}))
	}))
}

// Begin wraps bolt.DB.Begin.
func (db DB) Begin(writable bool) (Tx, error) {
	tx, err := db.DB.Begin(writable)
	return Tx{tx}, errorsp.WithStacks(err)
}

// Close releases all database resources. All transactions must be closed
// before closing the database.
func (db DB) Close() error {
	return errorsp.WithStacks(db.DB.Close())
}

// Sync wraps bolt.DB.Sync.
func (db DB) Sync() error {
	return errorsp.WithStacks(db.DB.Sync())
}

// Update wraps bolt.DB.Update.
func (db DB) Update(f func(Tx) error) error {
	return errorsp.WithStacks(db.DB.Update(func(tx *bolt.Tx) error {
		return errorsp.WithStacks(f(Tx{tx}))
	}))
}

// View wraps bolt.DB.View.
func (db DB) View(f func(Tx) error) error {
	return errorsp.WithStacks(db.DB.View(func(tx *bolt.Tx) error {
		return errorsp.WithStacks(f(Tx{tx}))
	}))
}

func (tx Tx) Bucket(folders [][]byte, f func(Bucket) error) error {
	b := tx.Tx.Bucket(folders[0])
	if b == nil {
		return nil
	}
	if len(folders) == 1 {
		return errorsp.WithStacks(f(Bucket{b}))
	}
	return Bucket{b}.OpenBucket(folders[1:], f)
}

// Commit writes all changes to disk and updates the meta page. Returns an
// error if a disk write error occurs, or if Commit is called on a
// read-only transaction.
func (tx Tx) Commit() error {
	return errorsp.WithStacks(tx.Tx.Commit())
}

// CopyFile copies the entire database to file at the given path. A reader
// transaction is maintained during the copy so it is safe to continue
// using the database while a copy is in progress.
func (tx Tx) CopyFile(path string, mode os.FileMode) error {
	return errorsp.WithStacks(tx.Tx.CopyFile(path, mode))
}

// CreateBucketIfNotExists creates a new bucket if it doesn't already
// exist. Returns an error if the bucket name is blank, or if the bucket
// name is too long. The bucket instance is only valid for the lifetime of
// the transaction.
func (tx Tx) CreateBucketIfNotExists(folders [][]byte) (Bucket, error) {
	b, err := tx.Tx.CreateBucketIfNotExists(folders[0])
	if err != nil {
		return Bucket{}, nil
	}
	if len(folders) == 1 {
		return Bucket{b}, nil
	}
	return Bucket{b}.CreateBucketIfNotExists(folders[1:])
}

func (tx Tx) Cursor(folders [][]byte, f func(Cursor) error) error {
	if len(folders) == 0 {
		return errorsp.WithStacks(f(Cursor{tx.Tx.Cursor()}))
	}
	return tx.Bucket(folders, func(b Bucket) error {
		return errorsp.WithStacks(f(b.Cursor()))
	})
}

// DB returns a reference to the database that created the transaction.
func (tx Tx) DB() DB {
	return DB{tx.Tx.DB()}
}

// Delete deletes a key.
func (tx Tx) Delete(k [][]byte) error {
	if len(k) == 1 {
		return errorsp.WithStacks(tx.Tx.DeleteBucket(k[0]))
	}
	return tx.Bucket(k[:len(k)-1], func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.Delete(k[len(k)-1]))
	})
}

// ForEach iterates over all key values of a folder.
func (tx Tx) ForEach(folders [][]byte, f func(Bucket, bytesp.Slice, bytesp.Slice) error) error {
	return tx.Bucket(folders, func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.ForEach(func(k, v []byte) error {
			return errorsp.WithStacks(f(b, bytesp.Slice(k), bytesp.Slice(v)))
		}))
	})
}

// Rollback closes the transaction and ignores all previous updates.
// Read-only transactions must be rolled back and not committed.
func (tx Tx) Rollback() error {
	return errorsp.WithStacks(tx.Tx.Rollback())
}

// WriteTo writes the entire database to a writer. If err == nil then
// exactly tx.Size() bytes will be written into the writer.
func (tx *Tx) WriteTo(w io.Writer) (int64, error) {
	n, err := tx.Tx.WriteTo(w)
	return n, errorsp.WithStacks(err)
}

// Put sets the value for a key in the transaction.
// If the key exist then its previous value will be overwritten.
// Supplied value must remain valid for the life of the transaction.
// Returns an error if the bucket was created from a read-only transaction,
// if the key is blank, if the key is too large, or if the value is too
// large.
func (tx Tx) Put(k [][]byte, v []byte) error {
	b, err := tx.CreateBucketIfNotExists(k[:len(k)-1])
	if err != nil {
		return err
	}
	return errorsp.WithStacks(b.Bucket.Put(k[len(k)-1], v))
}

// Value tries to get a value from the transaction. If the key does not
// exist, the f is not called and nil is return.
func (tx Tx) Value(k [][]byte, f func(v bytesp.Slice) error) error {
	return tx.Bucket(k[:len(k)-1], func(b Bucket) error {
		v := b.Bucket.Get(k[len(k)-1])
		if v == nil {
			return nil
		}
		return errorsp.WithStacks(f(bytesp.Slice(v)))
	})
}

// Bucket retrieves a nested bucket by name. Returns nil if the bucket
// does not exist. The bucket instance is only valid for the lifetime of
// the transaction.
func (b Bucket) OpenBucket(folders [][]byte, f func(Bucket) error) error {
	bb := b.Bucket
	for _, fld := range folders {
		bb = bb.Bucket(fld)
		if bb == nil {
			return nil
		}
	}
	return errorsp.WithStacks(f(Bucket{bb}))
}

// CreateBucketIfNotExists creates a new bucket if it doesn't already
// exist and returns a reference to it. Returns an error if the bucket
// name is blank, or if the bucket name is too long. The bucket instance
// is only valid for the lifetime of the transaction.
func (b Bucket) CreateBucketIfNotExists(folders [][]byte) (Bucket, error) {
	bb := b.Bucket
	for _, fld := range folders {
		var err error
		if bb, err = bb.CreateBucketIfNotExists(fld); err != nil {
			return Bucket{}, errorsp.WithStacks(err)
		}
	}
	return Bucket{bb}, nil
}

// Cursor creates a cursor associated with the bucket. The cursor is only
// valid as long as the transaction is open. Do not use a cursor after the
// transaction is closed.
func (b Bucket) Cursor() Cursor {
	return Cursor{b.Bucket.Cursor()}
}

// Delete removes a key from the bucket. If the key does not exist then
// nothing is done and a nil error is returned. Returns an error if the
// bucket was created from a read-only transaction.
func (b Bucket) Delete(k [][]byte) error {
	return b.OpenBucket(k[:len(k)-1], func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.Delete(k[len(k)-1]))
	})
}

// ForEach executes a function for each key/value pair in a bucket. If the
// provided function returns an error then the iteration is stopped and
// the error is returned to the caller. The provided function must not
// modify the bucket; this will result in undefined behavior.
func (b Bucket) ForEach(folders [][]byte, f func(k, v bytesp.Slice) error) error {
	return b.OpenBucket(folders, func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.ForEach(func(k, v []byte) error {
			return errorsp.WithStacks(f(bytesp.Slice(k), bytesp.Slice(k)))
		}))
	})
}

// Get retrieves the value for a key in the bucket. f is not called if
// the key does not exist or if the key is a nested bucket.
func (b Bucket) Value(k [][]byte, f func(bytesp.Slice) error) error {
	return b.OpenBucket(k[:len(k)-1], func(b Bucket) error {
		if v := b.Bucket.Get(k[len(k)-1]); v != nil {
			return errorsp.WithStacks(f(bytesp.Slice(v)))
		}
		return nil
	})
}

// NextSequence returns an autoincrementing integer for the bucket.
func (b Bucket) NextSequence() (uint64, error) {
	s, err := b.Bucket.NextSequence()
	return s, errorsp.WithStacks(err)
}

// Put sets the value for a key in the bucket. If the key exist then its
// previous value will be overwritten. Supplied value must remain valid
// for the life of the transaction. Returns an error if the bucket was
// created from a read-only transaction, if the key is blank, if the key
// is too large, or if the value is too large.
func (b Bucket) Put(k [][]byte, v []byte) error {
	return b.OpenBucket(k[:len(k)-1], func(b Bucket) error {
		return errorsp.WithStacks(b.Bucket.Put(k[len(k)-1], v))
	})
}

// Tx returns the tx of the bucket.
func (b Bucket) Tx() Tx {
	return Tx{b.Bucket.Tx()}
}

// Bucket returns the bucket that this cursor was created from.
func (c Cursor) Bucket() Bucket {
	return Bucket{c.Cursor.Bucket()}
}

// Delete removes the current key/value under the cursor from the bucket.
// Delete fails if current key/value is a bucket or if the transaction is
// not writable.
func (c Cursor) Delete() error {
	return errorsp.WithStacks(c.Cursor.Delete())
}
<|endoftext|>


<commit_before>package kazoo

import (
	"encoding/json"
	"errors"
	"fmt"
	"path"
	"strconv"
	"time"

	"github.com/samuel/go-zookeeper/zk"
)

var (
	FailedToClaimPartition = errors.New("Failed to claim partition for this consumer instance. Do you have a rogue consumer running?")
)

type Topic struct {
	Name string
	kz   *Kazoo
}

// Partition information
type Partition struct {
	topic    *Topic
	ID       int32
	Replicas []int32
}

// Kazoo interacts with the Kafka metadata in Zookeeper
type Kazoo struct {
	conn *zk.Conn
	conf *Config
}

type Config struct {
	Chroot  string
	Timeout time.Duration
}

func NewConfig() *Config {
	return &Config{Timeout: 1 * time.Second}
}

// NewKazoo creates a new connection instance
func NewKazoo(servers []string, conf *Config) (*Kazoo, error) {
	if conf == nil {
		conf = NewConfig()
	}

	conn, _, err := zk.Connect(servers, conf.Timeout)
	if err != nil {
		return nil, err
	}
	return &Kazoo{conn, conf}, nil
}

/*******************************************************************
 * HIGH LEVEL API
 *******************************************************************/

func (kz *Kazoo) Brokers() (map[int32]string, error) {
	root := fmt.Sprintf("%s/brokers/ids", kz.conf.Chroot)
	children, _, err := kz.conn.Children(root)
	if err != nil {
		return nil, err
	}

	type brokerEntry struct {
		Host string `json:"host"`
		Port int    `json:"port"`
	}

	result := make(map[int32]string)
	for _, child := range children {
		brokerID, err := strconv.ParseInt(child, 10, 32)
		if err != nil {
			return nil, err
		}

		value, _, err := kz.conn.Get(path.Join(root, child))
		if err != nil {
			return nil, err
		}

		var brokerNode brokerEntry
		if err := json.Unmarshal(value, &brokerNode); err != nil {
			return nil, err
		}

		result[int32(brokerID)] = fmt.Sprintf("%s:%d", brokerNode.Host, brokerNode.Port)
	}

	return result, nil
}

func (kz *Kazoo) Topics() (map[string]*Topic, error) {
	root := fmt.Sprintf("%s/brokers/topics", kz.conf.Chroot)
	children, _, err := kz.conn.Children(root)
	if err != nil {
		return nil, err
	}

	result := make(map[string]*Topic)
	for _, name := range children {
		result[name] = kz.Topic(name)
	}
	return result, nil
}

func (kz *Kazoo) Topic(topic string) *Topic {
	return &Topic{Name: topic, kz: kz}
}

func (t *Topic) Partitions() (map[int32]*Partition, error) {
	node := fmt.Sprintf("%s/brokers/topics/%s", t.kz.conf.Chroot, t.Name)
	value, _, err := t.kz.conn.Get(node)
	if err != nil {
		return nil, err
	}

	type topicMetadata struct {
		Partitions map[string][]int32 `json:"partitions"`
	}

	var tm topicMetadata
	if err := json.Unmarshal(value, &tm); err != nil {
		return nil, err
	}

	result := make(map[int32]*Partition)
	for partitionNumber, replicas := range tm.Partitions {
		partitionID, err := strconv.ParseInt(partitionNumber, 10, 32)
		if err != nil {
			return nil, err
		}

		replicaIDs := make([]int32, 0, len(replicas))
		for _, r := range replicas {
			replicaIDs = append(replicaIDs, int32(r))
		}

		result[int32(partitionID)] = t.Partition(int32(partitionID), replicaIDs)
	}

	return result, nil
}

func (t *Topic) Partition(id int32, replicas []int32) *Partition {
	return &Partition{ID: id, Replicas: replicas, topic: t}
}

func (t *Topic) Config() (map[string]string, error) {
	value, _, err := t.kz.conn.Get(fmt.Sprintf("%s/config/topics/%s", t.kz.conf.Chroot, t.Name))
	if err != nil {
		return nil, err
	}

	var topicConfig struct {
		ConfigMap map[string]string `json:"config"`
	}

	if err := json.Unmarshal(value, &topicConfig); err != nil {
		return nil, err
	}

	return topicConfig.ConfigMap, nil
}

type partitionState struct {
	Leader int32   `json:"leader"`
	ISR    []int32 `json:"isr"`
}

func (p *Partition) state() (partitionState, error) {
	var state partitionState
	node := fmt.Sprintf("%s/brokers/topics/%s/partitions/%d/state", p.topic.kz.conf.Chroot, p.topic.Name, p.ID)
	value, _, err := p.topic.kz.conn.Get(node)
	if err != nil {
		return state, err
	}

	if err := json.Unmarshal(value, &state); err != nil {
		return state, err
	}

	return state, nil
}

func (p *Partition) Leader() (int32, error) {
	if state, err := p.state(); err != nil {
		return -1, err
	} else {
		return state.Leader, nil
	}
}

func (p *Partition) ISR() ([]int32, error) {
	if state, err := p.state(); err != nil {
		return nil, err
	} else {
		return state.ISR, nil
	}
}

// Consumers returns all active consumers within a group
func (kz *Kazoo) Consumers(group string) ([]string, <-chan zk.Event, error) {
	root := fmt.Sprintf("%s/consumers/%s/ids", kz.conf.Chroot, group)
	err := kz.mkdirRecursive(root)
	if err != nil {
		return nil, nil, err
	}

	strs, _, ch, err := kz.conn.ChildrenW(root)
	if err != nil {
		return nil, nil, err
	}
	return strs, ch, nil
}

// Claim claims a topic/partition ownership for a consumer ID within a group
func (kz *Kazoo) ClaimPartition(group, topic string, partition int32, id string) (err error) {
	root := fmt.Sprintf("%s/consumers/%s/owners/%s", kz.conf.Chroot, group, topic)
	if err = kz.mkdirRecursive(root); err != nil {
		return err
	}

	node := fmt.Sprintf("%s/%d", root, partition)
	tries := 0
	for {
		tries++
		if err = kz.create(node, []byte(id), true); err == nil {
			break
		} else if err != zk.ErrNodeExists {
			return err
		} else if tries > 100 {
			return FailedToClaimPartition
		}
		time.Sleep(200 * time.Millisecond)
	}
	return nil
}

// Release releases a claim
func (kz *Kazoo) ReleasePartition(group, topic string, partition int32, id string) error {
	node := fmt.Sprintf("%s/consumers/%s/owners/%s/%d", kz.conf.Chroot, group, topic, partition)
	val, _, err := kz.conn.Get(node)

	// Already deleted
	if err == zk.ErrNoNode {
		return nil
	}

	// Locked by someone else?
	if string(val) != id {
		return zk.ErrNotLocked
	}

	return kz.deleteRecursive(node)
}

// CommitOffset commits an offset to a group/topic/partition
func (kz *Kazoo) CommitOffset(group, topic string, partition int32, offset int64) (err error) {
	root := fmt.Sprintf("%s/consumers/%s/offsets/%s", kz.conf.Chroot, group, topic)
	if err = kz.mkdirRecursive(root); err != nil {
		return err
	}

	node := fmt.Sprintf("%s/%d", root, partition)
	data := []byte(fmt.Sprintf("%d", offset))
	_, stat, err := kz.conn.Get(node)

	// Try to create new node
	if err == zk.ErrNoNode {
		return kz.create(node, data, false)
	} else if err != nil {
		return err
	}

	_, err = kz.conn.Set(node, data, stat.Version)
	return
}

// FetchOffset retrieves an offset to a group/topic/partition
func (kz *Kazoo) FetchOffset(group, topic string, partition int32) (int64, error) {
	node := fmt.Sprintf("%s/consumers/%s/offsets/%s/%d", kz.conf.Chroot, group, topic, partition)
	val, _, err := kz.conn.Get(node)
	if err == zk.ErrNoNode {
		return 0, nil
	} else if err != nil {
		return -1, err
	}
	return strconv.ParseInt(string(val), 10, 64)
}

// RegisterGroup creates/updates a group directory
func (kz *Kazoo) RegisterGroup(group string) error {
	return kz.mkdirRecursive(fmt.Sprintf("%s/consumers/%s/ids", kz.conf.Chroot, group))
}

// CreateConsumer registers a new consumer within a group
func (kz *Kazoo) RegisterConsumer(group, id string, topics []string) error {
	subscription := make(map[string]int)
	for _, topic := range topics {
		subscription[topic] = 1
	}

	data, err := json.Marshal(map[string]interface{}{
		"pattern":      "white_list",
		"subscription": subscription,
		"timestamp":    fmt.Sprintf("%d", time.Now().Unix()),
		"version":      1,
	})
	if err != nil {
		return err
	}

	return kz.create(fmt.Sprintf("%s/consumers/%s/ids/%s", kz.conf.Chroot, group, id), data, true)
}

func (kz *Kazoo) DeregisterConsumer(group, id string) error {
	return kz.conn.Delete(fmt.Sprintf("%s/consumers/%s/ids/%s", kz.conf.Chroot, group, id), 0)
}

func (kz *Kazoo) Close() error {
	kz.conn.Close()
	return nil
}

/*******************************************************************
 * LOW LEVEL API
 *******************************************************************/

// Exists checks existence of a node
func (kz *Kazoo) exists(node string) (ok bool, err error) {
	ok, _, err = kz.conn.Exists(node)
	return
}

// DeleteAll deletes a node recursively
func (kz *Kazoo) deleteRecursive(node string) (err error) {
	children, stat, err := kz.conn.Children(node)
	if err == zk.ErrNoNode {
		return nil
	} else if err != nil {
		return
	}

	for _, child := range children {
		if err = kz.deleteRecursive(path.Join(node, child)); err != nil {
			return
		}
	}

	return kz.conn.Delete(node, stat.Version)
}

// MkdirAll creates a directory recursively
func (kz *Kazoo) mkdirRecursive(node string) (err error) {
	parent := path.Dir(node)
	if parent != "/" {
		if err = kz.mkdirRecursive(parent); err != nil {
			return
		}
	}

	_, err = kz.conn.Create(node, nil, 0, zk.WorldACL(zk.PermAll))
	if err == zk.ErrNodeExists {
		err = nil
	}
	return
}

// Create stores a new value at node. Fails if already set.
func (kz *Kazoo) create(node string, value []byte, ephemeral bool) (err error) {
	if err = kz.mkdirRecursive(path.Dir(node)); err != nil {
		return
	}

	flags := int32(0)
	if ephemeral {
		flags = zk.FlagEphemeral
	}
	_, err = kz.conn.Create(node, value, flags, zk.WorldACL(zk.PermAll))
	return
}
<commit_msg>Made CommitOffset more efficient by removing parent path creation, instead making it purely an update operation.  The 'make parent' logic is already being handled in Kazoo.create().<commit_after>package kazoo

import (
	"encoding/json"
	"errors"
	"fmt"
	"path"
	"strconv"
	"time"

	"github.com/samuel/go-zookeeper/zk"
)

var (
	FailedToClaimPartition = errors.New("Failed to claim partition for this consumer instance. Do you have a rogue consumer running?")
)

type Topic struct {
	Name string
	kz   *Kazoo
}

// Partition information
type Partition struct {
	topic    *Topic
	ID       int32
	Replicas []int32
}

// Kazoo interacts with the Kafka metadata in Zookeeper
type Kazoo struct {
	conn *zk.Conn
	conf *Config
}

type Config struct {
	Chroot  string
	Timeout time.Duration
}

func NewConfig() *Config {
	return &Config{Timeout: 1 * time.Second}
}

// NewKazoo creates a new connection instance
func NewKazoo(servers []string, conf *Config) (*Kazoo, error) {
	if conf == nil {
		conf = NewConfig()
	}

	conn, _, err := zk.Connect(servers, conf.Timeout)
	if err != nil {
		return nil, err
	}
	return &Kazoo{conn, conf}, nil
}

/*******************************************************************
 * HIGH LEVEL API
 *******************************************************************/

func (kz *Kazoo) Brokers() (map[int32]string, error) {
	root := fmt.Sprintf("%s/brokers/ids", kz.conf.Chroot)
	children, _, err := kz.conn.Children(root)
	if err != nil {
		return nil, err
	}

	type brokerEntry struct {
		Host string `json:"host"`
		Port int    `json:"port"`
	}

	result := make(map[int32]string)
	for _, child := range children {
		brokerID, err := strconv.ParseInt(child, 10, 32)
		if err != nil {
			return nil, err
		}

		value, _, err := kz.conn.Get(path.Join(root, child))
		if err != nil {
			return nil, err
		}

		var brokerNode brokerEntry
		if err := json.Unmarshal(value, &brokerNode); err != nil {
			return nil, err
		}

		result[int32(brokerID)] = fmt.Sprintf("%s:%d", brokerNode.Host, brokerNode.Port)
	}

	return result, nil
}

func (kz *Kazoo) Topics() (map[string]*Topic, error) {
	root := fmt.Sprintf("%s/brokers/topics", kz.conf.Chroot)
	children, _, err := kz.conn.Children(root)
	if err != nil {
		return nil, err
	}

	result := make(map[string]*Topic)
	for _, name := range children {
		result[name] = kz.Topic(name)
	}
	return result, nil
}

func (kz *Kazoo) Topic(topic string) *Topic {
	return &Topic{Name: topic, kz: kz}
}

func (t *Topic) Partitions() (map[int32]*Partition, error) {
	node := fmt.Sprintf("%s/brokers/topics/%s", t.kz.conf.Chroot, t.Name)
	value, _, err := t.kz.conn.Get(node)
	if err != nil {
		return nil, err
	}

	type topicMetadata struct {
		Partitions map[string][]int32 `json:"partitions"`
	}

	var tm topicMetadata
	if err := json.Unmarshal(value, &tm); err != nil {
		return nil, err
	}

	result := make(map[int32]*Partition)
	for partitionNumber, replicas := range tm.Partitions {
		partitionID, err := strconv.ParseInt(partitionNumber, 10, 32)
		if err != nil {
			return nil, err
		}

		replicaIDs := make([]int32, 0, len(replicas))
		for _, r := range replicas {
			replicaIDs = append(replicaIDs, int32(r))
		}

		result[int32(partitionID)] = t.Partition(int32(partitionID), replicaIDs)
	}

	return result, nil
}

func (t *Topic) Partition(id int32, replicas []int32) *Partition {
	return &Partition{ID: id, Replicas: replicas, topic: t}
}

func (t *Topic) Config() (map[string]string, error) {
	value, _, err := t.kz.conn.Get(fmt.Sprintf("%s/config/topics/%s", t.kz.conf.Chroot, t.Name))
	if err != nil {
		return nil, err
	}

	var topicConfig struct {
		ConfigMap map[string]string `json:"config"`
	}

	if err := json.Unmarshal(value, &topicConfig); err != nil {
		return nil, err
	}

	return topicConfig.ConfigMap, nil
}

type partitionState struct {
	Leader int32   `json:"leader"`
	ISR    []int32 `json:"isr"`
}

func (p *Partition) state() (partitionState, error) {
	var state partitionState
	node := fmt.Sprintf("%s/brokers/topics/%s/partitions/%d/state", p.topic.kz.conf.Chroot, p.topic.Name, p.ID)
	value, _, err := p.topic.kz.conn.Get(node)
	if err != nil {
		return state, err
	}

	if err := json.Unmarshal(value, &state); err != nil {
		return state, err
	}

	return state, nil
}

func (p *Partition) Leader() (int32, error) {
	if state, err := p.state(); err != nil {
		return -1, err
	} else {
		return state.Leader, nil
	}
}

func (p *Partition) ISR() ([]int32, error) {
	if state, err := p.state(); err != nil {
		return nil, err
	} else {
		return state.ISR, nil
	}
}

// Consumers returns all active consumers within a group
func (kz *Kazoo) Consumers(group string) ([]string, <-chan zk.Event, error) {
	root := fmt.Sprintf("%s/consumers/%s/ids", kz.conf.Chroot, group)
	err := kz.mkdirRecursive(root)
	if err != nil {
		return nil, nil, err
	}

	strs, _, ch, err := kz.conn.ChildrenW(root)
	if err != nil {
		return nil, nil, err
	}
	return strs, ch, nil
}

// Claim claims a topic/partition ownership for a consumer ID within a group
func (kz *Kazoo) ClaimPartition(group, topic string, partition int32, id string) (err error) {
	root := fmt.Sprintf("%s/consumers/%s/owners/%s", kz.conf.Chroot, group, topic)
	if err = kz.mkdirRecursive(root); err != nil {
		return err
	}

	node := fmt.Sprintf("%s/%d", root, partition)
	tries := 0
	for {
		tries++
		if err = kz.create(node, []byte(id), true); err == nil {
			break
		} else if err != zk.ErrNodeExists {
			return err
		} else if tries > 100 {
			return FailedToClaimPartition
		}
		time.Sleep(200 * time.Millisecond)
	}
	return nil
}

// Release releases a claim
func (kz *Kazoo) ReleasePartition(group, topic string, partition int32, id string) error {
	node := fmt.Sprintf("%s/consumers/%s/owners/%s/%d", kz.conf.Chroot, group, topic, partition)
	val, _, err := kz.conn.Get(node)

	// Already deleted
	if err == zk.ErrNoNode {
		return nil
	}

	// Locked by someone else?
	if string(val) != id {
		return zk.ErrNotLocked
	}

	return kz.deleteRecursive(node)
}

// CommitOffset commits an offset to a group/topic/partition
func (kz *Kazoo) CommitOffset(group, topic string, partition int32, offset int64) error {
	node := fmt.Sprintf("%s/consumers/%s/offsets/%s/%d", kz.conf.Chroot, group, topic, partition)
	data := []byte(fmt.Sprintf("%d", offset))

	// get current info for node
	_, stat, err := kz.conn.Get(node)
	if err != nil {
		if err == zk.ErrNoNode {
			// Node doesn't exist; try to create it
			return kz.create(node, data, false)
		}
		return err
	}

	// update the existing node
	_, err = kz.conn.Set(node, data, stat.Version)
	if err != nil {
		return err
	}
	return nil
}

// FetchOffset retrieves an offset to a group/topic/partition
func (kz *Kazoo) FetchOffset(group, topic string, partition int32) (int64, error) {
	node := fmt.Sprintf("%s/consumers/%s/offsets/%s/%d", kz.conf.Chroot, group, topic, partition)
	val, _, err := kz.conn.Get(node)
	if err == zk.ErrNoNode {
		return 0, nil
	} else if err != nil {
		return -1, err
	}
	return strconv.ParseInt(string(val), 10, 64)
}

// RegisterGroup creates/updates a group directory
func (kz *Kazoo) RegisterGroup(group string) error {
	return kz.mkdirRecursive(fmt.Sprintf("%s/consumers/%s/ids", kz.conf.Chroot, group))
}

// CreateConsumer registers a new consumer within a group
func (kz *Kazoo) RegisterConsumer(group, id string, topics []string) error {
	subscription := make(map[string]int)
	for _, topic := range topics {
		subscription[topic] = 1
	}

	data, err := json.Marshal(map[string]interface{}{
		"pattern":      "white_list",
		"subscription": subscription,
		"timestamp":    fmt.Sprintf("%d", time.Now().Unix()),
		"version":      1,
	})
	if err != nil {
		return err
	}

	return kz.create(fmt.Sprintf("%s/consumers/%s/ids/%s", kz.conf.Chroot, group, id), data, true)
}

func (kz *Kazoo) DeregisterConsumer(group, id string) error {
	return kz.conn.Delete(fmt.Sprintf("%s/consumers/%s/ids/%s", kz.conf.Chroot, group, id), 0)
}

func (kz *Kazoo) Close() error {
	kz.conn.Close()
	return nil
}

/*******************************************************************
 * LOW LEVEL API
 *******************************************************************/

// Exists checks existence of a node
func (kz *Kazoo) exists(node string) (ok bool, err error) {
	ok, _, err = kz.conn.Exists(node)
	return
}

// DeleteAll deletes a node recursively
func (kz *Kazoo) deleteRecursive(node string) (err error) {
	children, stat, err := kz.conn.Children(node)
	if err == zk.ErrNoNode {
		return nil
	} else if err != nil {
		return
	}

	for _, child := range children {
		if err = kz.deleteRecursive(path.Join(node, child)); err != nil {
			return
		}
	}

	return kz.conn.Delete(node, stat.Version)
}

// MkdirAll creates a directory recursively
func (kz *Kazoo) mkdirRecursive(node string) (err error) {
	parent := path.Dir(node)
	if parent != "/" {
		if err = kz.mkdirRecursive(parent); err != nil {
			return
		}
	}

	_, err = kz.conn.Create(node, nil, 0, zk.WorldACL(zk.PermAll))
	if err == zk.ErrNodeExists {
		err = nil
	}
	return
}

// Create stores a new value at node. Fails if already set.
func (kz *Kazoo) create(node string, value []byte, ephemeral bool) (err error) {
	if err = kz.mkdirRecursive(path.Dir(node)); err != nil {
		return
	}

	flags := int32(0)
	if ephemeral {
		flags = zk.FlagEphemeral
	}
	_, err = kz.conn.Create(node, value, flags, zk.WorldACL(zk.PermAll))
	return
}
<|endoftext|>


<commit_before>package main

const (
	StarHyperGiant = iota
	StarSuperGiant
	StarBrightGiant
	StarGiant
	StarSubGiant
	StarDwarf
	StarSubDwarf
	StarWhiteDwarf
	StarRedDwarf
	StarBrownDwarf
)

func main() {
	println(StarHyperGiant,
		StarSuperGiant,
		StarBrightGiant,
		StarGiant,
		StarSubGiant,
	)
}
<commit_msg>const enum 2<commit_after>package main

const (
	StarHyperGiant = 2.0 * iota
	StarSuperGiant
	StarBrightGiant
	StarGiant
	StarSubGiant
	StarDwarf
	StarSubDwarf
	StarWhiteDwarf
	StarRedDwarf
	StarBrownDwarf
)

func main() {
	println(StarHyperGiant,
		StarSuperGiant,
		StarBrightGiant,
		StarGiant,
		StarSubGiant,
	)
}
<|endoftext|>


<commit_before>package analytics

import (
	"net/http"

	"github.com/getlantern/flashlight/config"
	"github.com/mitchellh/mapstructure"

	"github.com/getlantern/analytics"
	"github.com/getlantern/flashlight/ui"
	"github.com/getlantern/golog"
)

const (
	messageType = `Analytics`
)

var (
	log        = golog.LoggerFor("flashlight.analytics")
	service    *ui.Service
	httpClient *http.Client
	hostName   *string
	stopCh     chan bool
)

func Configure(cfg *config.Config, serverSession bool, newClient *http.Client) {

	httpClient = newClient

	sessionPayload := &analytics.Payload{
		ClientId:      cfg.InstanceId,
		ClientVersion: string(cfg.Version),
		HitType:       analytics.EventType,
		Event: &analytics.Event{
			Category: "Session",
			Action:   "Start",
		},
	}

	if serverSession {
		sessionPayload.Hostname = cfg.Server.AdvertisedHost
	} else {
		sessionPayload.Hostname = "localhost"
	}

	analytics.SessionEvent(httpClient, sessionPayload)

	if !serverSession && cfg.AutoReport != nil && *cfg.AutoReport {
		err := StartService()
		if err != nil {
			log.Errorf("Error starting analytics service: %q", err)
		}
	}
}

// Used with clients to track user interaction with the UI
func StartService() error {

	var err error

	if service != nil {
		return nil
	}

	newMessage := func() interface{} {
		return &analytics.Payload{}
	}

	if service, err = ui.Register(messageType, newMessage, nil); err != nil {
		log.Errorf("Unable to register analytics service: %q", err)
		return err
	}

	stopCh = make(chan bool)

	// process analytics messages
	go read()

	return err
}

func StopService() {
	if service != nil && stopCh != nil {
		ui.Unregister(messageType)
		stopCh <- true
		service = nil
		log.Debug("Successfully stopped analytics service")
	}
}

func read() {

	for {
		select {
		case <-stopCh:
			return
		case msg := <-service.In:
			log.Debugf("New UI analytics message: %q", msg)
			var payload analytics.Payload
			if err := mapstructure.Decode(msg, &payload); err != nil {
				log.Errorf("Could not decode payload: %q", err)
			} else {
				// set to localhost on clients
				payload.Hostname = "localhost"
				payload.HitType = analytics.PageViewType
				// for now, the only analytics messages we are
				// currently receiving from the UI are initial page
				// views which indicate new UI sessions
				analytics.UIEvent(httpClient, &payload)
			}
		}
	}
}
<commit_msg>add check for existing analytics service<commit_after>package analytics

import (
	"net/http"

	"github.com/getlantern/flashlight/config"
	"github.com/mitchellh/mapstructure"

	"github.com/getlantern/analytics"
	"github.com/getlantern/flashlight/ui"
	"github.com/getlantern/golog"
)

const (
	messageType = `Analytics`
)

var (
	log        = golog.LoggerFor("flashlight.analytics")
	service    *ui.Service
	httpClient *http.Client
	hostName   *string
	stopCh     chan bool
)

func Configure(cfg *config.Config, serverSession bool, newClient *http.Client) {

	if service != nil {
		return
	}

	httpClient = newClient

	sessionPayload := &analytics.Payload{
		ClientId:      cfg.InstanceId,
		ClientVersion: string(cfg.Version),
		HitType:       analytics.EventType,
		Event: &analytics.Event{
			Category: "Session",
			Action:   "Start",
		},
	}

	if serverSession {
		sessionPayload.Hostname = cfg.Server.AdvertisedHost
	} else {
		sessionPayload.Hostname = "localhost"
	}

	analytics.SessionEvent(httpClient, sessionPayload)

	if !serverSession && cfg.AutoReport != nil && *cfg.AutoReport {
		err := StartService()
		if err != nil {
			log.Errorf("Error starting analytics service: %q", err)
		}
	}
}

// Used with clients to track user interaction with the UI
func StartService() error {

	var err error

	if service != nil {
		return nil
	}

	newMessage := func() interface{} {
		return &analytics.Payload{}
	}

	if service, err = ui.Register(messageType, newMessage, nil); err != nil {
		log.Errorf("Unable to register analytics service: %q", err)
		return err
	}

	stopCh = make(chan bool)

	// process analytics messages
	go read()

	return err
}

func StopService() {
	if service != nil && stopCh != nil {
		ui.Unregister(messageType)
		stopCh <- true
		service = nil
		log.Debug("Successfully stopped analytics service")
	}
}

func read() {

	for {
		select {
		case <-stopCh:
			return
		case msg := <-service.In:
			log.Debugf("New UI analytics message: %q", msg)
			var payload analytics.Payload
			if err := mapstructure.Decode(msg, &payload); err != nil {
				log.Errorf("Could not decode payload: %q", err)
			} else {
				// set to localhost on clients
				payload.Hostname = "localhost"
				payload.HitType = analytics.PageViewType
				// for now, the only analytics messages we are
				// currently receiving from the UI are initial page
				// views which indicate new UI sessions
				analytics.UIEvent(httpClient, &payload)
			}
		}
	}
}
<|endoftext|>


<commit_before>// Package gotenv provides functionality to dynamically load the environment variables
package gotenv

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
)

const (
	// Pattern for detecting valid line format
	linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`

	// Pattern for detecting valid variable within a value
	variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
)

// Env holds key/value pair of valid environment variable
type Env map[string]string

/*
Load is function to load a file or multiple files and then export the valid variables into environment variables if they are not exists.
When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
Otherwise, it will loop over the filenames parameter and set the proper environment variables.
*/
func Load(filenames ...string) error {
	return loadenv(false, filenames...)
}

/*
OverLoad is function to load a file or multiple files and then export and override the valid variables into environment variables.
*/
func OverLoad(filenames ...string) error {
	return loadenv(true, filenames...)
}

/*
Must is wrapper function that will panic when supplied function returns error
*/
func Must(fn func(filenames ...string) error, filenames ...string) {
	if err := fn(filenames...); err != nil {
		panic(err.Error())
	}
}

/*
Apply is function to load an io Reader then export the valid variables into environment variables if they are not exist.
*/
func Apply(r io.Reader) error {
	return parset(r, false)
}

/*
OverApply is function to load an io Reader then export and override the valid variables into environment variables.
*/
func OverApply(r io.Reader) error {
	return parset(r, true)
}

func loadenv(override bool, filenames ...string) error {
	if len(filenames) == 0 {
		filenames = []string{".env"}
	}

	for _, filename := range filenames {
		f, err := os.Open(filename)
		if err != nil {
			return err
		}
		defer f.Close()
		err = parset(f, override)
		if err != nil {
			return err
		}
	}

	return nil
}

// parse and set :)
func parset(r io.Reader, override bool) error {
	env, err := StrictParse(r)
	if err != nil {
		return err
	}

	for key, val := range env {
		setenv(key, val, override)
	}

	return nil
}

func setenv(key, val string, override bool) {
	if override {
		os.Setenv(key, val)
	} else {
		if _, present := os.LookupEnv(key); !present {
			os.Setenv(key, val)
		}
	}
}

// Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
// It expands the value of a variable from environment variable, but does not set the value to the environment itself.
// This function is skipping any invalid lines and only processing the valid one.
func Parse(r io.Reader) Env {
	env, _ := StrictParse(r)
	return env
}

// StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
// It expands the value of a variable from environment variable, but does not set the value to the environment itself.
// This function is returning an error if there is any invalid lines.
func StrictParse(r io.Reader) (Env, error) {
	env := make(Env)
	scanner := bufio.NewScanner(r)

	i := 1
	bom := string([]byte{239, 187, 191})

	for scanner.Scan() {
		line := scanner.Text()

		if i == 1 {
			line = strings.TrimPrefix(line, bom)
		}

		i++

		err := parseLine(line, env)
		if err != nil {
			return env, err
		}
	}

	return env, nil
}

func parseLine(s string, env Env) error {
	rl := regexp.MustCompile(linePattern)
	rm := rl.FindStringSubmatch(s)

	if len(rm) == 0 {
		return checkFormat(s, env)
	}

	key := rm[1]
	val := rm[2]

	// determine if string has quote prefix
	hdq := strings.HasPrefix(val, `"`)

	// determine if string has single quote prefix
	hsq := strings.HasPrefix(val, `'`)

	// trim whitespace
	val = strings.Trim(val, " ")

	// remove quotes '' or ""
	rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
	val = rq.ReplaceAllString(val, "$2")

	if hdq {
		val = strings.Replace(val, `\n`, "\n", -1)
		val = strings.Replace(val, `\r`, "\r", -1)

		// Unescape all characters except $ so variables can be escaped properly
		re := regexp.MustCompile(`\\([^$])`)
		val = re.ReplaceAllString(val, "$1")
	}

	rv := regexp.MustCompile(variablePattern)
	fv := func(s string) string {
		return varReplacement(s, hsq, env)
	}

	val = rv.ReplaceAllStringFunc(val, fv)
	val = parseVal(val, env)

	env[key] = val
	return nil
}

func parseExport(st string, env Env) error {
	if strings.HasPrefix(st, "export") {
		vs := strings.SplitN(st, " ", 2)

		if len(vs) > 1 {
			if _, ok := env[vs[1]]; !ok {
				return fmt.Errorf("line `%s` has an unset variable", st)
			}
		}
	}

	return nil
}

func varReplacement(s string, hsq bool, env Env) string {
	if strings.HasPrefix(s, "\\") {
		return strings.TrimPrefix(s, "\\")
	}

	if hsq {
		return s
	}

	sn := `(\$)(\{?([A-Z0-9_]+)\}?)`
	rn := regexp.MustCompile(sn)
	mn := rn.FindStringSubmatch(s)

	if len(mn) == 0 {
		return s
	}

	v := mn[3]

	replace, ok := env[v]
	if !ok {
		replace = os.Getenv(v)
	}

	return replace
}

func checkFormat(s string, env Env) error {
	st := strings.TrimSpace(s)

	if (st == "") || strings.HasPrefix(st, "#") {
		return nil
	}

	if err := parseExport(st, env); err != nil {
		return err
	}

	return fmt.Errorf("line `%s` doesn't match format", s)
}

func parseVal(val string, env Env) string {
	if strings.Contains(val, "=") {
		if !(val == "\n" || val == "\r") {
			kv := strings.Split(val, "\n")

			if len(kv) == 1 {
				kv = strings.Split(val, "\r")
			}

			if len(kv) > 1 {
				val = kv[0]

				for i := 1; i < len(kv); i++ {
					parseLine(kv[i], env)
				}
			}
		}
	}

	return val
}
<commit_msg>removed defer<commit_after>// Package gotenv provides functionality to dynamically load the environment variables
package gotenv

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
)

const (
	// Pattern for detecting valid line format
	linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`

	// Pattern for detecting valid variable within a value
	variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
)

// Env holds key/value pair of valid environment variable
type Env map[string]string

/*
Load is function to load a file or multiple files and then export the valid variables into environment variables if they are not exists.
When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
Otherwise, it will loop over the filenames parameter and set the proper environment variables.
*/
func Load(filenames ...string) error {
	return loadenv(false, filenames...)
}

/*
OverLoad is function to load a file or multiple files and then export and override the valid variables into environment variables.
*/
func OverLoad(filenames ...string) error {
	return loadenv(true, filenames...)
}

/*
Must is wrapper function that will panic when supplied function returns error
*/
func Must(fn func(filenames ...string) error, filenames ...string) {
	if err := fn(filenames...); err != nil {
		panic(err.Error())
	}
}

/*
Apply is function to load an io Reader then export the valid variables into environment variables if they are not exist.
*/
func Apply(r io.Reader) error {
	return parset(r, false)
}

/*
OverApply is function to load an io Reader then export and override the valid variables into environment variables.
*/
func OverApply(r io.Reader) error {
	return parset(r, true)
}

func loadenv(override bool, filenames ...string) error {
	if len(filenames) == 0 {
		filenames = []string{".env"}
	}

	for _, filename := range filenames {
		f, err := os.Open(filename)
		if err != nil {
			return err
		}

		err = parset(f, override)
		if err != nil {
			return err
		}

		f.Close()
	}

	return nil
}

// parse and set :)
func parset(r io.Reader, override bool) error {
	env, err := StrictParse(r)
	if err != nil {
		return err
	}

	for key, val := range env {
		setenv(key, val, override)
	}

	return nil
}

func setenv(key, val string, override bool) {
	if override {
		os.Setenv(key, val)
	} else {
		if _, present := os.LookupEnv(key); !present {
			os.Setenv(key, val)
		}
	}
}

// Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
// It expands the value of a variable from environment variable, but does not set the value to the environment itself.
// This function is skipping any invalid lines and only processing the valid one.
func Parse(r io.Reader) Env {
	env, _ := StrictParse(r)
	return env
}

// StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
// It expands the value of a variable from environment variable, but does not set the value to the environment itself.
// This function is returning an error if there is any invalid lines.
func StrictParse(r io.Reader) (Env, error) {
	env := make(Env)
	scanner := bufio.NewScanner(r)

	i := 1
	bom := string([]byte{239, 187, 191})

	for scanner.Scan() {
		line := scanner.Text()

		if i == 1 {
			line = strings.TrimPrefix(line, bom)
		}

		i++

		err := parseLine(line, env)
		if err != nil {
			return env, err
		}
	}

	return env, nil
}

func parseLine(s string, env Env) error {
	rl := regexp.MustCompile(linePattern)
	rm := rl.FindStringSubmatch(s)

	if len(rm) == 0 {
		return checkFormat(s, env)
	}

	key := rm[1]
	val := rm[2]

	// determine if string has quote prefix
	hdq := strings.HasPrefix(val, `"`)

	// determine if string has single quote prefix
	hsq := strings.HasPrefix(val, `'`)

	// trim whitespace
	val = strings.Trim(val, " ")

	// remove quotes '' or ""
	rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
	val = rq.ReplaceAllString(val, "$2")

	if hdq {
		val = strings.Replace(val, `\n`, "\n", -1)
		val = strings.Replace(val, `\r`, "\r", -1)

		// Unescape all characters except $ so variables can be escaped properly
		re := regexp.MustCompile(`\\([^$])`)
		val = re.ReplaceAllString(val, "$1")
	}

	rv := regexp.MustCompile(variablePattern)
	fv := func(s string) string {
		return varReplacement(s, hsq, env)
	}

	val = rv.ReplaceAllStringFunc(val, fv)
	val = parseVal(val, env)

	env[key] = val
	return nil
}

func parseExport(st string, env Env) error {
	if strings.HasPrefix(st, "export") {
		vs := strings.SplitN(st, " ", 2)

		if len(vs) > 1 {
			if _, ok := env[vs[1]]; !ok {
				return fmt.Errorf("line `%s` has an unset variable", st)
			}
		}
	}

	return nil
}

func varReplacement(s string, hsq bool, env Env) string {
	if strings.HasPrefix(s, "\\") {
		return strings.TrimPrefix(s, "\\")
	}

	if hsq {
		return s
	}

	sn := `(\$)(\{?([A-Z0-9_]+)\}?)`
	rn := regexp.MustCompile(sn)
	mn := rn.FindStringSubmatch(s)

	if len(mn) == 0 {
		return s
	}

	v := mn[3]

	replace, ok := env[v]
	if !ok {
		replace = os.Getenv(v)
	}

	return replace
}

func checkFormat(s string, env Env) error {
	st := strings.TrimSpace(s)

	if (st == "") || strings.HasPrefix(st, "#") {
		return nil
	}

	if err := parseExport(st, env); err != nil {
		return err
	}

	return fmt.Errorf("line `%s` doesn't match format", s)
}

func parseVal(val string, env Env) string {
	if strings.Contains(val, "=") {
		if !(val == "\n" || val == "\r") {
			kv := strings.Split(val, "\n")

			if len(kv) == 1 {
				kv = strings.Split(val, "\r")
			}

			if len(kv) > 1 {
				val = kv[0]

				for i := 1; i < len(kv); i++ {
					parseLine(kv[i], env)
				}
			}
		}
	}

	return val
}
<|endoftext|>


<commit_before>package main

import  (
        "fmt"
        "os"
        "../go-todotxt"
        "github.com/spf13/cobra"
        "os/user"
        "strings"
        "strconv"
        "github.com/rakyll/globalconf"
        "flag"
)

func extendedLoader(filename string) (todotxt.TaskList, error) {
        usr, err := user.Current()
        if err != nil {
                return nil, err
        }

        filename = strings.Replace(filename, "~", usr.HomeDir, -1)
        tasks := todotxt.LoadTaskList(filename)

        return tasks, nil
}

func main() {

        conf, _ := globalconf.New("gotodo")

        var numtasks bool
        var sortby string
        var finished bool
        var prettyformat string
        var filename string

        var flagFilename = flag.String("file", "", "Location of the todo.txt file.")

        var flags = make(map[string]string)

        var cmdConfig = &cobra.Command{
            Use:   "config [key] [value]",
            Short: "Show and sets config values",
            Long:  `Config can be used to see and also set configuration variables.`,
            Run: func(cmd *cobra.Command, args []string) {
                    if len(args) == 0{
                            fmt.Printf("Available config variables:\n\n")
                            for k := range flags {
                                    fmt.Printf("%s\n", k)
                            }
                    }
                    if len(args) == 1 {
                            val, exists := flags[args[0]]
                            if exists {
                                    fmt.Printf("%s\n", val)
                            } else {
                                    // otherwise exit with non-zero status
                                    os.Exit(1)
                            }
                    }
            },
        }

        var cmdList = &cobra.Command{
            Use:   "list [keyword]",
            Short: "Lists tasks that contain keyword, if any",
            Long:  `List is the most basic command that is used for listing tasks.
                    You can specify a keyword as well as other options.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if numtasks {
                    fmt.Println(tasks.Len())
                } else {
                    tasks.Sort(sortby)

                    var filteredTasks todotxt.TaskList
                    for _, task := range tasks {
                        if (!task.Finished() && !finished) ||
                           (task.Finished() && finished) {
                           filteredTasks = append(filteredTasks, task)
                        }
                    }

                    for _, task := range filteredTasks {
                            task.SetIdPaddingBy(tasks)
                            fmt.Println(task.PrettyPrint(prettyformat))
                    }
                }
            },
        }
        cmdList.Flags().BoolVarP(&numtasks, "num-tasks", "n", false,
                                 "Show the number of tasks")
        cmdList.Flags().BoolVarP(&finished, "finished", "f", false,
                                 "Show finished tasks")
        cmdList.Flags().StringVarP(&sortby, "sort", "s", "prio",
                                   "Sort tasks by parameter (prio|date|len|prio-rev|date-rev|len-rev|id|rand)")
        cmdList.Flags().StringVarP(&prettyformat, "pretty", "", "%i %p %t",
                                   "Pretty print tasks")

        var cmdAdd = &cobra.Command{
            Use:   "add [task]",
            Short: "Adds a task to the todo list.",
            Long:  `Adds a task to the todo list.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                task := strings.Join(args, " ")
                tasks.Add(task)

                tasks.Save(filename)
            },
        }

        var nofinishdate bool
        var cmdDone = &cobra.Command{
            Use:   "done [taskid]",
            Short: "Marks task as done.",
            Long:  `Marks task as done.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                err = tasks.Done(taskid, !nofinishdate)
                if err != nil {
                        fmt.Printf("There was an error %v\n", err)
                }

                tasks.Save(filename)
            },
        }
        cmdDone.Flags().BoolVarP(&nofinishdate, "no-finish-date", "D", false,
                                        "Do not mark finished tasks with date.")

        var cmdArchive = &cobra.Command{
            Use:   "archive [taskid]",
            Short: "Archives task.",
            Long:  `Archives task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                fmt.Printf("Archiving task %v\n", taskid)

                tasks.Save(filename)
            },
        }

        var editprio string
        var edittodo string
        var cmdEdit = &cobra.Command{
            Use:   "edit [taskid]",
            Short: "Edits given task.",
            Long:  `Edits given task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                if len(editprio) > 0 {
                        tasks[taskid].SetPriority(editprio[0])
                        tasks[taskid].RebuildRawTodo()
                }

                if len(edittodo) > 0 {
                        tasks[taskid].SetTodo(edittodo)
                        tasks[taskid].RebuildRawTodo()
                }

                tasks.Save(filename)
            },
        }

        cmdEdit.PersistentFlags().StringVarP(&editprio, "priority", "p", "",
                                     "Sets task's priority.")
        cmdEdit.PersistentFlags().StringVarP(&edittodo, "todo", "t", "",
                                     "Edit task's todo.")

        var GotodoCmd = &cobra.Command{
            Use:   "gotodo",
            Short: "Gotodo is a go implementation of todo.txt.",
            Long: `A small, fast and fun implementation of todo.txt`,
            Run: func(cmd *cobra.Command, args []string) {
                cmdList.Run(cmd, nil)
            },
        }

        GotodoCmd.PersistentFlags().StringVarP(&filename, "filename", "", "",
                                     "Load tasks from this file.")

        conf.ParseAll()

        // values here
        flags["file"] = *flagFilename

        // sadly, this is the best we can do right now
        if filename == "" {
                if *flagFilename == "" {
                        filename = "todo.txt"
                } else {
                        filename = *flagFilename
                }
        }

        GotodoCmd.AddCommand(cmdList)
        GotodoCmd.AddCommand(cmdAdd)
        GotodoCmd.AddCommand(cmdDone)
        GotodoCmd.AddCommand(cmdArchive)
        GotodoCmd.AddCommand(cmdEdit)
        GotodoCmd.AddCommand(cmdConfig)
        GotodoCmd.Execute()
}
<commit_msg>setting variables from command line<commit_after>package main

import  (
        "fmt"
        "os"
        "../go-todotxt"
        "github.com/spf13/cobra"
        "os/user"
        "strings"
        "strconv"
        "github.com/rakyll/globalconf"
        "flag"
)

func extendedLoader(filename string) (todotxt.TaskList, error) {
        usr, err := user.Current()
        if err != nil {
                return nil, err
        }

        filename = strings.Replace(filename, "~", usr.HomeDir, -1)
        tasks := todotxt.LoadTaskList(filename)

        return tasks, nil
}

func main() {

        conf, _ := globalconf.New("gotodo")

        var numtasks bool
        var sortby string
        var finished bool
        var prettyformat string
        var filename string

        var flagFilename = flag.String("file", "", "Location of the todo.txt file.")

        var flags = make(map[string]string)

        var cmdConfig = &cobra.Command{
            Use:   "config [key] [value]",
            Short: "Show and sets config values",
            Long:  `Config can be used to see and also set configuration variables.`,
            Run: func(cmd *cobra.Command, args []string) {
                    if len(args) == 0{
                            fmt.Printf("Available config variables:\n\n")
                            for k := range flags {
                                    fmt.Printf("%s\n", k)
                            }
                    }
                    if len(args) == 1 {
                            val, exists := flags[args[0]]
                            if exists {
                                    fmt.Printf("%s\n", val)
                            } else {
                                    // otherwise exit with non-zero status
                                    os.Exit(1)
                            }
                    }
                    if len(args) == 2 {
                            _, exists := flags[args[0]]
                            if exists {
                                    f := &flag.Flag{Name: args[0], Value: args[1]}
                                    conf.Set("", f)
                            } else {
                                    os.Exit(1)
                            }
                    }
            },
        }

        var cmdList = &cobra.Command{
            Use:   "list [keyword]",
            Short: "Lists tasks that contain keyword, if any",
            Long:  `List is the most basic command that is used for listing tasks.
                    You can specify a keyword as well as other options.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if numtasks {
                    fmt.Println(tasks.Len())
                } else {
                    tasks.Sort(sortby)

                    var filteredTasks todotxt.TaskList
                    for _, task := range tasks {
                        if (!task.Finished() && !finished) ||
                           (task.Finished() && finished) {
                           filteredTasks = append(filteredTasks, task)
                        }
                    }

                    for _, task := range filteredTasks {
                            task.SetIdPaddingBy(tasks)
                            fmt.Println(task.PrettyPrint(prettyformat))
                    }
                }
            },
        }
        cmdList.Flags().BoolVarP(&numtasks, "num-tasks", "n", false,
                                 "Show the number of tasks")
        cmdList.Flags().BoolVarP(&finished, "finished", "f", false,
                                 "Show finished tasks")
        cmdList.Flags().StringVarP(&sortby, "sort", "s", "prio",
                                   "Sort tasks by parameter (prio|date|len|prio-rev|date-rev|len-rev|id|rand)")
        cmdList.Flags().StringVarP(&prettyformat, "pretty", "", "%i %p %t",
                                   "Pretty print tasks")

        var cmdAdd = &cobra.Command{
            Use:   "add [task]",
            Short: "Adds a task to the todo list.",
            Long:  `Adds a task to the todo list.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                task := strings.Join(args, " ")
                tasks.Add(task)

                tasks.Save(filename)
            },
        }

        var nofinishdate bool
        var cmdDone = &cobra.Command{
            Use:   "done [taskid]",
            Short: "Marks task as done.",
            Long:  `Marks task as done.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                err = tasks.Done(taskid, !nofinishdate)
                if err != nil {
                        fmt.Printf("There was an error %v\n", err)
                }

                tasks.Save(filename)
            },
        }
        cmdDone.Flags().BoolVarP(&nofinishdate, "no-finish-date", "D", false,
                                        "Do not mark finished tasks with date.")

        var cmdArchive = &cobra.Command{
            Use:   "archive [taskid]",
            Short: "Archives task.",
            Long:  `Archives task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                fmt.Printf("Archiving task %v\n", taskid)

                tasks.Save(filename)
            },
        }

        var editprio string
        var edittodo string
        var cmdEdit = &cobra.Command{
            Use:   "edit [taskid]",
            Short: "Edits given task.",
            Long:  `Edits given task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                if len(editprio) > 0 {
                        tasks[taskid].SetPriority(editprio[0])
                        tasks[taskid].RebuildRawTodo()
                }

                if len(edittodo) > 0 {
                        tasks[taskid].SetTodo(edittodo)
                        tasks[taskid].RebuildRawTodo()
                }

                tasks.Save(filename)
            },
        }

        cmdEdit.PersistentFlags().StringVarP(&editprio, "priority", "p", "",
                                     "Sets task's priority.")
        cmdEdit.PersistentFlags().StringVarP(&edittodo, "todo", "t", "",
                                     "Edit task's todo.")

        var GotodoCmd = &cobra.Command{
            Use:   "gotodo",
            Short: "Gotodo is a go implementation of todo.txt.",
            Long: `A small, fast and fun implementation of todo.txt`,
            Run: func(cmd *cobra.Command, args []string) {
                cmdList.Run(cmd, nil)
            },
        }

        GotodoCmd.PersistentFlags().StringVarP(&filename, "filename", "", "",
                                     "Load tasks from this file.")

        conf.ParseAll()

        // values here
        flags["file"] = *flagFilename

        // sadly, this is the best we can do right now
        if filename == "" {
                if *flagFilename == "" {
                        filename = "todo.txt"
                } else {
                        filename = *flagFilename
                }
        }

        GotodoCmd.AddCommand(cmdList)
        GotodoCmd.AddCommand(cmdAdd)
        GotodoCmd.AddCommand(cmdDone)
        GotodoCmd.AddCommand(cmdArchive)
        GotodoCmd.AddCommand(cmdEdit)
        GotodoCmd.AddCommand(cmdConfig)
        GotodoCmd.Execute()
}
<|endoftext|>


<commit_before>package bot

import (
    "regexp"
    "fmt"
)

// Reference: tools.ietf.org/html/rfc2812

// TODO:
// Some other way of handling attempts at bad configurations apart from
// returning nil?
//
// Servers specify [MAX]NICKLEN/[MAX]CHANNELLEN and various other limits,
// how should this be handled?
//
// Option 1:
//     Specify server in Create("irc.gamesurge.net"), and have the server info
//     loaded into config when the server replies.
// Option 2: (current implementation)
//     Set our own predefined limits and (in the future, perhaps)
//     truncate any data that exceeds server limits upon receiving
//     info from the server.
// Option 3:
//     Do nothing.

// Settings mostly inspired by GameSurge
const (
    MAX_NAME_LENGTH = 30
)

type BotConfig struct {
    name string
    channels []string // Assuming the bot can be in multiple channels.
}

// Disables some warnings during testing
var testing_mode bool = false;

// Returns a bot configuration interface
func Create() *BotConfig {
    return &BotConfig{}
}

// Check that the bot name is a valid IRC nickname
func check_valid_nick(name string) bool {
    name_regex := "^[a-zA-Z\\[\\]\\\\`_\\^\\{\\}\\|][a-zA-Z\\d\\[\\]\\\\`_\\^\\{\\}\\|]*$"
    match, err := regexp.MatchString(name_regex, name)

    if err != nil {
        fmt.Printf("Unable to verify if %v is a valid nickname\n", name)
        fmt.Println(err)
        return false
    }

    if !match {
        // TODO: Print a list of rules?
        if !testing_mode {
            fmt.Printf("%v is not a valid nickname for a bot!\n", name)
        }

        return false
    }

    return true
}

// Set the name of the bot
func (bot *BotConfig) Name(name string) *BotConfig {
    // Cannot have a bot with no name
    if len(name) == 0 {
        return nil
    }

    // Cannot have a bot with a too long name
    if len(name) > MAX_NAME_LENGTH {
        return nil
    }

    // Check if it's a valid nickname
    if !check_valid_nick(name) {
        return nil
    }

    // Name was valid
    bot.name = name
    return bot
}

// TODO: Add the bot to a channel
func (bot *BotConfig) Channels(channel_name string) *BotConfig {
    return nil
}
<commit_msg>Added a bit of documentation to bot/config.go<commit_after>package bot

import (
    "regexp"
    "fmt"
)

// Reference: tools.ietf.org/html/rfc2812

// TODO:
// Some other way of handling attempts at bad configurations apart from
// returning nil?
//
// Servers specify [MAX]NICKLEN/[MAX]CHANNELLEN and various other limits,
// how should this be handled?
//
// Option 1:
//     Specify server in Create("irc.gamesurge.net"), and have the server info
//     loaded into config when the server replies.
// Option 2: (current implementation)
//     Set our own predefined limits and (in the future, perhaps)
//     truncate any data that exceeds server limits upon receiving
//     info from the server.
// Option 3:
//     Do nothing.

// Settings mostly inspired by GameSurge
const (
    MAX_NAME_LENGTH = 30
)

// Configuration interface for IRC bots
type BotConfig struct {
    name string       // Name of the IRC bot
    channels []string // List of channels this bot will join
}

// TODO: Ditch this, and stack errors on the config struct instead.
// Disables some warnings during testing
var testing_mode bool = false;

// Returns a bot configuration interface
func Create() *BotConfig {
    return &BotConfig{}
}

// Check that the bot name is a valid IRC nickname
func check_valid_nick(name string) bool {
    name_regex := "^[a-zA-Z\\[\\]\\\\`_\\^\\{\\}\\|][a-zA-Z\\d\\[\\]\\\\`_\\^\\{\\}\\|]*$"
    match, err := regexp.MatchString(name_regex, name)

    if err != nil {
        fmt.Printf("Unable to verify if %v is a valid nickname\n", name)
        fmt.Println(err)
        return false
    }

    if !match {
        // TODO: Print a list of rules?
        if !testing_mode {
            fmt.Printf("%v is not a valid nickname for a bot!\n", name)
        }

        return false
    }

    return true
}

// Set the name of the bot
func (bot *BotConfig) Name(name string) *BotConfig {
    // Cannot have a bot with no name
    if len(name) == 0 {
        return nil
    }

    // Cannot have a bot with a too long name
    if len(name) > MAX_NAME_LENGTH {
        return nil
    }

    // Check if it's a valid nickname
    if !check_valid_nick(name) {
        return nil
    }

    // Name was valid
    bot.name = name
    return bot
}

// TODO: Add the bot to a channel
func (bot *BotConfig) Channels(channel_name string) *BotConfig {
    return nil
}
<|endoftext|>


<commit_before>package bot

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

	"github.com/arachnist/gorepost/irc"
)

func google(output func(irc.Message), msg irc.Message) {
	if strings.Split(msg.Trailing, " ")[0] != ":g" {
		return
	}

	query := strings.TrimPrefix(msg.Trailing, ":g ")

	req, _ := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)

	q := req.URL.Query()
	q.Set("q", query)

	req.URL.RawQuery = q.Encode()

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		output(reply(msg, "problem connecting to google"))
		return
	}

	defer resp.Body.Close()

	var data struct {
		ResponseData struct {
			Results []struct {
				TitleNoFormatting string
				URL               string
			}
		}
	}
	if errJ := json.NewDecoder(resp.Body).Decode(&data); errJ != nil {
		output(reply(msg, "problem decoding google response"))
		return
	}
	if len(data.ResponseData.Results) > 0 {
		res := data.ResponseData.Results[0]
		output(reply(msg, res.TitleNoFormatting+" "+res.URL))
	}
}

func init() {
	addCallback("PRIVMSG", "google", google)
}
<commit_msg>Urldecode links returned from google search.<commit_after>package bot

import (
	"encoding/json"
	"net/http"
	"net/url"
	"strings"

	"github.com/arachnist/gorepost/irc"
)

func google(output func(irc.Message), msg irc.Message) {
	if strings.Split(msg.Trailing, " ")[0] != ":g" {
		return
	}

	query := strings.TrimPrefix(msg.Trailing, ":g ")

	req, _ := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)

	q := req.URL.Query()
	q.Set("q", query)

	req.URL.RawQuery = q.Encode()

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		output(reply(msg, "problem connecting to google"))
		return
	}

	defer resp.Body.Close()

	var data struct {
		ResponseData struct {
			Results []struct {
				TitleNoFormatting string
				URL               string
			}
		}
	}
	if errJ := json.NewDecoder(resp.Body).Decode(&data); errJ != nil {
		output(reply(msg, "problem decoding google response"))
		return
	}
	if len(data.ResponseData.Results) > 0 {
		res := data.ResponseData.Results[0]
		link, err := url.QueryUnescape(res.URL)
		if err != nil {
			output(reply(msg, "problem decoding url"))
		}
		output(reply(msg, res.TitleNoFormatting+" "+link))
	}
}

func init() {
	addCallback("PRIVMSG", "google", google)
}
<|endoftext|>