<commit_before>package main

import (
	"fmt"
	"html/template"
	"net/http"
	"os"
	"strconv"
)

const htmlPage string = `

Auto transition

<form method="post" action="/">

Input:

  <input type="checkbox" name="r" value="1" {{if .Input1}}checked="checked"{{end}} /> 1
  <input type="checkbox" name="r" value="2" {{if .Input2}}checked="checked"{{end}} /> 2
  <input type="checkbox" name="r" value="3" {{if .Input3}}checked="checked"{{end}} /> 3
  <input type="checkbox" name="r" value="4" {{if .Input4}}checked="checked"{{end}} /> 4

Transition mode:

  {{select .Trans}} rate {{select .Rate}}

Interval:

  {{select .Interval}}

Boot time options:

  <input type="checkbox" name="broadcast" value="true" {{if .StartLiveBroadcast}}checked="checked"{{end}} /> Start live broadcasting

  <input type="checkbox" name="upload" value="true" {{if .UploadStillPicture}}checked="checked"{{end}}/> Upload a still picture and use it as input4

  <input type="text" name="picture_url" size="40" value="{{.PictureUrl}}" />

  <input type="submit" name="send" value="send" />

<input type="submit" name="quit" value="quit" />

</form>`

type tmplParams struct {
	Interval           *forHTMLSelect
	Trans              *forHTMLSelect
	Rate               *forHTMLSelect
	StartLiveBroadcast bool
	UploadStillPicture bool
	PictureUrl string
	Input1             bool
	Input2             bool
	Input3             bool
	Input4             bool
}

type selectItem struct {
	val int
	str string
}

type forHTMLSelect struct {
	Name     string
	Options  []selectItem
	Selected int
}

var (
	params Params
	notify chan Params
)

var tp = tmplParams{
	Interval: &forHTMLSelect{
		Name: "interval",
		Options: []selectItem{
			selectItem{10, "10 sec"},
			selectItem{30, "30 sec"},
			selectItem{60, "1 min"},
			selectItem{180, "3 min"},
			selectItem{300, "5 min"},
			selectItem{600, "10 min"},
			selectItem{1200, "20 min"},
		},
	},
	Trans: &forHTMLSelect{
		Name: "trans",
		Options: []selectItem{
			selectItem{0, "Cut"},
			selectItem{1, "Mix"},
			selectItem{2, "Dip to 1"},
			selectItem{258, "Dip to 2"},
			selectItem{514, "Dip to 3"},
			selectItem{770, "Dip to 4"},
			selectItem{3, "Wipe (horizontal)"},
			selectItem{4, "Wipe (horizontal_r)"},
			selectItem{5, "Wipe (vertical)"},
			selectItem{6, "Wipe (vertical_r)"},
			selectItem{7, "Wipe (horizontal_slide)"},
			selectItem{8, "Wipe (horizontal_slide_r)"},
			selectItem{9, "Wipe (vertical_slide)"},
			selectItem{10, "Wipe (vertical_slide_r)"},
			selectItem{11, "Wipe (horizontal_double_slide)"},
			selectItem{12, "Wipe (horizontal_double_slide_r)"},
			selectItem{13, "Wipe (vertical_double_slide)"},
			selectItem{14, "Wipe (vertical_double_slide_r)"},
			selectItem{15, "Wipe (square_top_left)"},
			selectItem{16, "Wipe (square_top_left_r)"},
			selectItem{17, "Wipe (square_top)"},
			selectItem{18, "Wipe (square_top_r)"},
			selectItem{19, "Wipe (square_top_right)"},
			selectItem{20, "Wipe (square_top_right_r)"},
			selectItem{21, "Wipe (square_center_left)"},
			selectItem{22, "Wipe (square_center_left_r)"},
			selectItem{23, "Wipe (square_center)"},
			selectItem{24, "Wipe (square_center_r)"},
			selectItem{25, "Wipe (square_center_right)"},
			selectItem{26, "Wipe (square_center_right_r)"},
			selectItem{27, "Wipe (square_bottom_left)"},
			selectItem{28, "Wipe (square_bottom_left_r)"},
			selectItem{29, "Wipe (square_bottom)"},
			selectItem{30, "Wipe (square_bottom_r)"},
			selectItem{31, "Wipe (square_bottom_right)"},
			selectItem{32, "Wipe (square_bottom_right_r)"},
		},
	},
	Rate: &forHTMLSelect{
		Name: "rate",
		Options: []selectItem{
			selectItem{1000, "1 sec"},
			selectItem{2000, "2 sec"},
			selectItem{3000, "3 sec"},
			selectItem{4000, "4 sec"},
			selectItem{5000, "5 sec"},
			selectItem{6000, "6 sec"},
			selectItem{7000, "7 sec"},
			selectItem{8000, "8 sec"},
			selectItem{9000, "9 sec"},
			selectItem{10000, "10 sec"},
			selectItem{11000, "11 sec"},
			selectItem{12000, "12 sec"},
			selectItem{20000, "20 sec"},
		},
	},
}

func form(r *http.Request) {
	if r.FormValue("quit") == "quit" {
		os.Exit(0)
	}
	if r.FormValue("send") != "send" {
		return
	}
	r.ParseForm()
	for i := range params.Input {
		params.Input[i] = false
	}
	for _, i := range r.Form["r"] {
		val, err := strconv.ParseInt(i, 10, 32)
		if err == nil {
			params.Input[val-1] = true
		}
	}
	interval, _ := strconv.ParseInt(r.FormValue("interval"), 10, 32)
	t, _ := strconv.ParseInt(r.FormValue("trans"), 10, 32)
	rate, _ := strconv.ParseInt(r.FormValue("rate"), 10, 32)
	params.Interval = int(interval)
	params.Trans = int(t)
	params.Rate = int(rate)
	params.StartLiveBroadcast = (r.FormValue("broadcast") == "true")
	params.UploadStillPicture = (r.FormValue("upload") == "true")
	params.PictureUrl = r.FormValue("picture_url")
	notify <- params
}

func handler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		form(r)
	}

	funcMap := template.FuncMap{
		"select": func(sel *forHTMLSelect) template.HTML {
			h := fmt.Sprintf(`<select name="%s">`, sel.Name)
			for _, v := range sel.Options {
				var s string
				if v.val == sel.Selected {
					s = " selected"
				}
				h += fmt.Sprintf(`<option value="%d"%s>%s</option>`, v.val, s, v.str)
			}
			h += "</select>"
			return template.HTML(h)
		},
	}

	tp.Interval.Selected = params.Interval
	tp.Trans.Selected = params.Trans
	tp.Rate.Selected = params.Rate
	tp.StartLiveBroadcast = params.StartLiveBroadcast
	tp.UploadStillPicture = params.UploadStillPicture
	tp.PictureUrl = params.PictureUrl
	tp.Input1 = params.Input[0]
	tp.Input2 = params.Input[1]
	tp.Input3 = params.Input[2]
	tp.Input4 = params.Input[3]

	tmpl, _ := template.New("autotrans").Funcs(funcMap).Parse(htmlPage)
	tmpl.Execute(w, tp)
}

func WebUI(pa Params, nt chan Params) {
	params = pa
	notify = nt
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
<commit_msg>autotrans: Prepare template ahead of time<commit_after>package main

import (
	"fmt"
	"html/template"
	"net/http"
	"os"
	"strconv"
)

const htmlPage string = `

Auto transition

<form method="post" action="/">

Input:

  <input type="checkbox" name="r" value="1" {{if .Input1}}checked="checked"{{end}} /> 1
  <input type="checkbox" name="r" value="2" {{if .Input2}}checked="checked"{{end}} /> 2
  <input type="checkbox" name="r" value="3" {{if .Input3}}checked="checked"{{end}} /> 3
  <input type="checkbox" name="r" value="4" {{if .Input4}}checked="checked"{{end}} /> 4

Transition mode:

  {{select .Trans}} rate {{select .Rate}}

Interval:

  {{select .Interval}}

Boot time options:

  <input type="checkbox" name="broadcast" value="true" {{if .StartLiveBroadcast}}checked="checked"{{end}} /> Start live broadcasting

  <input type="checkbox" name="upload" value="true" {{if .UploadStillPicture}}checked="checked"{{end}}/> Upload a still picture and use it as input4

  <input type="text" name="picture_url" size="40" value="{{.PictureUrl}}" />

  <input type="submit" name="send" value="send" />

<input type="submit" name="quit" value="quit" />

</form>`

type tmplParams struct {
	Interval           *forHTMLSelect
	Trans              *forHTMLSelect
	Rate               *forHTMLSelect
	StartLiveBroadcast bool
	UploadStillPicture bool
	PictureUrl string
	Input1             bool
	Input2             bool
	Input3             bool
	Input4             bool
}

type selectItem struct {
	val int
	str string
}

type forHTMLSelect struct {
	Name     string
	Options  []selectItem
	Selected int
}

var (
	params Params
	notify chan Params
	template0 *template.Template
)

var tp = tmplParams{
	Interval: &forHTMLSelect{
		Name: "interval",
		Options: []selectItem{
			selectItem{10, "10 sec"},
			selectItem{30, "30 sec"},
			selectItem{60, "1 min"},
			selectItem{180, "3 min"},
			selectItem{300, "5 min"},
			selectItem{600, "10 min"},
			selectItem{1200, "20 min"},
		},
	},
	Trans: &forHTMLSelect{
		Name: "trans",
		Options: []selectItem{
			selectItem{0, "Cut"},
			selectItem{1, "Mix"},
			selectItem{2, "Dip to 1"},
			selectItem{258, "Dip to 2"},
			selectItem{514, "Dip to 3"},
			selectItem{770, "Dip to 4"},
			selectItem{3, "Wipe (horizontal)"},
			selectItem{4, "Wipe (horizontal_r)"},
			selectItem{5, "Wipe (vertical)"},
			selectItem{6, "Wipe (vertical_r)"},
			selectItem{7, "Wipe (horizontal_slide)"},
			selectItem{8, "Wipe (horizontal_slide_r)"},
			selectItem{9, "Wipe (vertical_slide)"},
			selectItem{10, "Wipe (vertical_slide_r)"},
			selectItem{11, "Wipe (horizontal_double_slide)"},
			selectItem{12, "Wipe (horizontal_double_slide_r)"},
			selectItem{13, "Wipe (vertical_double_slide)"},
			selectItem{14, "Wipe (vertical_double_slide_r)"},
			selectItem{15, "Wipe (square_top_left)"},
			selectItem{16, "Wipe (square_top_left_r)"},
			selectItem{17, "Wipe (square_top)"},
			selectItem{18, "Wipe (square_top_r)"},
			selectItem{19, "Wipe (square_top_right)"},
			selectItem{20, "Wipe (square_top_right_r)"},
			selectItem{21, "Wipe (square_center_left)"},
			selectItem{22, "Wipe (square_center_left_r)"},
			selectItem{23, "Wipe (square_center)"},
			selectItem{24, "Wipe (square_center_r)"},
			selectItem{25, "Wipe (square_center_right)"},
			selectItem{26, "Wipe (square_center_right_r)"},
			selectItem{27, "Wipe (square_bottom_left)"},
			selectItem{28, "Wipe (square_bottom_left_r)"},
			selectItem{29, "Wipe (square_bottom)"},
			selectItem{30, "Wipe (square_bottom_r)"},
			selectItem{31, "Wipe (square_bottom_right)"},
			selectItem{32, "Wipe (square_bottom_right_r)"},
		},
	},
	Rate: &forHTMLSelect{
		Name: "rate",
		Options: []selectItem{
			selectItem{1000, "1 sec"},
			selectItem{2000, "2 sec"},
			selectItem{3000, "3 sec"},
			selectItem{4000, "4 sec"},
			selectItem{5000, "5 sec"},
			selectItem{6000, "6 sec"},
			selectItem{7000, "7 sec"},
			selectItem{8000, "8 sec"},
			selectItem{9000, "9 sec"},
			selectItem{10000, "10 sec"},
			selectItem{11000, "11 sec"},
			selectItem{12000, "12 sec"},
			selectItem{20000, "20 sec"},
		},
	},
}

func form(r *http.Request) {
	if r.FormValue("quit") == "quit" {
		os.Exit(0)
	}
	if r.FormValue("send") != "send" {
		return
	}
	r.ParseForm()
	for i := range params.Input {
		params.Input[i] = false
	}
	for _, i := range r.Form["r"] {
		val, err := strconv.ParseInt(i, 10, 32)
		if err == nil {
			params.Input[val-1] = true
		}
	}
	interval, _ := strconv.ParseInt(r.FormValue("interval"), 10, 32)
	t, _ := strconv.ParseInt(r.FormValue("trans"), 10, 32)
	rate, _ := strconv.ParseInt(r.FormValue("rate"), 10, 32)
	params.Interval = int(interval)
	params.Trans = int(t)
	params.Rate = int(rate)
	params.StartLiveBroadcast = (r.FormValue("broadcast") == "true")
	params.UploadStillPicture = (r.FormValue("upload") == "true")
	params.PictureUrl = r.FormValue("picture_url")
	notify <- params
}

func handler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		form(r)
	}

	tp.Interval.Selected = params.Interval
	tp.Trans.Selected = params.Trans
	tp.Rate.Selected = params.Rate
	tp.StartLiveBroadcast = params.StartLiveBroadcast
	tp.UploadStillPicture = params.UploadStillPicture
	tp.PictureUrl = params.PictureUrl
	tp.Input1 = params.Input[0]
	tp.Input2 = params.Input[1]
	tp.Input3 = params.Input[2]
	tp.Input4 = params.Input[3]

	template0.Execute(w, tp)
}

func WebUI(pa Params, nt chan Params) {
	params = pa
	notify = nt

	funcMap := template.FuncMap{
		"select": func(sel *forHTMLSelect) template.HTML {
			h := fmt.Sprintf(`<select name="%s">`, sel.Name)
			for _, v := range sel.Options {
				var s string
				if v.val == sel.Selected {
					s = " selected"
				}
				h += fmt.Sprintf(`<option value="%d"%s>%s</option>`, v.val, s, v.str)
			}
			h += "</select>"
			return template.HTML(h)
		},
	}
	template0 = template.Must(template.New("autotrans").Funcs(funcMap).Parse(htmlPage))

	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package telegram

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type ParseMode string

// Parse modes
const (
	ModeNone     ParseMode = ""
	ModeMarkdown ParseMode = "Markdown"
	ModeHTML     ParseMode = "HTML"
)

// Bot represent a Telegram bot.
type Bot struct {
	token     string
	baseURL   string
	client    *http.Client
	messageCh chan *Message
}

// New creates a new Telegram bot with the given token, which is given by
// Botfather. See https://core.telegram.org/bots#botfather
func New(token string) *Bot {
	return &Bot{
		token:     token,
		baseURL:   fmt.Sprintf("https://api.telegram.org/bot%v/", token),
		client:    &http.Client{Timeout: 30 * time.Second},
		messageCh: make(chan *Message),
	}
}

func (b *Bot) Handler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer w.WriteHeader(http.StatusOK)

		var u Update
		_ = json.NewDecoder(r.Body).Decode(&u)
		b.messageCh <- &u.Payload
	}
}

func (b *Bot) Messages() <-chan *Message {
	return b.messageCh
}

// SetWebhook assigns bot's webhook url with the given url.
func (b *Bot) SetWebhook(webhook string) error {
	params := url.Values{}
	params.Set("url", webhook)

	var r response
	err := b.sendCommand(nil, "setWebhook", params, &r)
	if err != nil {
		return err
	}

	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

// SendMessage sends text message to the recipient. Callers can send plain
// text or markdown messages by setting mode parameter.
func (b *Bot) SendMessage(recipient int64, message string, opts ...SendOption) (Message, error) {
	const method = "sendMessage"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("text", message)
	mapSendOptions(&params, opts...)

	var r struct {
		response
		Message Message `json:"result"`
	}
	err := b.sendCommand(nil, "sendMessage", params, &r)
	if err != nil {
		return r.Message, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

func (b *Bot) forwardMessage(recipient User, message Message) (Message, error) {
	panic("not implemented yet")
}

// SendPhoto sends given photo to recipient. Only remote URLs are supported for now.
// A trivial example is:
//
//  b := bot.New("your-token-here")
//  photo := bot.Photo{URL: "http://i.imgur.com/6S9naG6.png"}
//  err := b.SendPhoto(recipient, photo, "sample image", nil)
func (b *Bot) SendPhoto(recipient int64, photo Photo, opts ...SendOption) (Message, error) {
	const method = "sendPhoto"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("caption", photo.Caption)

	mapSendOptions(&params, opts...)
	var r struct {
		response
		Message Message `json:"result"`
	}

	var err error
	if photo.Exists() {
		params.Set("photo", photo.FileID)
		err = b.sendCommand(nil, method, params, &r)
	} else if photo.URL != "" {
		params.Set("photo", photo.URL)
		err = b.sendCommand(nil, method, params, &r)
	} else {
		err = b.sendFile(method, photo.File, "photo", params, &r)
	}

	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

func (b *Bot) sendFile(method string, f File, form string, params url.Values, v interface{}) error {
	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	part, err := w.CreateFormFile(form, f.Name)
	if err != nil {
		return err
	}

	_, err = io.Copy(part, f.Body)
	if err != nil {
		return err
	}

	for k, v := range params {
		w.WriteField(k, v[0])
	}

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

	resp, err := b.client.Post(b.baseURL+method, w.FormDataContentType(), &buf)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status code: %v", resp.StatusCode)
	}

	return json.NewDecoder(resp.Body).Decode(&v)
}

// SendAudio sends audio files, if you want Telegram clients to display
// them in the music player. audio must be in the .mp3 format and must not
// exceed 50 MB in size.
func (b *Bot) SendAudio(recipient int64, audio Audio, opts ...SendOption) (Message, error) {
	const method = "sendAudio"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("caption", audio.Caption)

	mapSendOptions(&params, opts...)
	var r struct {
		response
		Message Message `json:"result"`
	}

	var err error
	if audio.Exists() {
		params.Set("audio", audio.FileID)
		err = b.sendCommand(nil, method, params, &r)
	} else if audio.URL != "" {
		params.Set("audio", audio.URL)
		err = b.sendCommand(nil, method, params, &r)
	} else {
		err = b.sendFile(method, audio.File, "audio", params, &r)
	}

	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

// SendDocument sends general files. Documents must not exceed 50 MB in size.
func (b *Bot) sendDocument(recipient int64, document Document, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

//SendSticker sends stickers with .webp extensions.
func (b *Bot) sendSticker(recipient int64, sticker Sticker, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

// SendVideo sends video files. Telegram clients support mp4 videos (other
// formats may be sent as Document). Video files must not exceed 50 MB in size.
func (b *Bot) sendVideo(recipient int64, video Video, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

// SendVoice sends audio files, if you want Telegram clients to display
// the file as a playable voice message. For this to work, your audio must be
// in an .ogg file encoded with OPUS (other formats may be sent as Audio or
// Document). audio must not exceed 50 MB in size.
func (b *Bot) sendVoice(recipient int64, audio Audio, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

// SendLocation sends location point on the map.
func (b *Bot) SendLocation(recipient int64, location Location, opts ...SendOption) (Message, error) {
	const method = "sendLocation"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(location.Long, 'f', -1, 64))

	mapSendOptions(&params, opts...)

	var r struct {
		response
		Message Message `json:"result"`
	}
	err := b.sendCommand(nil, method, params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

// SendVenue sends information about a venue.
func (b *Bot) SendVenue(recipient int64, venue Venue, opts ...SendOption) (Message, error) {
	const method = "sendVenue"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(venue.Location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(venue.Location.Long, 'f', -1, 64))
	params.Set("title", venue.Title)
	params.Set("address", venue.Address)

	mapSendOptions(&params, opts...)

	var r struct {
		response
		Message Message `json:"result"`
	}
	err := b.sendCommand(nil, method, params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

// SendChatAction broadcasts type of action to recipient, such as `typing`,
// `uploading a photo` etc.
func (b *Bot) SendChatAction(recipient int64, action Action) error {
	const method = "sendChatAction"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("action", string(action))

	var r response
	err := b.sendCommand(nil, method, params, &r)
	if err != nil {
		return err

	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

// sendOptions configure a SendMessage call. sendOptions are set by the
// SendOption values passed to SendMessage.
type sendOptions struct {
	replyTo int64

	parseMode ParseMode

	disableWebPagePreview bool

	disableNotification bool

	replyMarkup ReplyMarkup
}

// SendOption configures how we configure the message to be sent.
type SendOption func(*sendOptions)

// WithParseMode returns a SendOption which sets the message format, such as
// HTML, Markdown etc.
func WithParseMode(mode ParseMode) SendOption {
	return func(o *sendOptions) {
		o.parseMode = mode
	}
}

// WithReplyTo returns a SendOption which sets the message to be replied to.
func WithReplyTo(to int64) SendOption {
	return func(o *sendOptions) {
		o.replyTo = to
	}
}

// WithReplyMarkup returns a SendOption which configures a custom keyboard for
// the sent message.
func WithReplyMarkup(markup ReplyMarkup) SendOption {
	return func(o *sendOptions) {
		o.replyMarkup = markup
	}
}

// WithDisableWebPagePreview returns a SendOption which disables webpage
// previews if the message contains a link.
func WithDisableWebPagePreview(disable bool) SendOption {
	return func(o *sendOptions) {
		o.disableWebPagePreview = disable
	}
}

func WithDisableNotification(disable bool) SendOption {
	return func(o *sendOptions) {
		o.disableNotification = disable
	}
}

func (b *Bot) GetFile(fileID string) (File, error) {
	params := url.Values{}
	params.Set("file_id", fileID)

	var r struct {
		response
		File File `json:"result"`
	}
	err := b.sendCommand(nil, "getFile", params, &r)
	if err != nil {
		return File{}, err
	}

	if !r.OK {
		return File{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.File, nil
}

func (b *Bot) GetFileDownloadURL(fileID string) (string, error) {
	f, err := b.GetFile(fileID)
	if err != nil {
		return "", err
	}

	u := "https://api.telegram.org/file/bot" + b.token + "/" + f.FilePath
	return u, nil
}

func (b *Bot) sendCommand(ctx context.Context, method string, params url.Values, v interface{}) error {
	req, err := http.NewRequest("POST", b.baseURL+method, strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}

	if ctx == nil {
		ctx = context.Background()
	}

	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := b.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status code: %v", resp.StatusCode)
	}
	return json.NewDecoder(resp.Body).Decode(&v)
}

func (b *Bot) getMe() (User, error) {
	var r struct {
		response
		User User `json:"result"`
	}
	err := b.sendCommand(nil, "getMe", url.Values{}, &r)
	if err != nil {
		return User{}, err
	}

	if !r.OK {
		return User{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.User, nil
}

func mapSendOptions(m *url.Values, opts ...SendOption) {
	var o sendOptions
	for _, opt := range opts {
		if opt != nil {
			opt(&o)
		}
	}

	if o.replyTo != 0 {
		m.Set("reply_to_message_id", strconv.FormatInt(o.replyTo, 10))
	}

	if o.disableWebPagePreview {
		m.Set("disable_web_page_preview", "true")
	}

	if o.disableNotification {
		m.Set("disable_notification", "true")
	}

	if o.parseMode != ModeNone {
		m.Set("parse_mode", string(o.parseMode))
	}

	// TODO: map ReplyMarkup options as well
}

// response is a common response structure.
type response struct {
	OK      bool   `json:"ok"`
	Desc    string `json:"description"`
	ErrCode int    `json:"error_code"`
}
<commit_msg>telegram: send keyboard data if any<commit_after>package telegram

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type ParseMode string

// Parse modes
const (
	ModeNone     ParseMode = ""
	ModeMarkdown ParseMode = "Markdown"
	ModeHTML     ParseMode = "HTML"
)

// Bot represent a Telegram bot.
type Bot struct {
	token     string
	baseURL   string
	client    *http.Client
	messageCh chan *Message
}

// New creates a new Telegram bot with the given token, which is given by
// Botfather. See https://core.telegram.org/bots#botfather
func New(token string) *Bot {
	return &Bot{
		token:     token,
		baseURL:   fmt.Sprintf("https://api.telegram.org/bot%v/", token),
		client:    &http.Client{Timeout: 30 * time.Second},
		messageCh: make(chan *Message),
	}
}

func (b *Bot) Handler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer w.WriteHeader(http.StatusOK)

		var u Update
		_ = json.NewDecoder(r.Body).Decode(&u)
		b.messageCh <- &u.Payload
	}
}

func (b *Bot) Messages() <-chan *Message {
	return b.messageCh
}

// SetWebhook assigns bot's webhook url with the given url.
func (b *Bot) SetWebhook(webhook string) error {
	params := url.Values{}
	params.Set("url", webhook)

	var r response
	err := b.sendCommand(nil, "setWebhook", params, &r)
	if err != nil {
		return err
	}

	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

// SendMessage sends text message to the recipient. Callers can send plain
// text or markdown messages by setting mode parameter.
func (b *Bot) SendMessage(recipient int64, message string, opts ...SendOption) (Message, error) {
	const method = "sendMessage"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("text", message)
	mapSendOptions(&params, opts...)

	var r struct {
		response
		Message Message `json:"result"`
	}
	err := b.sendCommand(nil, "sendMessage", params, &r)
	if err != nil {
		return r.Message, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

func (b *Bot) forwardMessage(recipient User, message Message) (Message, error) {
	panic("not implemented yet")
}

// SendPhoto sends given photo to recipient. Only remote URLs are supported for now.
// A trivial example is:
//
//  b := bot.New("your-token-here")
//  photo := bot.Photo{URL: "http://i.imgur.com/6S9naG6.png"}
//  err := b.SendPhoto(recipient, photo, "sample image", nil)
func (b *Bot) SendPhoto(recipient int64, photo Photo, opts ...SendOption) (Message, error) {
	const method = "sendPhoto"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("caption", photo.Caption)

	mapSendOptions(&params, opts...)
	var r struct {
		response
		Message Message `json:"result"`
	}

	var err error
	if photo.Exists() {
		params.Set("photo", photo.FileID)
		err = b.sendCommand(nil, method, params, &r)
	} else if photo.URL != "" {
		params.Set("photo", photo.URL)
		err = b.sendCommand(nil, method, params, &r)
	} else {
		err = b.sendFile(method, photo.File, "photo", params, &r)
	}

	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

func (b *Bot) sendFile(method string, f File, form string, params url.Values, v interface{}) error {
	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	part, err := w.CreateFormFile(form, f.Name)
	if err != nil {
		return err
	}

	_, err = io.Copy(part, f.Body)
	if err != nil {
		return err
	}

	for k, v := range params {
		w.WriteField(k, v[0])
	}

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

	resp, err := b.client.Post(b.baseURL+method, w.FormDataContentType(), &buf)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status code: %v", resp.StatusCode)
	}

	return json.NewDecoder(resp.Body).Decode(&v)
}

// SendAudio sends audio files, if you want Telegram clients to display
// them in the music player. audio must be in the .mp3 format and must not
// exceed 50 MB in size.
func (b *Bot) SendAudio(recipient int64, audio Audio, opts ...SendOption) (Message, error) {
	const method = "sendAudio"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("caption", audio.Caption)

	mapSendOptions(&params, opts...)
	var r struct {
		response
		Message Message `json:"result"`
	}

	var err error
	if audio.Exists() {
		params.Set("audio", audio.FileID)
		err = b.sendCommand(nil, method, params, &r)
	} else if audio.URL != "" {
		params.Set("audio", audio.URL)
		err = b.sendCommand(nil, method, params, &r)
	} else {
		err = b.sendFile(method, audio.File, "audio", params, &r)
	}

	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

// SendDocument sends general files. Documents must not exceed 50 MB in size.
func (b *Bot) sendDocument(recipient int64, document Document, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

//SendSticker sends stickers with .webp extensions.
func (b *Bot) sendSticker(recipient int64, sticker Sticker, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

// SendVideo sends video files. Telegram clients support mp4 videos (other
// formats may be sent as Document). Video files must not exceed 50 MB in size.
func (b *Bot) sendVideo(recipient int64, video Video, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

// SendVoice sends audio files, if you want Telegram clients to display
// the file as a playable voice message. For this to work, your audio must be
// in an .ogg file encoded with OPUS (other formats may be sent as Audio or
// Document). audio must not exceed 50 MB in size.
func (b *Bot) sendVoice(recipient int64, audio Audio, opts ...SendOption) (Message, error) {
	panic("not implemented yet")
}

// SendLocation sends location point on the map.
func (b *Bot) SendLocation(recipient int64, location Location, opts ...SendOption) (Message, error) {
	const method = "sendLocation"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(location.Long, 'f', -1, 64))

	mapSendOptions(&params, opts...)

	var r struct {
		response
		Message Message `json:"result"`
	}
	err := b.sendCommand(nil, method, params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.Message, nil
}

// SendVenue sends information about a venue.
func (b *Bot) SendVenue(recipient int64, venue Venue, opts ...SendOption) (Message, error) {
	const method = "sendVenue"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("latitude", strconv.FormatFloat(venue.Location.Lat, 'f', -1, 64))
	params.Set("longitude", strconv.FormatFloat(venue.Location.Long, 'f', -1, 64))
	params.Set("title", venue.Title)
	params.Set("address", venue.Address)

	mapSendOptions(&params, opts...)

	var r struct {
		response
		Message Message `json:"result"`
	}
	err := b.sendCommand(nil, method, params, &r)
	if err != nil {
		return Message{}, err
	}

	if !r.OK {
		return Message{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}
	return r.Message, nil
}

// SendChatAction broadcasts type of action to recipient, such as `typing`,
// `uploading a photo` etc.
func (b *Bot) SendChatAction(recipient int64, action Action) error {
	const method = "sendChatAction"
	params := url.Values{}
	params.Set("chat_id", strconv.FormatInt(recipient, 10))
	params.Set("action", string(action))

	var r response
	err := b.sendCommand(nil, method, params, &r)
	if err != nil {
		return err

	}
	if !r.OK {
		return fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return nil
}

// sendOptions configure a SendMessage call. sendOptions are set by the
// SendOption values passed to SendMessage.
type sendOptions struct {
	replyTo int64

	parseMode ParseMode

	disableWebPagePreview bool

	disableNotification bool

	replyMarkup ReplyMarkup
}

// SendOption configures how we configure the message to be sent.
type SendOption func(*sendOptions)

// WithParseMode returns a SendOption which sets the message format, such as
// HTML, Markdown etc.
func WithParseMode(mode ParseMode) SendOption {
	return func(o *sendOptions) {
		o.parseMode = mode
	}
}

// WithReplyTo returns a SendOption which sets the message to be replied to.
func WithReplyTo(to int64) SendOption {
	return func(o *sendOptions) {
		o.replyTo = to
	}
}

// WithReplyMarkup returns a SendOption which configures a custom keyboard for
// the sent message.
func WithReplyMarkup(markup ReplyMarkup) SendOption {
	return func(o *sendOptions) {
		o.replyMarkup = markup
	}
}

// WithDisableWebPagePreview returns a SendOption which disables webpage
// previews if the message contains a link.
func WithDisableWebPagePreview(disable bool) SendOption {
	return func(o *sendOptions) {
		o.disableWebPagePreview = disable
	}
}

func WithDisableNotification(disable bool) SendOption {
	return func(o *sendOptions) {
		o.disableNotification = disable
	}
}

func (b *Bot) GetFile(fileID string) (File, error) {
	params := url.Values{}
	params.Set("file_id", fileID)

	var r struct {
		response
		File File `json:"result"`
	}
	err := b.sendCommand(nil, "getFile", params, &r)
	if err != nil {
		return File{}, err
	}

	if !r.OK {
		return File{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.File, nil
}

func (b *Bot) GetFileDownloadURL(fileID string) (string, error) {
	f, err := b.GetFile(fileID)
	if err != nil {
		return "", err
	}

	u := "https://api.telegram.org/file/bot" + b.token + "/" + f.FilePath
	return u, nil
}

func (b *Bot) sendCommand(ctx context.Context, method string, params url.Values, v interface{}) error {
	req, err := http.NewRequest("POST", b.baseURL+method, strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}

	if ctx == nil {
		ctx = context.Background()
	}

	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := b.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status code: %v", resp.StatusCode)
	}
	return json.NewDecoder(resp.Body).Decode(&v)
}

func (b *Bot) getMe() (User, error) {
	var r struct {
		response
		User User `json:"result"`
	}
	err := b.sendCommand(nil, "getMe", url.Values{}, &r)
	if err != nil {
		return User{}, err
	}

	if !r.OK {
		return User{}, fmt.Errorf("%v (%v)", r.Desc, r.ErrCode)
	}

	return r.User, nil
}

func mapSendOptions(m *url.Values, opts ...SendOption) {
	var o sendOptions
	for _, opt := range opts {
		if opt != nil {
			opt(&o)
		}
	}

	if o.replyTo != 0 {
		m.Set("reply_to_message_id", strconv.FormatInt(o.replyTo, 10))
	}

	if o.disableWebPagePreview {
		m.Set("disable_web_page_preview", "true")
	}

	if o.disableNotification {
		m.Set("disable_notification", "true")
	}

	if o.parseMode != ModeNone {
		m.Set("parse_mode", string(o.parseMode))
	}

	if o.replyMarkup.Keyboard != nil {
		kb, _ := json.Marshal(o.replyMarkup)
		m.Set("reply_markup", string(kb))
	}
}

// response is a common response structure.
type response struct {
	OK      bool   `json:"ok"`
	Desc    string `json:"description"`
	ErrCode int    `json:"error_code"`
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
)

type CommandHandlerFunc func(conn *websocket.Conn, message map[string]interface{})

var (
	port     = flag.Int("port", 8080, "Port the server listens on")
	upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}
	commandHandlers = map[string]CommandHandlerFunc{
		"BROADCAST": commandBroadcast,
	}
)

func main() {
	http.HandleFunc("/api/ws", handleWebSocket)
	log.Fatal("ListenAndServe:", http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	for {
		var rawMessage interface{}
		if err := conn.ReadJSON(&rawMessage); err != nil {
			log.Printf("Read error: %v\n", err)
			conn.Close()
			return
		}

		messageObject, ok := rawMessage.(map[string]interface{})
		if !ok {
			errorMessage := "Message is not a JSON object"
			log.Println(errorMessage)
			sendErrorMessage(conn, errorMessage)
			conn.Close()
			return
		}

		command, ok := messageObject["command"].(string)
		if !ok {
			errorMessage := "Message is lacking a command property"
			log.Println(errorMessage)
			sendErrorMessage(conn, errorMessage)
			conn.Close()
			return
		}

		log.Printf("Received command: %v\n", command)
		if commandHandler, ok := commandHandlers[command]; ok {
			commandHandler(conn, messageObject)
		} else {
			errorMessage := fmt.Sprintf("Unknown command: %v", command)
			log.Printf(errorMessage)
			sendErrorMessage(conn, errorMessage)
			conn.Close()
			return
		}
	}
}

func commandBroadcast(conn *websocket.Conn, message map[string]interface{}) {
	fmt.Println("broadacast!")
}

func sendErrorMessage(conn *websocket.Conn, message string) {
	conn.WriteJSON(struct {
		Message string `json:"message"`
	}{message})
	conn.Close()
}

func sendErrorMessageAndCode(conn *websocket.Conn, message string, errorCode int) {
	conn.WriteJSON(struct {
		message string `json:"message"`
		code    int    `json:"code"`
	}{
		message: message,
		code:    errorCode,
	})
	conn.Close()
}
<commit_msg>Make struct members public<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
)

type CommandHandlerFunc func(conn *websocket.Conn, message map[string]interface{})

var (
	port     = flag.Int("port", 8080, "Port the server listens on")
	upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
	}
	commandHandlers = map[string]CommandHandlerFunc{
		"BROADCAST": commandBroadcast,
	}
)

func main() {
	http.HandleFunc("/api/ws", handleWebSocket)
	log.Fatal("ListenAndServe:", http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	for {
		var rawMessage interface{}
		if err := conn.ReadJSON(&rawMessage); err != nil {
			log.Printf("Read error: %v\n", err)
			conn.Close()
			return
		}

		messageObject, ok := rawMessage.(map[string]interface{})
		if !ok {
			errorMessage := "Message is not a JSON object"
			log.Println(errorMessage)
			sendErrorMessage(conn, errorMessage)
			conn.Close()
			return
		}

		command, ok := messageObject["command"].(string)
		if !ok {
			errorMessage := "Message is lacking a command property"
			log.Println(errorMessage)
			sendErrorMessage(conn, errorMessage)
			conn.Close()
			return
		}

		log.Printf("Received command: %v\n", command)
		if commandHandler, ok := commandHandlers[command]; ok {
			commandHandler(conn, messageObject)
		} else {
			errorMessage := fmt.Sprintf("Unknown command: %v", command)
			log.Printf(errorMessage)
			sendErrorMessage(conn, errorMessage)
			conn.Close()
			return
		}
	}
}

func commandBroadcast(conn *websocket.Conn, message map[string]interface{}) {
	fmt.Println("broadacast!")
}

func sendErrorMessage(conn *websocket.Conn, message string) {
	conn.WriteJSON(struct {
		Message string `json:"message"`
	}{message})
	conn.Close()
}

func sendErrorMessageAndCode(conn *websocket.Conn, message string, errorCode int) {
	conn.WriteJSON(struct {
		Message string `json:"message"`
		Code    int    `json:"code"`
	}{
		message: message,
		code:    errorCode,
	})
	conn.Close()
}
<|endoftext|>


<commit_before>// A simple semaphore primitive
//
// Usage:
//    sem := New(3)
//    for i := 0; i < 10; i++ {
//    	sem.Wait()
//    	go func() {
//    		time.Sleep(time.Second)
//    		sem.Signal()
//    	}()
//    }
package semaphore

type Semaphore chan struct{}

// Increments the `Semaphore` by 1
func (s Semaphore) Signal() {
	s <- struct{}{}
}

// Decrements the `Semaphore` by 1
func (s Semaphore) Wait() {
	<-s
}

// New returns an instance of a `Semaphore`:
//    sem := New(10)
func New(size int) Semaphore {
	sem := make(Semaphore, size)
	for _ = range make([]struct{}, size) {
		sem <- struct{}{}
	}
	return sem
}
<commit_msg>Just use the struct{}{} from the slice<commit_after>// A simple semaphore primitive
//
// Usage:
//    sem := New(3)
//    for i := 0; i < 10; i++ {
//    	sem.Wait()
//    	go func() {
//    		time.Sleep(time.Second)
//    		sem.Signal()
//    	}()
//    }
package semaphore

type Semaphore chan struct{}

// Increments the `Semaphore` by 1
func (s Semaphore) Signal() {
	s <- struct{}{}
}

// Decrements the `Semaphore` by 1
func (s Semaphore) Wait() {
	<-s
}

// New returns an instance of a `Semaphore`:
//    sem := New(10)
func New(size int) Semaphore {
	sem := make(Semaphore, size)
	for _, s := range make([]struct{}, size) {
		sem <- s
	}
	return sem
}
<|endoftext|>


<commit_before>package TagTest

import "fmt"

func Tag() string {
	return "Tagged v0.0.0"
}

func init() {
	fmt.Println(Tag())
}
<commit_msg>Release v0.0.1.<commit_after>package TagTest

import "fmt"

func Tag() string {
	return "Tagged v0.0.1"
}

func init() {
	fmt.Println(Tag())
}
<|endoftext|>


<commit_before>package solr

import (
	"fmt"
	"math"
	"net/url"
)

type Document map[string]interface{}

// Has check if a key exist in document
func (d Document) Has(k string) bool {
	_, ok := d[k]
	return ok
}

// Get returns value of a key
func (d Document) Get(k string) interface{} {
	v, _ := d[k]
	return v
}

// Set add a key/value to document
func (d Document) Set(k string, v interface{}) {
	d[k] = v
}

type Collection struct {
	Docs     []Document
	Start    int
	NumFound int
}

type SolrResult struct {
	Status       int         // status quick access to status
	Results      *Collection // results parsed documents, basically response object

	ResponseHeader map[string]interface{}
	FacetCounts map[string]interface{}
	Highlighting map[string]interface{}
	Error        map[string]interface{}

	// grouped for grouping result
	// if grouping Results will be empty
	Grouped map[string]interface{}
}

type SolrInterface struct {
	conn *Connection
}

func NewSolrInterface(solrUrl string) (*SolrInterface, error) {
	c, err := NewConnection(solrUrl)
	if err != nil {
		return nil, err
	}
	return &SolrInterface{conn: c}, nil
}

func (si *SolrInterface) SetBasicAuth(username, password string) error {
	if si.conn == nil {
		return fmt.Errorf("You need to a connection to be able to set basic auth")
	}
	si.conn.SetBasicAuth(username, password)
	return nil
}

func (si *SolrInterface) Search(q *Query) *Search {
	s := NewSearch(si.conn, q)

	return s
}

// makeAddChunks splits the documents into chunks. If chunk_size is less than one it will be default to 100
func makeAddChunks(docs []Document, chunk_size int) []map[string]interface{} {
	if chunk_size < 1 {
		chunk_size = 100
	}
	docs_len := len(docs)
	num_chunk := int(math.Ceil(float64(docs_len) / float64(chunk_size)))
	doc_counter := 0
	chunks := make([]map[string]interface{}, num_chunk)
	for i := 0; i < num_chunk; i++ {
		add := make([]Document, 0, chunk_size)
		for j := 0; j < chunk_size; j++ {
			if doc_counter >= docs_len {
				break
			}
			add = append(add, docs[doc_counter])
			doc_counter++
		}
		chunks[i] = map[string]interface{}{"add": add}
	}
	return chunks
}

// Add will insert documents in batch of chunk_size. success is false as long as one chunk failed.
// The result in UpdateResponse is summery of response from all chunks
// with key chunk_%d
func (si *SolrInterface) Add(docs []Document, chunk_size int, params *url.Values) (*UpdateResponse, error) {
	result := &UpdateResponse{Success: true}
	responses := map[string]interface{}{}
	chunks := makeAddChunks(docs, chunk_size)

	for i := 0; i < len(chunks); i++ {
		res, err := si.Update(chunks[i], params)
		if err != nil {
			return nil, err
		}
		result.Success = result.Success && res.Success
		responses[fmt.Sprintf("chunk_%d", i+1)] = map[string]interface{}{
			"result":  res.Result,
			"success": res.Success,
			"total":   len(chunks[i]["add"].([]Document))}
	}
	result.Result = responses
	return result, nil
}

// Delete take data of type map and optional params which can use to specify addition parameters such as commit=true .
// Only one delete statement is supported, ie data can be { "id":"ID" } .
// If you want to delete more docs use { "query":"QUERY" } .
// Extra params can specify in params or in data such as { "query":"QUERY", "commitWithin":"500" }
func (si *SolrInterface) Delete(data map[string]interface{}, params *url.Values) (*UpdateResponse, error) {
	message := map[string]interface{}{"delete": data}
	return si.Update(message, params)
}

// DeleteAll will remove all documents and commit
func (si *SolrInterface) DeleteAll() (*UpdateResponse, error) {
	params := &url.Values{}
	params.Add("commit", "true")
	return si.Delete(map[string]interface{}{"query": "*:*"}, params)
}

// Update take data of type map and optional params which can use to specify addition parameters such as commit=true
func (si *SolrInterface) Update(data map[string]interface{}, params *url.Values) (*UpdateResponse, error) {
	if si.conn == nil {
		return nil, fmt.Errorf("No connection found for making request to solr")
	}
	return si.conn.Update(data, params)
}

func (si *SolrInterface) Commit() (*UpdateResponse, error) {
	params := &url.Values{}
	params.Add("commit", "true")
	return si.Update(map[string]interface{}{}, params)
}

func (si *SolrInterface) Optimize(params *url.Values) (*UpdateResponse, error) {
	if params == nil {
		params = &url.Values{}
	}
	params.Set("optimize", "true")
	return si.Update(map[string]interface{}{}, params)
}

// Rollback rollbacks all add/deletes made to the index since the last commit.
// This should use with caution.
// See https://wiki.apache.org/solr/UpdateXmlMessages#A.22rollback.22
func (si *SolrInterface) Rollback() (*UpdateResponse, error) {
	return si.Update(map[string]interface{}{"rollback": map[string]interface{}{}}, nil)
}
<commit_msg>#11 Adding docstring<commit_after>package solr

import (
	"fmt"
	"math"
	"net/url"
)

type Document map[string]interface{}

// Has check if a key exist in document
func (d Document) Has(k string) bool {
	_, ok := d[k]
	return ok
}

// Get returns value of a key
func (d Document) Get(k string) interface{} {
	v, _ := d[k]
	return v
}

// Set add a key/value to document
func (d Document) Set(k string, v interface{}) {
	d[k] = v
}

type Collection struct {
	Docs     []Document
	Start    int
	NumFound int
}

type SolrResult struct {
	Status       int         // status quick access to status
	Results      *Collection // results parsed documents, basically response object

	ResponseHeader map[string]interface{}
	FacetCounts map[string]interface{}
	Highlighting map[string]interface{}
	Error        map[string]interface{}

	// grouped for grouping result
	// if grouping Results will be empty
	Grouped map[string]interface{}
}

type SolrInterface struct {
	conn *Connection
}

func NewSolrInterface(solrUrl string) (*SolrInterface, error) {
	c, err := NewConnection(solrUrl)
	if err != nil {
		return nil, err
	}
	return &SolrInterface{conn: c}, nil
}

// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
// See http://golang.org/pkg/net/http/#Request.SetBasicAuth
func (si *SolrInterface) SetBasicAuth(username, password string) error {
	if si.conn == nil {
		return fmt.Errorf("You need to a connection to be able to set basic auth")
	}
	si.conn.SetBasicAuth(username, password)
	return nil
}

func (si *SolrInterface) Search(q *Query) *Search {
	s := NewSearch(si.conn, q)

	return s
}

// makeAddChunks splits the documents into chunks. If chunk_size is less than one it will be default to 100
func makeAddChunks(docs []Document, chunk_size int) []map[string]interface{} {
	if chunk_size < 1 {
		chunk_size = 100
	}
	docs_len := len(docs)
	num_chunk := int(math.Ceil(float64(docs_len) / float64(chunk_size)))
	doc_counter := 0
	chunks := make([]map[string]interface{}, num_chunk)
	for i := 0; i < num_chunk; i++ {
		add := make([]Document, 0, chunk_size)
		for j := 0; j < chunk_size; j++ {
			if doc_counter >= docs_len {
				break
			}
			add = append(add, docs[doc_counter])
			doc_counter++
		}
		chunks[i] = map[string]interface{}{"add": add}
	}
	return chunks
}

// Add will insert documents in batch of chunk_size. success is false as long as one chunk failed.
// The result in UpdateResponse is summery of response from all chunks
// with key chunk_%d
func (si *SolrInterface) Add(docs []Document, chunk_size int, params *url.Values) (*UpdateResponse, error) {
	result := &UpdateResponse{Success: true}
	responses := map[string]interface{}{}
	chunks := makeAddChunks(docs, chunk_size)

	for i := 0; i < len(chunks); i++ {
		res, err := si.Update(chunks[i], params)
		if err != nil {
			return nil, err
		}
		result.Success = result.Success && res.Success
		responses[fmt.Sprintf("chunk_%d", i+1)] = map[string]interface{}{
			"result":  res.Result,
			"success": res.Success,
			"total":   len(chunks[i]["add"].([]Document))}
	}
	result.Result = responses
	return result, nil
}

// Delete take data of type map and optional params which can use to specify addition parameters such as commit=true .
// Only one delete statement is supported, ie data can be { "id":"ID" } .
// If you want to delete more docs use { "query":"QUERY" } .
// Extra params can specify in params or in data such as { "query":"QUERY", "commitWithin":"500" }
func (si *SolrInterface) Delete(data map[string]interface{}, params *url.Values) (*UpdateResponse, error) {
	message := map[string]interface{}{"delete": data}
	return si.Update(message, params)
}

// DeleteAll will remove all documents and commit
func (si *SolrInterface) DeleteAll() (*UpdateResponse, error) {
	params := &url.Values{}
	params.Add("commit", "true")
	return si.Delete(map[string]interface{}{"query": "*:*"}, params)
}

// Update take data of type map and optional params which can use to specify addition parameters such as commit=true
func (si *SolrInterface) Update(data map[string]interface{}, params *url.Values) (*UpdateResponse, error) {
	if si.conn == nil {
		return nil, fmt.Errorf("No connection found for making request to solr")
	}
	return si.conn.Update(data, params)
}

func (si *SolrInterface) Commit() (*UpdateResponse, error) {
	params := &url.Values{}
	params.Add("commit", "true")
	return si.Update(map[string]interface{}{}, params)
}

func (si *SolrInterface) Optimize(params *url.Values) (*UpdateResponse, error) {
	if params == nil {
		params = &url.Values{}
	}
	params.Set("optimize", "true")
	return si.Update(map[string]interface{}{}, params)
}

// Rollback rollbacks all add/deletes made to the index since the last commit.
// This should use with caution.
// See https://wiki.apache.org/solr/UpdateXmlMessages#A.22rollback.22
func (si *SolrInterface) Rollback() (*UpdateResponse, error) {
	return si.Update(map[string]interface{}{"rollback": map[string]interface{}{}}, nil)
}
<|endoftext|>


<commit_before>/*
Copyright 2013 CoreOS Inc.

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 etcd

import (
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	goetcd "github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
	golog "github.com/coreos/etcd/third_party/github.com/coreos/go-log/log"
	"github.com/coreos/etcd/third_party/github.com/goraft/raft"

	"github.com/coreos/etcd/config"
	ehttp "github.com/coreos/etcd/http"
	"github.com/coreos/etcd/log"
	"github.com/coreos/etcd/metrics"
	"github.com/coreos/etcd/server"
	"github.com/coreos/etcd/store"
)

type Etcd struct {
	Config       *config.Config     // etcd config
	Store        store.Store        // data store
	Registry     *server.Registry   // stores URL information for nodes
	Server       *server.Server     // http server, runs on 4001 by default
	PeerServer   *server.PeerServer // peer server, runs on 7001 by default
	listener     net.Listener       // Listener for Server
	peerListener net.Listener       // Listener for PeerServer
	readyC       chan bool          // To signal when server is ready to accept connections
	stopC        chan bool          // To signal when etcd is stopped with Stop() method
}

// New returns a new Etcd instance.
func New(c *config.Config) *Etcd {
	if c == nil {
		c = config.New()
	}
	return &Etcd{
		Config: c,
		readyC: make(chan bool),
		stopC:  make(chan bool),
	}
}

// Run etcd.
func (e *Etcd) Run() {
	// Enable options.
	if e.Config.VeryVeryVerbose {
		log.Verbose = true
		raft.SetLogLevel(raft.Trace)
		goetcd.SetLogger(
			golog.New(
				"go-etcd",
				false,
				golog.CombinedSink(
					os.Stdout,
					"[%s] %s %-9s | %s\n",
					[]string{"prefix", "time", "priority", "message"},
				),
			),
		)
	} else if e.Config.VeryVerbose {
		log.Verbose = true
		raft.SetLogLevel(raft.Debug)
	} else if e.Config.Verbose {
		log.Verbose = true
	}
	if e.Config.CPUProfileFile != "" {
		profile(e.Config.CPUProfileFile)
	}

	if e.Config.DataDir == "" {
		log.Fatal("The data dir was not set and could not be guessed from machine name")
	}

	// Create data directory if it doesn't already exist.
	if err := os.MkdirAll(e.Config.DataDir, 0744); err != nil {
		log.Fatalf("Unable to create path: %s", err)
	}

	// Warn people if they have an info file
	info := filepath.Join(e.Config.DataDir, "info")
	if _, err := os.Stat(info); err == nil {
		log.Warnf("All cached configuration is now ignored. The file %s can be removed.", info)
	}

	var mbName string
	if e.Config.Trace() {
		mbName = e.Config.MetricsBucketName()
		runtime.SetBlockProfileRate(1)
	}

	mb := metrics.NewBucket(mbName)

	if e.Config.GraphiteHost != "" {
		err := mb.Publish(e.Config.GraphiteHost)
		if err != nil {
			panic(err)
		}
	}

	// Retrieve CORS configuration
	corsInfo, err := ehttp.NewCORSInfo(e.Config.CorsOrigins)
	if err != nil {
		log.Fatal("CORS:", err)
	}

	// Create etcd key-value store and registry.
	e.Store = store.New()
	e.Registry = server.NewRegistry(e.Store)

	// Create stats objects
	followersStats := server.NewRaftFollowersStats(e.Config.Name)
	serverStats := server.NewRaftServerStats(e.Config.Name)

	// Calculate all of our timeouts
	heartbeatInterval := time.Duration(e.Config.Peer.HeartbeatInterval) * time.Millisecond
	electionTimeout := time.Duration(e.Config.Peer.ElectionTimeout) * time.Millisecond
	dialTimeout := (3 * heartbeatInterval) + electionTimeout
	responseHeaderTimeout := (3 * heartbeatInterval) + electionTimeout

	// Create peer server
	psConfig := server.PeerServerConfig{
		Name:          e.Config.Name,
		Scheme:        e.Config.PeerTLSInfo().Scheme(),
		URL:           e.Config.Peer.Addr,
		SnapshotCount: e.Config.SnapshotCount,
		RetryTimes:    e.Config.MaxRetryAttempts,
		RetryInterval: e.Config.RetryInterval,
	}
	e.PeerServer = server.NewPeerServer(psConfig, e.Registry, e.Store, &mb, followersStats, serverStats)

	// Create raft transporter and server
	raftTransporter := server.NewTransporter(followersStats, serverStats, e.Registry, heartbeatInterval, dialTimeout, responseHeaderTimeout)
	if psConfig.Scheme == "https" {
		raftClientTLSConfig, err := e.Config.PeerTLSInfo().ClientConfig()
		if err != nil {
			log.Fatal("raft client TLS error: ", err)
		}
		raftTransporter.SetTLSConfig(*raftClientTLSConfig)
	}
	raftServer, err := raft.NewServer(e.Config.Name, e.Config.DataDir, raftTransporter, e.Store, e.PeerServer, "")
	if err != nil {
		log.Fatal(err)
	}
	raftServer.SetElectionTimeout(electionTimeout)
	raftServer.SetHeartbeatInterval(heartbeatInterval)
	e.PeerServer.SetRaftServer(raftServer)

	// Create etcd server
	e.Server = server.New(e.Config.Name, e.Config.Addr, e.PeerServer, e.Registry, e.Store, &mb)

	if e.Config.Trace() {
		e.Server.EnableTracing()
	}

	// An error string equivalent to net.errClosing for using with
	// http.Serve() during server shutdown. Need to re-declare
	// here because it is not exported by "net" package.
	const errClosing = "use of closed network connection"

	e.PeerServer.SetServer(e.Server)

	// Generating config could be slow.
	// Put it here to make listen happen immediately after peer-server starting.
	peerTLSConfig := server.TLSServerConfig(e.Config.PeerTLSInfo())
	etcdTLSConfig := server.TLSServerConfig(e.Config.EtcdTLSInfo())

	log.Infof("peer server [name %s, listen on %s, advertised url %s]", e.PeerServer.Config.Name, e.Config.Peer.BindAddr, e.PeerServer.Config.URL)
	e.peerListener = server.NewListener(psConfig.Scheme, e.Config.Peer.BindAddr, e.Config.PeerTLSInfo())

	peerServerClosed := make(chan bool)
	go func() {
		// Starting peer server should be followed close by listening on its port
		// If not, it may leave many requests unaccepted, or cannot receive heartbeat from the cluster.
		// One severe problem caused if failing receiving heartbeats is when the second node joins one-node cluster,
		// the cluster could be out of work as long as the two nodes cannot transfer messages.
		e.PeerServer.Start(e.Config.Snapshot, e.Config.Discovery, e.Config.Peers)
		log.Infof("peer server [name %s, listen on %s, advertised url %s]", e.PeerServer.Config.Name, e.Config.Peer.BindAddr, e.PeerServer.Config.URL)
		e.peerListener = server.NewListener(psConfig.Scheme, e.Config.Peer.BindAddr, e.Config.PeerTLSInfo())

		sHTTP := &ehttp.CORSHandler{e.PeerServer.HTTPHandler(), corsInfo}
		if err = http.Serve(e.peerListener, sHTTP); err != nil {
			if !strings.Contains(err.Error(), errClosing) {
				log.Fatal(err)
			}
		}
		close(peerServerClosed)
	}()

	log.Infof("etcd server [name %s, listen on %s, advertised url %s]", e.Server.Name, e.Config.BindAddr, e.Server.URL())
	e.listener = server.NewListener(e.Config.EtcdTLSInfo().Scheme(), e.Config.BindAddr, etcdTLSConfig)

	close(e.readyC) // etcd server is ready to accept connections, notify waiters.

	sHTTP := &ehttp.CORSHandler{e.Server.HTTPHandler(), corsInfo}
	if err = http.Serve(e.listener, sHTTP); err != nil {
		if !strings.Contains(err.Error(), errClosing) {
			log.Fatal(err)
		}
	}

	<-peerServerClosed
	close(e.stopC) // etcd instance is stopped, notify waiters.

	log.Infof("etcd instance is stopped [name %s]", e.Config.Name)
}

func (e *Etcd) Stop() {
	e.peerListener.Close()
	e.listener.Close()
}

func (e *Etcd) StopNotify() chan bool {
	return e.stopC
}

func (e *Etcd) ReadyNotify() chan bool {
	return e.readyC
}
<commit_msg>fix(main): stop PeerServer on Etcd.Stop()<commit_after>/*
Copyright 2013 CoreOS Inc.

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 etcd

import (
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	goetcd "github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
	golog "github.com/coreos/etcd/third_party/github.com/coreos/go-log/log"
	"github.com/coreos/etcd/third_party/github.com/goraft/raft"

	"github.com/coreos/etcd/config"
	ehttp "github.com/coreos/etcd/http"
	"github.com/coreos/etcd/log"
	"github.com/coreos/etcd/metrics"
	"github.com/coreos/etcd/server"
	"github.com/coreos/etcd/store"
)

type Etcd struct {
	Config       *config.Config     // etcd config
	Store        store.Store        // data store
	Registry     *server.Registry   // stores URL information for nodes
	Server       *server.Server     // http server, runs on 4001 by default
	PeerServer   *server.PeerServer // peer server, runs on 7001 by default
	listener     net.Listener       // Listener for Server
	peerListener net.Listener       // Listener for PeerServer
	readyC       chan bool          // To signal when server is ready to accept connections
	stopC        chan bool          // To signal when etcd is stopped with Stop() method
}

// New returns a new Etcd instance.
func New(c *config.Config) *Etcd {
	if c == nil {
		c = config.New()
	}
	return &Etcd{
		Config: c,
		readyC: make(chan bool),
		stopC:  make(chan bool),
	}
}

// Run etcd.
func (e *Etcd) Run() {
	// Enable options.
	if e.Config.VeryVeryVerbose {
		log.Verbose = true
		raft.SetLogLevel(raft.Trace)
		goetcd.SetLogger(
			golog.New(
				"go-etcd",
				false,
				golog.CombinedSink(
					os.Stdout,
					"[%s] %s %-9s | %s\n",
					[]string{"prefix", "time", "priority", "message"},
				),
			),
		)
	} else if e.Config.VeryVerbose {
		log.Verbose = true
		raft.SetLogLevel(raft.Debug)
	} else if e.Config.Verbose {
		log.Verbose = true
	}
	if e.Config.CPUProfileFile != "" {
		profile(e.Config.CPUProfileFile)
	}

	if e.Config.DataDir == "" {
		log.Fatal("The data dir was not set and could not be guessed from machine name")
	}

	// Create data directory if it doesn't already exist.
	if err := os.MkdirAll(e.Config.DataDir, 0744); err != nil {
		log.Fatalf("Unable to create path: %s", err)
	}

	// Warn people if they have an info file
	info := filepath.Join(e.Config.DataDir, "info")
	if _, err := os.Stat(info); err == nil {
		log.Warnf("All cached configuration is now ignored. The file %s can be removed.", info)
	}

	var mbName string
	if e.Config.Trace() {
		mbName = e.Config.MetricsBucketName()
		runtime.SetBlockProfileRate(1)
	}

	mb := metrics.NewBucket(mbName)

	if e.Config.GraphiteHost != "" {
		err := mb.Publish(e.Config.GraphiteHost)
		if err != nil {
			panic(err)
		}
	}

	// Retrieve CORS configuration
	corsInfo, err := ehttp.NewCORSInfo(e.Config.CorsOrigins)
	if err != nil {
		log.Fatal("CORS:", err)
	}

	// Create etcd key-value store and registry.
	e.Store = store.New()
	e.Registry = server.NewRegistry(e.Store)

	// Create stats objects
	followersStats := server.NewRaftFollowersStats(e.Config.Name)
	serverStats := server.NewRaftServerStats(e.Config.Name)

	// Calculate all of our timeouts
	heartbeatInterval := time.Duration(e.Config.Peer.HeartbeatInterval) * time.Millisecond
	electionTimeout := time.Duration(e.Config.Peer.ElectionTimeout) * time.Millisecond
	dialTimeout := (3 * heartbeatInterval) + electionTimeout
	responseHeaderTimeout := (3 * heartbeatInterval) + electionTimeout

	// Create peer server
	psConfig := server.PeerServerConfig{
		Name:          e.Config.Name,
		Scheme:        e.Config.PeerTLSInfo().Scheme(),
		URL:           e.Config.Peer.Addr,
		SnapshotCount: e.Config.SnapshotCount,
		RetryTimes:    e.Config.MaxRetryAttempts,
		RetryInterval: e.Config.RetryInterval,
	}
	e.PeerServer = server.NewPeerServer(psConfig, e.Registry, e.Store, &mb, followersStats, serverStats)

	// Create raft transporter and server
	raftTransporter := server.NewTransporter(followersStats, serverStats, e.Registry, heartbeatInterval, dialTimeout, responseHeaderTimeout)
	if psConfig.Scheme == "https" {
		raftClientTLSConfig, err := e.Config.PeerTLSInfo().ClientConfig()
		if err != nil {
			log.Fatal("raft client TLS error: ", err)
		}
		raftTransporter.SetTLSConfig(*raftClientTLSConfig)
	}
	raftServer, err := raft.NewServer(e.Config.Name, e.Config.DataDir, raftTransporter, e.Store, e.PeerServer, "")
	if err != nil {
		log.Fatal(err)
	}
	raftServer.SetElectionTimeout(electionTimeout)
	raftServer.SetHeartbeatInterval(heartbeatInterval)
	e.PeerServer.SetRaftServer(raftServer)

	// Create etcd server
	e.Server = server.New(e.Config.Name, e.Config.Addr, e.PeerServer, e.Registry, e.Store, &mb)

	if e.Config.Trace() {
		e.Server.EnableTracing()
	}

	// An error string equivalent to net.errClosing for using with
	// http.Serve() during server shutdown. Need to re-declare
	// here because it is not exported by "net" package.
	const errClosing = "use of closed network connection"

	e.PeerServer.SetServer(e.Server)

	// Generating config could be slow.
	// Put it here to make listen happen immediately after peer-server starting.
	peerTLSConfig := server.TLSServerConfig(e.Config.PeerTLSInfo())
	etcdTLSConfig := server.TLSServerConfig(e.Config.EtcdTLSInfo())

	log.Infof("peer server [name %s, listen on %s, advertised url %s]", e.PeerServer.Config.Name, e.Config.Peer.BindAddr, e.PeerServer.Config.URL)
	e.peerListener = server.NewListener(psConfig.Scheme, e.Config.Peer.BindAddr, e.Config.PeerTLSInfo())

	peerServerClosed := make(chan bool)
	go func() {
		// Starting peer server should be followed close by listening on its port
		// If not, it may leave many requests unaccepted, or cannot receive heartbeat from the cluster.
		// One severe problem caused if failing receiving heartbeats is when the second node joins one-node cluster,
		// the cluster could be out of work as long as the two nodes cannot transfer messages.
		e.PeerServer.Start(e.Config.Snapshot, e.Config.Discovery, e.Config.Peers)
		log.Infof("peer server [name %s, listen on %s, advertised url %s]", e.PeerServer.Config.Name, e.Config.Peer.BindAddr, e.PeerServer.Config.URL)
		e.peerListener = server.NewListener(psConfig.Scheme, e.Config.Peer.BindAddr, e.Config.PeerTLSInfo())

		sHTTP := &ehttp.CORSHandler{e.PeerServer.HTTPHandler(), corsInfo}
		if err = http.Serve(e.peerListener, sHTTP); err != nil {
			if !strings.Contains(err.Error(), errClosing) {
				log.Fatal(err)
			}
		}
		close(peerServerClosed)
	}()

	log.Infof("etcd server [name %s, listen on %s, advertised url %s]", e.Server.Name, e.Config.BindAddr, e.Server.URL())
	e.listener = server.NewListener(e.Config.EtcdTLSInfo().Scheme(), e.Config.BindAddr, etcdTLSConfig)

	close(e.readyC) // etcd server is ready to accept connections, notify waiters.

	sHTTP := &ehttp.CORSHandler{e.Server.HTTPHandler(), corsInfo}
	if err = http.Serve(e.listener, sHTTP); err != nil {
		if !strings.Contains(err.Error(), errClosing) {
			log.Fatal(err)
		}
	}

	<-peerServerClosed
	close(e.stopC) // etcd instance is stopped, notify waiters.

	log.Infof("etcd instance is stopped [name %s]", e.Config.Name)
}

func (e *Etcd) Stop() {
	e.PeerServer.Stop()
	e.peerListener.Close()
	e.listener.Close()
}

func (e *Etcd) StopNotify() chan bool {
	return e.stopC
}

func (e *Etcd) ReadyNotify() chan bool {
	return e.readyC
}
<|endoftext|>


<commit_before>package conf

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"strings"
)

type Config struct {
	// RootURL gets the URL of the root of the site (e.g., http://localhost/gomp).
	RootURL string `json:"root_url"`

	// RootURLPath gets just the path portion of the RootUrl value,
	// without any trailing slashes.
	RootURLPath string `json:"-"`

	// Port gets the port number under which the site is being hosted.
	Port int `json:"port"`

	// DataPath gets the path (full or relative) under which to store the database
	// and other runtime date (e.g., uploaded images).
	DataPath string `json:"data_path"`

	// IsDevelopment defines whether to run the application in "development mode".
	// Development mode turns on additional features, such as logging, that may
	// not be desirable in a production environment.
	IsDevelopment bool `json:"is_development"`

	// SecretKey is used to keep data safe.
	SecretKey string `json:"secret_key"`
}

// Load reads the configuration file from the specified path
func Load(path string) *Config {
	c := Config{
		RootURL:       "http://localhost:4000/",
		Port:          4000,
		DataPath:      "data",
		IsDevelopment: false,
		SecretKey:     "Secret123",
	}

	file, err := ioutil.ReadFile(path)
	if err == nil {
		err = json.Unmarshal(file, &c)
		if err != nil {
			log.Fatalf("Failed to marshal configuration settings. Error = %s", err)
		}
	} else if !os.IsNotExist(err) {
		log.Fatalf("Failed to read in app.json. Error = %s", err)
	}

	// Check if root url has a sub-path
	url, err := url.Parse(c.RootURL)
	if err != nil {
		log.Fatal("Invalid root_url")
	}
	c.RootURLPath = strings.TrimSuffix(url.Path, "/")

	return &c
}
<commit_msg>Default to the $PORT environment variable.<commit_after>package conf

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"strconv"
	"strings"
)

type Config struct {
	// RootURL gets the URL of the root of the site (e.g., http://localhost/gomp).
	RootURL string `json:"root_url"`

	// RootURLPath gets just the path portion of the RootUrl value,
	// without any trailing slashes.
	RootURLPath string `json:"-"`

	// Port gets the port number under which the site is being hosted.
	Port int `json:"port"`

	// DataPath gets the path (full or relative) under which to store the database
	// and other runtime date (e.g., uploaded images).
	DataPath string `json:"data_path"`

	// IsDevelopment defines whether to run the application in "development mode".
	// Development mode turns on additional features, such as logging, that may
	// not be desirable in a production environment.
	IsDevelopment bool `json:"is_development"`

	// SecretKey is used to keep data safe.
	SecretKey string `json:"secret_key"`
}

// Load reads the configuration file from the specified path
func Load(path string) *Config {
	port, _ := strconv.Atoi(os.Getenv("PORT"))
	c := Config{
		RootURL:       "http://localhost:4000/",
		Port:          port,
		DataPath:      "data",
		IsDevelopment: false,
		SecretKey:     "Secret123",
	}

	file, err := ioutil.ReadFile(path)
	if err == nil {
		err = json.Unmarshal(file, &c)
		if err != nil {
			log.Fatalf("Failed to marshal configuration settings. Error = %s", err)
		}
	} else if !os.IsNotExist(err) {
		log.Fatalf("Failed to read in app.json. Error = %s", err)
	}

	// Check if root url has a sub-path
	url, err := url.Parse(c.RootURL)
	if err != nil {
		log.Fatal("Invalid root_url")
	}
	c.RootURLPath = strings.TrimSuffix(url.Path, "/")

	return &c
}
<|endoftext|>


<commit_before>package routing

import (
	"bytes"
	"encoding/json"
	"html"
	"html/template"
	"io"
	"reflect"
	"time"

	"github.com/davecgh/go-spew/spew"
)

// Type APIStruct holds data needed to render the HTML document.
type API struct {
	Name    string
	Version string
	BaseURL string
	Router  *Router
}

func WriteDocs(
	out io.Writer,
	name string,
	version string,
	baseURL string,
	router *Router,
) error {
	return tmpl.ExecuteTemplate(out, "main", &API{name, version, baseURL, router})
}

var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
	"EscapeHTML": html.EscapeString,
	"MarshalJSON": func(v interface{}) (string, error) {
		b, err := json.Marshal(v)
		if err != nil {
			return "", err
		}

		var buf bytes.Buffer
		if err := json.Indent(&buf, b, "", "\t"); err != nil {
			return "", err
		}

		return buf.String(), nil
	},
	"PrettyPrint": func(v interface{}) string {
		spew.Config.Indent = "\t"
		return spew.Sdump(v)
	},
	"HasField": func(v interface{}, name string) bool {
		rv := reflect.ValueOf(v)
		if rv.Kind() == reflect.Ptr {
			rv = rv.Elem()
		}
		if rv.Kind() != reflect.Struct {
			return false
		}
		return rv.FieldByName(name).IsValid()
	},
	"Now": func() string {
		return time.Now().Format(time.RFC850)
	},
}).Parse(`
{{define "main"}}
<!DOCTYPE html>

	<meta charset="UTF-8">
	<link href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" rel="stylesheet">

	<title>{{.Name}} API documentation</title>

{{.Name}} docs

			{{.BaseURL}}

			v{{.Version}}

Index

Routes

			{{range $tag := .Router.Tags}}
				{{if $tag}}
					{{$tag}}
				{{else}}
					Untagged
				{{end}}
				{{range $.Router.RoutesForTag $tag}}

							{{.Rex.String}}

				{{end}}
			{{end}}

			{{range $tag := .Router.Tags}}
				{{if $tag}}

{{$tag}}

				{{else}}

Untagged

				{{end}}
				{{range $.Router.RoutesForTag $tag}}

								{{.Method}} {{.Rex.String}}

							{{if .Description}}

{{.Description}}

							{{end}}
							{{if .RequestBody}}

Request body

								{{template "argument" .RequestBody}}
							{{end}}
							{{if .PathArguments}}

Path arguments

								{{range .PathArguments}}
									{{template "argument" .}}
								{{end}}
							{{end}}
							{{if .QueryParameters}}

Query parameters

								{{range .QueryParameters}}
									{{template "argument" .}}
								{{end}}
							{{end}}
							{{if .Response}}

Response

								{{template "argument" .Response}}
							{{end}}
							{{if .Middleware}}

Middlewares

									{{range .Middleware}}

											{{.Name}}

											{{.Description}}

									{{end}}

							{{end}}

				{{end}}
			{{end}}

			Generated {{Now}}

{{end}}
{{define "argument"}}

		{{if .Name}}
			Name:

{{.Name}}

		{{end}}
		{{if HasField . "ContentType"}}
			Content type:

{{.ContentType}}

		{{end}}
		{{if .Example}}
			Example:

				JSON

{{MarshalJSON .Example}}

				Go type

{{PrettyPrint .Example}}

		{{end}}

{{end}}
`))
<commit_msg>docs: addd http method to route index.<commit_after>package routing

import (
	"bytes"
	"encoding/json"
	"html"
	"html/template"
	"io"
	"reflect"
	"time"

	"github.com/davecgh/go-spew/spew"
)

// Type APIStruct holds data needed to render the HTML document.
type API struct {
	Name    string
	Version string
	BaseURL string
	Router  *Router
}

func WriteDocs(
	out io.Writer,
	name string,
	version string,
	baseURL string,
	router *Router,
) error {
	return tmpl.ExecuteTemplate(out, "main", &API{name, version, baseURL, router})
}

var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
	"EscapeHTML": html.EscapeString,
	"MarshalJSON": func(v interface{}) (string, error) {
		b, err := json.Marshal(v)
		if err != nil {
			return "", err
		}

		var buf bytes.Buffer
		if err := json.Indent(&buf, b, "", "\t"); err != nil {
			return "", err
		}

		return buf.String(), nil
	},
	"PrettyPrint": func(v interface{}) string {
		spew.Config.Indent = "\t"
		return spew.Sdump(v)
	},
	"HasField": func(v interface{}, name string) bool {
		rv := reflect.ValueOf(v)
		if rv.Kind() == reflect.Ptr {
			rv = rv.Elem()
		}
		if rv.Kind() != reflect.Struct {
			return false
		}
		return rv.FieldByName(name).IsValid()
	},
	"Now": func() string {
		return time.Now().Format(time.RFC850)
	},
}).Parse(`
{{define "main"}}
<!DOCTYPE html>

	<meta charset="UTF-8">
	<link href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" rel="stylesheet">

	<title>{{.Name}} API documentation</title>

{{.Name}} docs

			{{.BaseURL}}

			v{{.Version}}

Index

Routes

			{{range $tag := .Router.Tags}}
				{{if $tag}}
					{{$tag}}
				{{else}}
					Untagged
				{{end}}
				{{range $.Router.RoutesForTag $tag}}

						{{.Method}} {{.Rex.String}}

				{{end}}
			{{end}}

			{{range $tag := .Router.Tags}}
				{{if $tag}}

{{$tag}}

				{{else}}

Untagged

				{{end}}
				{{range $.Router.RoutesForTag $tag}}

								{{.Method}} {{.Rex.String}}

							{{if .Description}}

{{.Description}}

							{{end}}
							{{if .RequestBody}}

Request body

								{{template "argument" .RequestBody}}
							{{end}}
							{{if .PathArguments}}

Path arguments

								{{range .PathArguments}}
									{{template "argument" .}}
								{{end}}
							{{end}}
							{{if .QueryParameters}}

Query parameters

								{{range .QueryParameters}}
									{{template "argument" .}}
								{{end}}
							{{end}}
							{{if .Response}}

Response

								{{template "argument" .Response}}
							{{end}}
							{{if .Middleware}}

Middlewares

									{{range .Middleware}}

											{{.Name}}

											{{.Description}}

									{{end}}

							{{end}}

				{{end}}
			{{end}}

			Generated {{Now}}

{{end}}
{{define "argument"}}

		{{if .Name}}
			Name:

{{.Name}}

		{{end}}
		{{if HasField . "ContentType"}}
			Content type:

{{.ContentType}}

		{{end}}
		{{if .Example}}
			Example:

				JSON

{{MarshalJSON .Example}}

				Go type

{{PrettyPrint .Example}}

		{{end}}

{{end}}
`))
<|endoftext|>


<commit_before>package textbelt

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
)

const (
	// Send text messages to the USA
	TextbeltAPIusa = "http://textbelt.com/text"

	// Send text messages to Canada
	TextbeltAPIcanada = "http://textbelt.com/canada"

	// Send text messages internationally
	TextbeltAPIinternational = "http://textbelt.com/intl"
)

type textbeltResponse struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

// Client is your entry point into the rest of the library.
type Client struct {
	apiURL string
}

// New creates a new client that will use http://textbelt.com to send text messages in the USA.
func New() *Client {
	return NewClientFromURL(TextbeltAPIusa)
}

// NewClientFromURL creates a new client using a url of your choosing.
func NewClientFromURL(apiURL string) *Client {
	return &Client{
		apiURL: apiURL,
	}
}

// Text sends a text message.
func (c *Client) Text(phoneNumber string, message string) error {
	data := url.Values{
		"number":  {phoneNumber},
		"message": {message},
	}
	response, err := http.PostForm(c.apiURL, data)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	responseText, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return err
	}

	i := textbeltResponse{}
	err = json.Unmarshal([]byte(responseText), &i)

	if !i.Success {
		return errors.New(i.Message)
	}
	return nil
}
<commit_msg>comment update<commit_after>package textbelt

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
)

const (
	// Send text messages to the USA
	TextbeltAPIusa = "http://textbelt.com/text"

	// Send text messages to Canada
	TextbeltAPIcanada = "http://textbelt.com/canada"

	// Send text messages internationally
	TextbeltAPIinternational = "http://textbelt.com/intl"
)

type textbeltResponse struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

// Client is your entry point into the rest of the library.
type Client struct {
	apiURL string
}

// New creates a new client that will use the USA version
// of http://textbelt.com to send text messages.
func New() *Client {
	return NewClientFromURL(TextbeltAPIusa)
}

// NewClientFromURL creates a new client using a url of your choosing.
func NewClientFromURL(apiURL string) *Client {
	return &Client{
		apiURL: apiURL,
	}
}

// Text sends a text message.
func (c *Client) Text(phoneNumber string, message string) error {
	data := url.Values{
		"number":  {phoneNumber},
		"message": {message},
	}
	response, err := http.PostForm(c.apiURL, data)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	responseText, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return err
	}

	i := textbeltResponse{}
	err = json.Unmarshal([]byte(responseText), &i)

	if !i.Success {
		return errors.New(i.Message)
	}
	return nil
}
<|endoftext|>


<commit_before>package instana_test

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/instana/golang-sensor"
	ot "github.com/opentracing/opentracing-go"
)

func TestBasicSpan(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	sp := tracer.StartSpan(op)
	sp.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	span := spans[0]

	assert.NotZero(t, span.SpanID, "Missing span ID for this span")
	assert.NotZero(t, span.TraceID, "Missing trace ID for this span")
	assert.NotZero(t, span.Timestamp, "Missing timestamp for this span")
	assert.NotNil(t, span.Duration, "Duration is nil")
	assert.Equal(t, "sdk", span.Name, "Missing sdk span name")
	assert.Equal(t, "test", span.Data.SDK.Name, "Missing span name")
	assert.Nil(t, span.Data.SDK.Custom.Tags, "Tags has an unexpected value")
	assert.Nil(t, span.Data.SDK.Custom.Baggage, "Baggage has an unexpected value")
}

func TestSpanHeritage(t *testing.T) {
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&instana.Options{}, recorder)

	parentSpan := tracer.StartSpan("parent")

	childSpan := tracer.StartSpan("child", ot.ChildOf(parentSpan.Context()))
	time.Sleep(2 * time.Millisecond)
	childSpan.Finish()

	time.Sleep(2 * time.Millisecond)
	parentSpan.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 2)
	cSpan := spans[0]
	pSpan := spans[1]

	assert.Equal(t, "child", cSpan.Data.SDK.Name, "Child span name doesn't compute")
	assert.Equal(t, "parent", pSpan.Data.SDK.Name, "Parent span name doesn't compute")

	// Parent should not have a parent
	assert.Nil(t, pSpan.ParentID, "ParentID shouldn't have a value")

	// Child must have parent ID set as parent
	assert.Equal(t, pSpan.SpanID, *cSpan.ParentID, "parentID doesn't match")

	// Must be root span
	assert.Equal(t, pSpan.TraceID, pSpan.SpanID, "not a root span")

	// Trace ID must be consistent across spans
	assert.Equal(t, cSpan.TraceID, pSpan.TraceID, "trace IDs don't match")

}

func TestSpanBaggage(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	sp := tracer.StartSpan(op)
	sp.SetBaggageItem("foo", "bar")
	sp.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	span := spans[0]

	assert.NotNil(t, span.Data.SDK.Custom.Baggage, "Missing Baggage")
}

func TestSpanTags(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	sp := tracer.StartSpan(op)
	sp.SetTag("foo", "bar")
	sp.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	span := spans[0]

	assert.NotNil(t, span.Data.SDK.Custom.Tags, "Missing Tags")
}
<commit_msg>Add tests to validate error reporting/logging<commit_after>package instana_test

import (
	"errors"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/instana/golang-sensor"
	ot "github.com/opentracing/opentracing-go"
	ext "github.com/opentracing/opentracing-go/ext"
	"github.com/opentracing/opentracing-go/log"
)

func TestBasicSpan(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	sp := tracer.StartSpan(op)
	sp.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	span := spans[0]

	assert.NotZero(t, span.SpanID, "Missing span ID for this span")
	assert.NotZero(t, span.TraceID, "Missing trace ID for this span")
	assert.NotZero(t, span.Timestamp, "Missing timestamp for this span")
	assert.NotNil(t, span.Duration, "Duration is nil")
	assert.Equal(t, "sdk", span.Name, "Missing sdk span name")
	assert.Equal(t, "test", span.Data.SDK.Name, "Missing span name")
	assert.Nil(t, span.Data.SDK.Custom.Tags, "Tags has an unexpected value")
	assert.Nil(t, span.Data.SDK.Custom.Baggage, "Baggage has an unexpected value")
}

func TestSpanHeritage(t *testing.T) {
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&instana.Options{}, recorder)

	parentSpan := tracer.StartSpan("parent")

	childSpan := tracer.StartSpan("child", ot.ChildOf(parentSpan.Context()))
	time.Sleep(2 * time.Millisecond)
	childSpan.Finish()

	time.Sleep(2 * time.Millisecond)
	parentSpan.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 2)
	cSpan := spans[0]
	pSpan := spans[1]

	assert.Equal(t, "child", cSpan.Data.SDK.Name, "Child span name doesn't compute")
	assert.Equal(t, "parent", pSpan.Data.SDK.Name, "Parent span name doesn't compute")

	// Parent should not have a parent
	assert.Nil(t, pSpan.ParentID, "ParentID shouldn't have a value")

	// Child must have parent ID set as parent
	assert.Equal(t, pSpan.SpanID, *cSpan.ParentID, "parentID doesn't match")

	// Must be root span
	assert.Equal(t, pSpan.TraceID, pSpan.SpanID, "not a root span")

	// Trace ID must be consistent across spans
	assert.Equal(t, cSpan.TraceID, pSpan.TraceID, "trace IDs don't match")

}

func TestSpanBaggage(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	sp := tracer.StartSpan(op)
	sp.SetBaggageItem("foo", "bar")
	sp.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	span := spans[0]

	assert.NotNil(t, span.Data.SDK.Custom.Baggage, "Missing Baggage")
}

func TestSpanTags(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	sp := tracer.StartSpan(op)
	sp.SetTag("foo", "bar")
	sp.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	span := spans[0]

	assert.NotNil(t, span.Data.SDK.Custom.Tags, "Missing Tags")
}

func TestSpanLogFields(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	span := tracer.StartSpan(op)
	span.LogFields(
		log.String("event", "soft error"),
		log.String("type", "cache timeout"),
		log.Int("waited.millis", 1500))
	span.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	firstSpan := spans[0]

	// j, _ := json.MarshalIndent(spans, "", "  ")
	// fmt.Printf("spans:", bytes.NewBuffer(j))

	logData := firstSpan.Data.SDK.Custom.Logs
	assert.NotNil(t, logData, "Missing logged fields")
	assert.Equal(t, 1, len(logData), "Unexpected log count")

	for _, v := range logData {
		assert.Equal(t, "soft error", v["event"], "Wrong or missing log")
		assert.Equal(t, "cache timeout", v["type"], "Wrong or missing log")
		assert.Equal(t, 1500, v["waited.millis"], "Wrong or missing log")
	}
}

func TestSpanLogKVs(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	span := tracer.StartSpan(op)
	span.LogKV(
		"event", "soft error",
		"type", "cache timeout",
		"waited.millis", 1500)
	span.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	firstSpan := spans[0]

	// j, _ := json.MarshalIndent(spans, "", "  ")
	// fmt.Printf("spans:", bytes.NewBuffer(j))

	logData := firstSpan.Data.SDK.Custom.Logs
	assert.NotNil(t, logData, "Missing logged fields")
	assert.Equal(t, 1, len(logData), "Unexpected log count")

	for _, v := range logData {
		assert.Equal(t, "soft error", v["event"], "Wrong or missing log")
		assert.Equal(t, "cache timeout", v["type"], "Wrong or missing log")
		assert.Equal(t, 1500, v["waited.millis"], "Wrong or missing log")
	}
}

func TestOTLogError(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	span := tracer.StartSpan(op)
	ext.Error.Set(span, true)
	span.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	firstSpan := spans[0]

	logData := firstSpan.Data.SDK.Custom.Logs
	tagData := firstSpan.Data.SDK.Custom.Tags
	assert.Equal(t, 1, len(tagData), "Unexpected log count")
	assert.Equal(t, 1, firstSpan.Ec, "Error count should be 1")

	for _, v := range logData {
		for sk, sv := range v {
			fmt.Print(v)
			assert.Equal(t, "error", sk, "Wrong or missing log")
			assert.Equal(t, "simulated error", sv, "Wrong or missing log")
		}
	}
}

func TestSpanErrorLogKV(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	span := tracer.StartSpan(op)
	span.LogKV("error", "simulated error")
	span.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	firstSpan := spans[0]

	logData := firstSpan.Data.SDK.Custom.Logs
	assert.NotNil(t, logData, "Missing logged fields")
	assert.Equal(t, 1, len(logData), "Unexpected log count")
	assert.Equal(t, 1, firstSpan.Ec, "Error count should be 1")

	for _, v := range logData {
		for sk, sv := range v {
			assert.Equal(t, "error", sk, "Wrong or missing log")
			assert.Equal(t, "simulated error", sv, "Wrong or missing log")
		}
	}
}

func TestSpanErrorLogFields(t *testing.T) {
	const op = "test"
	opts := instana.Options{LogLevel: instana.Debug}
	recorder := instana.NewTestRecorder()
	tracer := instana.NewTracerWithEverything(&opts, recorder)

	span := tracer.StartSpan(op)

	err := errors.New("simulated error")
	span.LogFields(log.Error(err), log.String("function", "TestspanErrorLogFields"))
	span.LogFields(log.Error(err), log.String("function", "TestspanErrorLogFields"))
	span.Finish()

	spans := recorder.GetSpans()
	assert.Equal(t, len(spans), 1)
	firstSpan := spans[0]

	logData := firstSpan.Data.SDK.Custom.Logs
	assert.Equal(t, 1, len(logData), "Unexpected tag count")
	assert.Equal(t, 2, firstSpan.Ec, "Error count should be 2")
}
<|endoftext|>


<commit_before>package goexcel

import (
	"bytes"
	"encoding/csv"
	"errors"
	"net/http"
	"reflect"
	"strings"

	"google.golang.org/appengine"
	"google.golang.org/appengine/log"

	"golang.org/x/net/context"

	"github.com/news-ai/tabulae/models"
)

func csvGetCustomFields(r *http.Request, c context.Context, numberOfColumns int, headers []string) map[string]bool {
	customFields := make(map[string]bool, len(headers))

	for x := 0; x < numberOfColumns; x++ {
		columnName := headers[x]
		if !customOrNative(columnName) {
			customFields[columnName] = true
		}
	}
	return customFields
}

func csvRowToContact(r *http.Request, c context.Context, singleRow []string, headers []string) (models.Contact, error) {
	var (
		contact       models.Contact
		employers     []int64
		pastEmployers []int64
		customFields  []models.CustomContactField
	)

	for x := 0; x < len(singleRow); x++ {
		columnName := headers[x]
		cellName := singleRow[x]
		rowToContact(r, c, columnName, cellName, &contact, &employers, &pastEmployers, &customFields)
	}

	contact.CustomFields = customFields
	contact.Employers = employers
	contact.PastEmployers = pastEmployers
	return contact, nil
}

func csvToContactList(r *http.Request, file []byte, headers []string) ([]models.Contact, map[string]bool, error) {
	c := appengine.NewContext(r)

	readerFile := bytes.NewReader(file)
	incomingRecords := csv.NewReader(readerFile)
	records, err := incomingRecords.ReadAll()
	if err != nil {
		log.Infof(c, "%v", err)
		return []models.Contact{}, map[string]bool{}, err
	}

	// Number of columns in sheet to compare
	numberOfColumns := len(records[0])
	if numberOfColumns != len(headers) {
		err := errors.New("Number of headers does not match the ones for the sheet")
		log.Infof(c, "%v", err)
		return []models.Contact{}, map[string]bool{}, err
	}

	// Loop through all the rows
	// Extract information
	emptyContact := models.Contact{}
	contacts := []models.Contact{}

	for i := 0; i < len(records); i++ {
		contact, err := csvRowToContact(r, c, records[i], headers)
		if err != nil {
			log.Infof(c, "%v", err)
			return []models.Contact{}, map[string]bool{}, err
		}

		// To get rid of empty contacts. We don't want to create empty contacts.
		if !reflect.DeepEqual(emptyContact, contact) {
			contacts = append(contacts, contact)
		}
	}

	// Get custom fields
	customFields := getCustomFields(r, c, numberOfColumns, headers)

	return contacts, customFields, nil
}

func csvFileToExcelHeader(r *http.Request, file []byte) ([]Column, error) {
	c := appengine.NewContext(r)

	readerFile := bytes.NewReader(file)
	incomingRecords := csv.NewReader(readerFile)
	// incomingRecords.LazyQuotes = true
	records, err := incomingRecords.ReadAll()
	if err != nil {
		log.Errorf(c, "%v", err)
		return []Column{}, err
	}

	numberOfRows := 15
	if len(records) < numberOfRows+1 {
		numberOfRows = len(records)
	}

	numberOfColumns := len(records[0])
	columns := make([]Column, numberOfColumns)

	for _, row := range records[0:numberOfRows] {
		for currentColumn, cell := range row {
			columns[currentColumn].Rows = append(columns[currentColumn].Rows, strings.Trim(cell, " "))
		}
	}

	return columns, nil
}
<commit_msg>Removing LazyQuotes: breaks build<commit_after>package goexcel

import (
	"bytes"
	"encoding/csv"
	"errors"
	"net/http"
	"reflect"
	"strings"

	"google.golang.org/appengine"
	"google.golang.org/appengine/log"

	"golang.org/x/net/context"

	"github.com/news-ai/tabulae/models"
)

func csvGetCustomFields(r *http.Request, c context.Context, numberOfColumns int, headers []string) map[string]bool {
	customFields := make(map[string]bool, len(headers))

	for x := 0; x < numberOfColumns; x++ {
		columnName := headers[x]
		if !customOrNative(columnName) {
			customFields[columnName] = true
		}
	}
	return customFields
}

func csvRowToContact(r *http.Request, c context.Context, singleRow []string, headers []string) (models.Contact, error) {
	var (
		contact       models.Contact
		employers     []int64
		pastEmployers []int64
		customFields  []models.CustomContactField
	)

	for x := 0; x < len(singleRow); x++ {
		columnName := headers[x]
		cellName := singleRow[x]
		rowToContact(r, c, columnName, cellName, &contact, &employers, &pastEmployers, &customFields)
	}

	contact.CustomFields = customFields
	contact.Employers = employers
	contact.PastEmployers = pastEmployers
	return contact, nil
}

func csvToContactList(r *http.Request, file []byte, headers []string) ([]models.Contact, map[string]bool, error) {
	c := appengine.NewContext(r)

	readerFile := bytes.NewReader(file)
	incomingRecords := csv.NewReader(readerFile)
	records, err := incomingRecords.ReadAll()
	if err != nil {
		log.Infof(c, "%v", err)
		return []models.Contact{}, map[string]bool{}, err
	}

	// Number of columns in sheet to compare
	numberOfColumns := len(records[0])
	if numberOfColumns != len(headers) {
		err := errors.New("Number of headers does not match the ones for the sheet")
		log.Infof(c, "%v", err)
		return []models.Contact{}, map[string]bool{}, err
	}

	// Loop through all the rows
	// Extract information
	emptyContact := models.Contact{}
	contacts := []models.Contact{}

	for i := 0; i < len(records); i++ {
		contact, err := csvRowToContact(r, c, records[i], headers)
		if err != nil {
			log.Infof(c, "%v", err)
			return []models.Contact{}, map[string]bool{}, err
		}

		// To get rid of empty contacts. We don't want to create empty contacts.
		if !reflect.DeepEqual(emptyContact, contact) {
			contacts = append(contacts, contact)
		}
	}

	// Get custom fields
	customFields := getCustomFields(r, c, numberOfColumns, headers)

	return contacts, customFields, nil
}

func csvFileToExcelHeader(r *http.Request, file []byte) ([]Column, error) {
	c := appengine.NewContext(r)

	readerFile := bytes.NewReader(file)
	incomingRecords := csv.NewReader(readerFile)
	records, err := incomingRecords.ReadAll()
	if err != nil {
		log.Errorf(c, "%v", err)
		return []Column{}, err
	}

	numberOfRows := 15
	if len(records) < numberOfRows+1 {
		numberOfRows = len(records)
	}

	numberOfColumns := len(records[0])
	columns := make([]Column, numberOfColumns)

	for _, row := range records[0:numberOfRows] {
		for currentColumn, cell := range row {
			columns[currentColumn].Rows = append(columns[currentColumn].Rows, strings.Trim(cell, " "))
		}
	}

	return columns, nil
}
<|endoftext|>