<commit_before>// beelog project klog
package klog

import (
	"fmt"
	"github.com/aybabtme/color"
	"io"
	"log"
	"os"
	"runtime"
	"strings"
)

type Level int

const (
	Fshortfile = 1 << iota // show filename:lineno
	Fcolor

	Fdevflag = Fshortfile | Fcolor // for develop use
	Fstdflag = Fcolor
)

const (
	LDebug Level = iota
	LInfo
	LWarning
	LError
	LFatal
)

var levels = []string{
	"[DEBUG]",
	"[INFO.]",
	"[WARN.]",
	"[ERROR]",
	"[FATAL]",
}

var colors = []color.Paint{
	color.CyanPaint,
	color.GreenPaint,
	color.YellowPaint,
	color.RedPaint,
	color.DarkRedPaint,
}

type Logger struct {
	out         io.Writer
	level       Level
	logging     *log.Logger
	color       *ansi
	flags       int
	colorEnable bool
}

// default level is debug
func NewLogger(out io.Writer, prefix string) *Logger {
	if out == nil {
		out = os.Stdout
	}
	return &Logger{
		level:       LInfo,
		logging:     log.New(out, prefix, log.Ldate|log.Ltime),
		color:       &ansi{},
		colorEnable: isTermOutput() && runtime.GOOS != "windows",
		flags:       Fstdflag,
	}
}

func (l *Logger) SetFlags(flag int) {
	l.flags = flag
}

func (l *Logger) SetLevel(level Level) {
	l.level = level
}

func (l *Logger) write(level Level, format string, a ...interface{}) {
	if level < l.level {
		return
	}
	var levelName string = levels[int(level)]
	var preStr string

	preStr += levelName
	if l.flags&Fshortfile != 0 {
		// Retrieve the stack infos
		_, file, line, ok := runtime.Caller(2)
		if !ok {
			file = "<unknown>"
			line = -1
		} else {
			file = file[strings.LastIndex(file, "/")+1:]
		}
		preStr = fmt.Sprintf("%s %s:%d", preStr, file, line)
	}

	var outstr, sep = "", " "
	if format == "" {
		outstr = preStr + sep + fmt.Sprint(a...)
	} else {
		outstr = preStr + sep + fmt.Sprintf(format, a...)
	}
	outstr = strings.TrimSuffix(outstr, "\n")

	if l.colorEnable && l.flags&Fcolor != 0 {
		brush := color.NewBrush("", colors[int(level)])
		outstr = brush(outstr)
	}

	l.logging.Print(outstr)
}

func (l *Logger) Debug(v ...interface{}) {
	l.write(LDebug, "", v...)
}

func (l *Logger) Debugf(format string, v ...interface{}) {
	l.write(LDebug, format, v...)
}

func (l *Logger) Info(v ...interface{}) {
	l.write(LInfo, "", v...)
}
func (l *Logger) Infof(format string, v ...interface{}) {
	l.write(LInfo, format, v...)
}
func (l *Logger) Warn(v ...interface{}) {
	l.write(LWarning, "", v...)
}
func (l *Logger) Warnf(format string, v ...interface{}) {
	l.write(LWarning, format, v...)
}
func (l *Logger) Error(v ...interface{}) {
	l.write(LError, "", v...)
}
func (l *Logger) Errorf(format string, v ...interface{}) {
	l.write(LError, format, v...)
}

// will also call os.Exit(1)
func (l *Logger) Fatal(v ...interface{}) {
	l.write(LFatal, "", v...)
	os.Exit(1)
}

// will also call os.Exit(1)
func (l *Logger) Fatalf(format string, v ...interface{}) {
	l.write(LFatal, format, v...)
	os.Exit(1)
}
<commit_msg>color remove bug fix<commit_after>// beelog project klog
package klog

import (
	"fmt"
	"github.com/aybabtme/color"
	"io"
	"log"
	"os"
	"runtime"
	"strings"
)

type Level int

const (
	Fshortfile = 1 << iota // show filename:lineno
	Fcolor

	Fdevflag = Fshortfile | Fcolor // for develop use
	Fstdflag = Fcolor
)

const (
	LDebug Level = iota
	LInfo
	LWarning
	LError
	LFatal
)

var levels = []string{
	"[DEBUG]",
	"[INFO.]",
	"[WARN.]",
	"[ERROR]",
	"[FATAL]",
}

var colors = []color.Paint{
	color.CyanPaint,
	color.GreenPaint,
	color.YellowPaint,
	color.RedPaint,
	color.DarkRedPaint,
}

type Logger struct {
	out         io.Writer
	level       Level
	logging     *log.Logger
	flags       int
	colorEnable bool
}

// default level is debug
func NewLogger(out io.Writer, prefix string) *Logger {
	if out == nil {
		out = os.Stdout
	}
	return &Logger{
		level:       LInfo,
		logging:     log.New(out, prefix, log.Ldate|log.Ltime),
		colorEnable: isTermOutput() && runtime.GOOS != "windows",
		flags:       Fstdflag,
	}
}

func (l *Logger) SetFlags(flag int) {
	l.flags = flag
}

func (l *Logger) SetLevel(level Level) {
	l.level = level
}

func (l *Logger) write(level Level, format string, a ...interface{}) {
	if level < l.level {
		return
	}
	var levelName string = levels[int(level)]
	var preStr string

	preStr += levelName
	if l.flags&Fshortfile != 0 {
		// Retrieve the stack infos
		_, file, line, ok := runtime.Caller(2)
		if !ok {
			file = "<unknown>"
			line = -1
		} else {
			file = file[strings.LastIndex(file, "/")+1:]
		}
		preStr = fmt.Sprintf("%s %s:%d", preStr, file, line)
	}

	var outstr, sep = "", " "
	if format == "" {
		outstr = preStr + sep + fmt.Sprint(a...)
	} else {
		outstr = preStr + sep + fmt.Sprintf(format, a...)
	}
	outstr = strings.TrimSuffix(outstr, "\n")

	if l.colorEnable && l.flags&Fcolor != 0 {
		brush := color.NewBrush("", colors[int(level)])
		outstr = brush(outstr)
	}

	l.logging.Print(outstr)
}

func (l *Logger) Debug(v ...interface{}) {
	l.write(LDebug, "", v...)
}

func (l *Logger) Debugf(format string, v ...interface{}) {
	l.write(LDebug, format, v...)
}

func (l *Logger) Info(v ...interface{}) {
	l.write(LInfo, "", v...)
}
func (l *Logger) Infof(format string, v ...interface{}) {
	l.write(LInfo, format, v...)
}
func (l *Logger) Warn(v ...interface{}) {
	l.write(LWarning, "", v...)
}
func (l *Logger) Warnf(format string, v ...interface{}) {
	l.write(LWarning, format, v...)
}
func (l *Logger) Error(v ...interface{}) {
	l.write(LError, "", v...)
}
func (l *Logger) Errorf(format string, v ...interface{}) {
	l.write(LError, format, v...)
}

// will also call os.Exit(1)
func (l *Logger) Fatal(v ...interface{}) {
	l.write(LFatal, "", v...)
	os.Exit(1)
}

// will also call os.Exit(1)
func (l *Logger) Fatalf(format string, v ...interface{}) {
	l.write(LFatal, format, v...)
	os.Exit(1)
}
<|endoftext|>


<commit_before>// KRPC helpers.
package dht

import (
	"bytes"
	"log"
	"net"
	"strconv"
	"time"

	"github.com/nictuku/Taipei-Torrent/bencode"
)

// Owned by the DHT engine.
type DhtRemoteNode struct {
	address *net.UDPAddr
	id      string
	// lastQueryID should be incremented after consumed. Based on the
	// protocol, it would be two letters, but I'm using 0-255, although
	// treated as string.
	lastQueryID     int
	pendingQueries  map[string]*queryType // key: transaction ID
	pastQueries     map[string]*queryType // key: transaction ID
	reachable       bool
	lastTime        time.Time
	ActiveDownloads []string // List of infohashes we know this peer is downloading.
}

type queryType struct {
	Type    string
	ih      string
	srcNode string
}

const (
	NODE_ID_LEN         = 20
	NODE_CONTACT_LEN    = 26
	PEER_CONTACT_LEN    = 6
	MAX_UDP_PACKET_SIZE = 4096
)

// The 'nodes' response is a string with fixed length contacts concatenated arbitrarily.
func parseNodesString(nodes string) (parsed map[string]string) {
	parsed = make(map[string]string)
	if len(nodes)%NODE_CONTACT_LEN > 0 {
		// TODO: Err
		log.Println("DHT: Invalid length of nodes.")
		log.Printf("DHT: Should be a multiple of %d, got %d", NODE_CONTACT_LEN, len(nodes))
		return
	}
	for i := 0; i < len(nodes); i += NODE_CONTACT_LEN {
		id := nodes[i : i+NODE_ID_LEN]
		address := bencode.BinaryToDottedPort(nodes[i+NODE_ID_LEN : i+NODE_CONTACT_LEN])
		parsed[id] = address
	}
	return

}

// newQuery creates a new transaction id and adds an entry to r.pendingQueries.
// It does not set any extra information to the transaction information, so the
// caller must take care of that. (XXX: Ugly)
func (r *DhtRemoteNode) newQuery(transType string) (transId string) {
	r.lastQueryID = (r.lastQueryID + 1) % 256
	transId = strconv.Itoa(r.lastQueryID)
	r.pendingQueries[transId] = &queryType{Type: transType}
	return
}

type getPeersResponse struct {
	// TODO: argh, values can be a string depending on the client (e.g: original bittorrent).
	Values []string "values"
	Id     string   "id"
	Nodes  string   "nodes"
	Token  string   "token"
}

type answerType struct {
	Id       string "id"
	Target   string "target"
	InfoHash string "info_hash"
	Port     int    "port"
	Token    string "token"
}

// Generic stuff we read from the wire, not knowing what it is. This is as generic as can be.
type responseType struct {
	T string           "t"
	Y string           "y"
	Q string           "q"
	R getPeersResponse "r"
	E []string         "e"
	A answerType       "a"
	// Unsupported mainline extension for client identification.
	// V string(?)	"v"
}

// sendMsg bencodes the data in 'query' and sends it to the remote node.
func sendMsg(conn *net.UDPConn, raddr *net.UDPAddr, query interface{}) {
	var b bytes.Buffer
	if err := bencode.Marshal(&b, query); err != nil {
		return
	}
	if _, err := conn.WriteToUDP(b.Bytes(), raddr); err != nil {
		// debug.Println("DHT: node write failed:", err)
	}
	return
}

// Read responses from bencode-speaking nodes. Return the appropriate data structure.
func readResponse(p packetType) (response responseType, err error) {
	// The calls to bencode.Unmarshal() can be fragile.
	defer func() {
		if x := recover(); x != nil {
			// debug.Printf("DHT: !!! Recovering from panic() after bencode.Unmarshal %q, %v", string(p.b), x)
		}
	}()
	if e2 := bencode.Unmarshal(bytes.NewBuffer(p.b), &response); e2 == nil {
		err = nil
		return
	} else {
		// debug.Printf("DHT: unmarshal error, odd or partial data during UDP read? %v, err=%s", string(p.b), e2)
		return response, e2
	}
	return
}

// Message to be sent out in the wire. Must not have any extra fields.
type queryMessage struct {
	T string                 "t"
	Y string                 "y"
	Q string                 "q"
	A map[string]interface{} "a"
}

type replyMessage struct {
	T string                 "t"
	Y string                 "y"
	R map[string]interface{} "r"
}

type packetType struct {
	b     []byte
	raddr *net.UDPAddr
}

func listen(listenPort int) (socket *net.UDPConn, err error) {
	// debug.Printf("DHT: Listening for peers on port: %d\n", listenPort)
	listener, err := net.ListenPacket("udp4", ":"+strconv.Itoa(listenPort))
	if err != nil {
		// debug.Println("DHT: Listen failed:", err)
	}
	if listener != nil {
		socket = listener.(*net.UDPConn)
	}
	return
}

// Read from UDP socket, writes slice of byte into channel.
func readFromSocket(socket *net.UDPConn, conChan chan packetType) {
	for {
		b := make([]byte, MAX_UDP_PACKET_SIZE)
		n, addr, err := socket.ReadFromUDP(b)
		b = b[0:n]
		if n == MAX_UDP_PACKET_SIZE {
			// debug.Printf("DHT: Warning. Received packet with len >= %d, some data may have been discarded.\n", MAX_UDP_PACKET_SIZE)
		}
		if n > 0 && err == nil {
			p := packetType{b, addr}
			conChan <- p
			continue
		}
		// debug.Println("DHT: readResponse error:", err)
	}
}
<commit_msg>Generic expvar for recv and sent datagrams.<commit_after>// KRPC helpers.
package dht

import (
	"bytes"
	"expvar"
	"log"
	"net"
	"strconv"
	"time"

	"github.com/nictuku/Taipei-Torrent/bencode"
)

// Owned by the DHT engine.
type DhtRemoteNode struct {
	address *net.UDPAddr
	id      string
	// lastQueryID should be incremented after consumed. Based on the
	// protocol, it would be two letters, but I'm using 0-255, although
	// treated as string.
	lastQueryID     int
	pendingQueries  map[string]*queryType // key: transaction ID
	pastQueries     map[string]*queryType // key: transaction ID
	reachable       bool
	lastTime        time.Time
	ActiveDownloads []string // List of infohashes we know this peer is downloading.
}

type queryType struct {
	Type    string
	ih      string
	srcNode string
}

const (
	NODE_ID_LEN         = 20
	NODE_CONTACT_LEN    = 26
	PEER_CONTACT_LEN    = 6
	MAX_UDP_PACKET_SIZE = 4096
)

var (
	totalRecv = expvar.NewInt("totalRecv")
	totalSent = expvar.NewInt("totalSent")
)

// The 'nodes' response is a string with fixed length contacts concatenated arbitrarily.
func parseNodesString(nodes string) (parsed map[string]string) {
	parsed = make(map[string]string)
	if len(nodes)%NODE_CONTACT_LEN > 0 {
		// TODO: Err
		log.Println("DHT: Invalid length of nodes.")
		log.Printf("DHT: Should be a multiple of %d, got %d", NODE_CONTACT_LEN, len(nodes))
		return
	}
	for i := 0; i < len(nodes); i += NODE_CONTACT_LEN {
		id := nodes[i : i+NODE_ID_LEN]
		address := bencode.BinaryToDottedPort(nodes[i+NODE_ID_LEN : i+NODE_CONTACT_LEN])
		parsed[id] = address
	}
	return

}

// newQuery creates a new transaction id and adds an entry to r.pendingQueries.
// It does not set any extra information to the transaction information, so the
// caller must take care of that. (XXX: Ugly)
func (r *DhtRemoteNode) newQuery(transType string) (transId string) {
	r.lastQueryID = (r.lastQueryID + 1) % 256
	transId = strconv.Itoa(r.lastQueryID)
	r.pendingQueries[transId] = &queryType{Type: transType}
	return
}

type getPeersResponse struct {
	// TODO: argh, values can be a string depending on the client (e.g: original bittorrent).
	Values []string "values"
	Id     string   "id"
	Nodes  string   "nodes"
	Token  string   "token"
}

type answerType struct {
	Id       string "id"
	Target   string "target"
	InfoHash string "info_hash"
	Port     int    "port"
	Token    string "token"
}

// Generic stuff we read from the wire, not knowing what it is. This is as generic as can be.
type responseType struct {
	T string           "t"
	Y string           "y"
	Q string           "q"
	R getPeersResponse "r"
	E []string         "e"
	A answerType       "a"
	// Unsupported mainline extension for client identification.
	// V string(?)	"v"
}

// sendMsg bencodes the data in 'query' and sends it to the remote node.
func sendMsg(conn *net.UDPConn, raddr *net.UDPAddr, query interface{}) {
	totalSent.Add(1)
	var b bytes.Buffer
	if err := bencode.Marshal(&b, query); err != nil {
		return
	}
	if _, err := conn.WriteToUDP(b.Bytes(), raddr); err != nil {
		// debug.Println("DHT: node write failed:", err)
	}
	return
}

// Read responses from bencode-speaking nodes. Return the appropriate data structure.
func readResponse(p packetType) (response responseType, err error) {
	totalRecv.Add(1)
	// The calls to bencode.Unmarshal() can be fragile.
	defer func() {
		if x := recover(); x != nil {
			// debug.Printf("DHT: !!! Recovering from panic() after bencode.Unmarshal %q, %v", string(p.b), x)
		}
	}()
	if e2 := bencode.Unmarshal(bytes.NewBuffer(p.b), &response); e2 == nil {
		err = nil
		return
	} else {
		// debug.Printf("DHT: unmarshal error, odd or partial data during UDP read? %v, err=%s", string(p.b), e2)
		return response, e2
	}
	return
}

// Message to be sent out in the wire. Must not have any extra fields.
type queryMessage struct {
	T string                 "t"
	Y string                 "y"
	Q string                 "q"
	A map[string]interface{} "a"
}

type replyMessage struct {
	T string                 "t"
	Y string                 "y"
	R map[string]interface{} "r"
}

type packetType struct {
	b     []byte
	raddr *net.UDPAddr
}

func listen(listenPort int) (socket *net.UDPConn, err error) {
	// debug.Printf("DHT: Listening for peers on port: %d\n", listenPort)
	listener, err := net.ListenPacket("udp4", ":"+strconv.Itoa(listenPort))
	if err != nil {
		// debug.Println("DHT: Listen failed:", err)
	}
	if listener != nil {
		socket = listener.(*net.UDPConn)
	}
	return
}

// Read from UDP socket, writes slice of byte into channel.
func readFromSocket(socket *net.UDPConn, conChan chan packetType) {
	for {
		b := make([]byte, MAX_UDP_PACKET_SIZE)
		n, addr, err := socket.ReadFromUDP(b)
		b = b[0:n]
		if n == MAX_UDP_PACKET_SIZE {
			// debug.Printf("DHT: Warning. Received packet with len >= %d, some data may have been discarded.\n", MAX_UDP_PACKET_SIZE)
		}
		if n > 0 && err == nil {
			p := packetType{b, addr}
			conChan <- p
			continue
		}
		// debug.Println("DHT: readResponse error:", err)
	}
}
<|endoftext|>


<commit_before>package mastodon

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/tomnomnom/linkheader"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(ctx context.Context, method string, uri string, params interface{}, res interface{}, pg *Pagination) (*Pagination, error) {
	u, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	u.Path = path.Join(u.Path, uri)

	var req *http.Request
	ct := "application/x-www-form-urlencoded"
	if values, ok := params.(url.Values); ok {
		var body io.Reader
		if method == http.MethodGet {
			u.RawQuery = pg.setValues(values).Encode()
		} else {
			body = strings.NewReader(values.Encode())
		}
		req, err = http.NewRequest(method, u.String(), body)
		if err != nil {
			return nil, err
		}
	} else if file, ok := params.(string); ok {
		f, err := os.Open(file)
		if err != nil {
			return nil, err
		}
		defer f.Close()

		var buf bytes.Buffer
		mw := multipart.NewWriter(&buf)
		part, err := mw.CreateFormFile("file", filepath.Base(file))
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(part, f)
		if err != nil {
			return nil, err
		}
		err = mw.Close()
		if err != nil {
			return nil, err
		}
		req, err = http.NewRequest(method, u.String(), &buf)
		if err != nil {
			return nil, err
		}
		ct = mw.FormDataContentType()
	} else {
		req, err = http.NewRequest(method, u.String(), nil)
		if err != nil {
			return nil, err
		}
	}
	req = req.WithContext(ctx)
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	if params != nil {
		req.Header.Set("Content-Type", ct)
	}

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

	lh := resp.Header.Get("Link")
	var retPG *Pagination
	if lh != "" {
		retPG, err = newPagination(lh)
		if err != nil {
			return nil, err
		}
	}

	if resp.StatusCode != http.StatusOK {
		return nil, parseAPIError("bad request", resp)
	} else if res == nil {
		return nil, nil
	}
	return retPG, json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(ctx context.Context, username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	u, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	u.Path = path.Join(u.Path, "/oauth/token")

	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return parseAPIError("bad authorization", resp)
	}

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Mention hold information for mention.
type Mention struct {
	URL      string `json:"url"`
	Username string `json:"username"`
	Acct     string `json:"acct"`
	ID       int64  `json:"id"`
}

// Tag hold information for tag.
type Tag struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

// Attachment hold information for attachment.
type Attachment struct {
	ID         int64  `json:"id"`
	Type       string `json:"type"`
	URL        string `json:"url"`
	RemoteURL  string `json:"remote_url"`
	PreviewURL string `json:"preview_url"`
	TextURL    string `json:"text_url"`
}

// Results hold information for search result.
type Results struct {
	Accounts []*Account `json:"accounts"`
	Statuses []*Status  `json:"statuses"`
	Hashtags []string   `json:"hashtags"`
}

// Pagination is a struct for specifying the get range.
type Pagination struct {
	MaxID   *int64
	SinceID *int64
	Limit   *int64
}

func newPagination(rawlink string) (*Pagination, error) {
	if rawlink == "" {
		return nil, errors.New("empty link header")
	}

	p := &Pagination{}
	for _, link := range linkheader.Parse(rawlink) {
		switch link.Rel {
		case "next":
			maxID, err := getPaginationID(link.URL, "max_id")
			if err != nil {
				return nil, err
			}
			p.MaxID = &maxID
		case "prev":
			sinceID, err := getPaginationID(link.URL, "since_id")
			if err != nil {
				return nil, err
			}
			p.SinceID = &sinceID
		}
	}

	return p, nil
}

func getPaginationID(rawurl, key string) (int64, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return 0, err
	}

	id, err := strconv.ParseInt(u.Query().Get(key), 10, 64)
	if err != nil {
		return 0, err
	}

	return id, nil
}

func (p *Pagination) setValues(params url.Values) url.Values {
	if p != nil {
		if p.MaxID != nil {
			params.Set("max_id", fmt.Sprint(p.MaxID))
		}
		if p.SinceID != nil {
			params.Set("since_id", fmt.Sprint(p.SinceID))
		}
		if p.Limit != nil {
			params.Set("limit", fmt.Sprint(p.Limit))
		}
	}

	return params
}
<commit_msg>Fix doAPI pagination<commit_after>package mastodon

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/tomnomnom/linkheader"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(ctx context.Context, method string, uri string, params interface{}, res interface{}, pg *Pagination) (*Pagination, error) {
	u, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	u.Path = path.Join(u.Path, uri)

	var req *http.Request
	ct := "application/x-www-form-urlencoded"
	if values, ok := params.(url.Values); ok {
		var body io.Reader
		if method == http.MethodGet {
			if pg != nil {
				values = pg.setValues(values)
			}
			u.RawQuery = values.Encode()
		} else {
			body = strings.NewReader(values.Encode())
		}
		req, err = http.NewRequest(method, u.String(), body)
		if err != nil {
			return nil, err
		}
	} else if file, ok := params.(string); ok {
		f, err := os.Open(file)
		if err != nil {
			return nil, err
		}
		defer f.Close()

		var buf bytes.Buffer
		mw := multipart.NewWriter(&buf)
		part, err := mw.CreateFormFile("file", filepath.Base(file))
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(part, f)
		if err != nil {
			return nil, err
		}
		err = mw.Close()
		if err != nil {
			return nil, err
		}
		req, err = http.NewRequest(method, u.String(), &buf)
		if err != nil {
			return nil, err
		}
		ct = mw.FormDataContentType()
	} else {
		if method == http.MethodGet && pg != nil {
			u.RawQuery = pg.toValues().Encode()
		}
		req, err = http.NewRequest(method, u.String(), nil)
		if err != nil {
			return nil, err
		}
	}
	req = req.WithContext(ctx)
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	if params != nil {
		req.Header.Set("Content-Type", ct)
	}

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

	lh := resp.Header.Get("Link")
	var retPG *Pagination
	if lh != "" {
		retPG, err = newPagination(lh)
		if err != nil {
			return nil, err
		}
	}

	if resp.StatusCode != http.StatusOK {
		return nil, parseAPIError("bad request", resp)
	} else if res == nil {
		return nil, nil
	}
	return retPG, json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(ctx context.Context, username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	u, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	u.Path = path.Join(u.Path, "/oauth/token")

	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return parseAPIError("bad authorization", resp)
	}

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Mention hold information for mention.
type Mention struct {
	URL      string `json:"url"`
	Username string `json:"username"`
	Acct     string `json:"acct"`
	ID       int64  `json:"id"`
}

// Tag hold information for tag.
type Tag struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

// Attachment hold information for attachment.
type Attachment struct {
	ID         int64  `json:"id"`
	Type       string `json:"type"`
	URL        string `json:"url"`
	RemoteURL  string `json:"remote_url"`
	PreviewURL string `json:"preview_url"`
	TextURL    string `json:"text_url"`
}

// Results hold information for search result.
type Results struct {
	Accounts []*Account `json:"accounts"`
	Statuses []*Status  `json:"statuses"`
	Hashtags []string   `json:"hashtags"`
}

// Pagination is a struct for specifying the get range.
type Pagination struct {
	MaxID   *int64
	SinceID *int64
	Limit   *int64
}

func newPagination(rawlink string) (*Pagination, error) {
	if rawlink == "" {
		return nil, errors.New("empty link header")
	}

	p := &Pagination{}
	for _, link := range linkheader.Parse(rawlink) {
		switch link.Rel {
		case "next":
			maxID, err := getPaginationID(link.URL, "max_id")
			if err != nil {
				return nil, err
			}
			p.MaxID = &maxID
		case "prev":
			sinceID, err := getPaginationID(link.URL, "since_id")
			if err != nil {
				return nil, err
			}
			p.SinceID = &sinceID
		}
	}

	return p, nil
}

func getPaginationID(rawurl, key string) (int64, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return 0, err
	}

	id, err := strconv.ParseInt(u.Query().Get(key), 10, 64)
	if err != nil {
		return 0, err
	}

	return id, nil
}

func (p *Pagination) toValues() url.Values {
	return p.setValues(url.Values{})
}

func (p *Pagination) setValues(params url.Values) url.Values {
	if p.MaxID != nil {
		params.Set("max_id", fmt.Sprint(*p.MaxID))
	}
	if p.SinceID != nil {
		params.Set("since_id", fmt.Sprint(*p.SinceID))
	}
	if p.Limit != nil {
		params.Set("limit", fmt.Sprint(*p.Limit))
	}

	return params
}
<|endoftext|>


<commit_before>package mastodon

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"time"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, uri)

	var resp *http.Response
	req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	resp, err = c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if res == nil {
		return nil
	}
	return json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, "/oauth/token")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// AppConfig is a setting for registering applications.
type AppConfig struct {
	http.Client
	Server     string
	ClientName string

	// Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob)
	RedirectURIs string

	// This can be a space-separated list of the following items: "read", "write" and "follow".
	Scopes string

	// Optional.
	Website string
}

// Application is mastodon application.
type Application struct {
	ID           int64  `json:"id"`
	RedirectURI  string `json:"redirect_uri"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// RegisterApp returns the mastodon application.
func RegisterApp(appConfig *AppConfig) (*Application, error) {
	params := url.Values{}
	params.Set("client_name", appConfig.ClientName)
	params.Set("redirect_uris", appConfig.RedirectURIs)
	params.Set("scopes", appConfig.Scopes)
	params.Set("website", appConfig.Website)

	url, err := url.Parse(appConfig.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/apps")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return nil, err
	}
	resp, err := appConfig.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	app := &Application{}
	err = json.NewDecoder(resp.Body).Decode(app)
	if err != nil {
		return nil, err
	}

	return app, nil
}

// Account hold information for mastodon account.
type Account struct {
	ID             int64     `json:"id"`
	Username       string    `json:"username"`
	Acct           string    `json:"acct"`
	DisplayName    string    `json:"display_name"`
	Locked         bool      `json:"locked"`
	CreatedAt      time.Time `json:"created_at"`
	FollowersCount int64     `json:"followers_count"`
	FollowingCount int64     `json:"following_count"`
	StatusesCount  int64     `json:"statuses_count"`
	Note           string    `json:"note"`
	URL            string    `json:"url"`
	Avatar         string    `json:"avatar"`
	AvatarStatic   string    `json:"avatar_static"`
	Header         string    `json:"header"`
	HeaderStatic   string    `json:"header_static"`
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Status is struct to hold status.
type Status struct {
	ID                 int64         `json:"id"`
	CreatedAt          time.Time     `json:"created_at"`
	InReplyToID        interface{}   `json:"in_reply_to_id"`
	InReplyToAccountID interface{}   `json:"in_reply_to_account_id"`
	Sensitive          bool          `json:"sensitive"`
	SpoilerText        string        `json:"spoiler_text"`
	Visibility         string        `json:"visibility"`
	Application        interface{}   `json:"application"`
	Account            Account       `json:"account"`
	MediaAttachments   []interface{} `json:"media_attachments"`
	Mentions           []interface{} `json:"mentions"`
	Tags               []interface{} `json:"tags"`
	URI                string        `json:"uri"`
	Content            string        `json:"content"`
	URL                string        `json:"url"`
	ReblogsCount       int64         `json:"reblogs_count"`
	FavouritesCount    int64         `json:"favourites_count"`
	Reblog             interface{}   `json:"reblog"`
	Favourited         interface{}   `json:"favourited"`
	Reblogged          interface{}   `json:"reblogged"`
}

// GetAccount return Account.
func (c *Client) GetAccount(id int) (*Account, error) {
	var account Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetTimelineHome return statuses from home timeline.
func (c *Client) GetTimelineHome() ([]*Status, error) {
	var statuses []*Status
	err := c.doAPI("GET", "/api/v1/timelines/home", nil, &statuses)
	if err != nil {
		return nil, err
	}
	return statuses, nil
}

// PostStatus post the toot.
func (c *Client) PostStatus(toot *Toot) (*Status, error) {
	params := url.Values{}
	params.Set("status", toot.Status)
	if toot.InReplyToID > 0 {
		params.Set("in_reply_to_id", fmt.Sprint(toot.InReplyToID))
	}
	// TODO: media_ids, senstitive, spoiler_text, visibility
	//params.Set("visibility", "public")

	var status Status
	err := c.doAPI("POST", "/api/v1/statuses", params, &status)
	if err != nil {
		return nil, err
	}
	return &status, nil
}

// UpdateEvent is struct for passing status event to app.
type UpdateEvent struct {
	Status *Status
}

func (e *UpdateEvent) event() {}

// NotificationEvent is struct for passing notification event to app.
type NotificationEvent struct {
}

func (e *NotificationEvent) event() {}

// DeleteEvent is struct for passing deletion event to app.
type DeleteEvent struct {
	ID int64
}

func (e *DeleteEvent) event() {}

// ErrorEvent is struct for passing errors to app.
type ErrorEvent struct {
	err error
}

func (e *ErrorEvent) Error() string { return e.err.Error() }
func (e *ErrorEvent) event()        {}

// Event is interface passing events to app.
type Event interface {
	event()
}

// StreamingPublic return channel to read events.
func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/streaming/public")

	var resp *http.Response

	q := make(chan Event, 10)
	go func() {
		defer ctx.Done()

		for {
			req, err := http.NewRequest("GET", url.String(), nil)
			if err == nil {
				req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
				resp, err = c.Do(req)
			}
			if err == nil {
				name := ""
				s := bufio.NewScanner(io.TeeReader(resp.Body, os.Stdout))
				for s.Scan() {
					line := s.Text()
					token := strings.SplitN(line, ":", 2)
					if len(token) != 2 {
						continue
					}
					switch strings.TrimSpace(token[0]) {
					case "event":
						name = strings.TrimSpace(token[1])
					case "data":
						switch name {
						case "update":
							var status Status
							json.Unmarshal([]byte(token[1]), &status)
							q <- &UpdateEvent{&status}
						case "notification":
						case "delete":
						}
					default:
					}
				}
				resp.Body.Close()
				err = ctx.Err()
				if err == nil {
					break
				}
			} else {
				q <- &ErrorEvent{err}
			}
			time.Sleep(3 * time.Second)
		}
	}()
	go func() {
		<-ctx.Done()
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
	}()
	return q, nil
}
<commit_msg>check error<commit_after>package mastodon

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"time"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, uri)

	var resp *http.Response
	req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	resp, err = c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if res == nil {
		return nil
	}
	return json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, "/oauth/token")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// AppConfig is a setting for registering applications.
type AppConfig struct {
	http.Client
	Server     string
	ClientName string

	// Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob)
	RedirectURIs string

	// This can be a space-separated list of the following items: "read", "write" and "follow".
	Scopes string

	// Optional.
	Website string
}

// Application is mastodon application.
type Application struct {
	ID           int64  `json:"id"`
	RedirectURI  string `json:"redirect_uri"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// RegisterApp returns the mastodon application.
func RegisterApp(appConfig *AppConfig) (*Application, error) {
	params := url.Values{}
	params.Set("client_name", appConfig.ClientName)
	params.Set("redirect_uris", appConfig.RedirectURIs)
	params.Set("scopes", appConfig.Scopes)
	params.Set("website", appConfig.Website)

	url, err := url.Parse(appConfig.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/apps")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return nil, err
	}
	resp, err := appConfig.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	app := &Application{}
	err = json.NewDecoder(resp.Body).Decode(app)
	if err != nil {
		return nil, err
	}

	return app, nil
}

// Account hold information for mastodon account.
type Account struct {
	ID             int64     `json:"id"`
	Username       string    `json:"username"`
	Acct           string    `json:"acct"`
	DisplayName    string    `json:"display_name"`
	Locked         bool      `json:"locked"`
	CreatedAt      time.Time `json:"created_at"`
	FollowersCount int64     `json:"followers_count"`
	FollowingCount int64     `json:"following_count"`
	StatusesCount  int64     `json:"statuses_count"`
	Note           string    `json:"note"`
	URL            string    `json:"url"`
	Avatar         string    `json:"avatar"`
	AvatarStatic   string    `json:"avatar_static"`
	Header         string    `json:"header"`
	HeaderStatic   string    `json:"header_static"`
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Status is struct to hold status.
type Status struct {
	ID                 int64         `json:"id"`
	CreatedAt          time.Time     `json:"created_at"`
	InReplyToID        interface{}   `json:"in_reply_to_id"`
	InReplyToAccountID interface{}   `json:"in_reply_to_account_id"`
	Sensitive          bool          `json:"sensitive"`
	SpoilerText        string        `json:"spoiler_text"`
	Visibility         string        `json:"visibility"`
	Application        interface{}   `json:"application"`
	Account            Account       `json:"account"`
	MediaAttachments   []interface{} `json:"media_attachments"`
	Mentions           []interface{} `json:"mentions"`
	Tags               []interface{} `json:"tags"`
	URI                string        `json:"uri"`
	Content            string        `json:"content"`
	URL                string        `json:"url"`
	ReblogsCount       int64         `json:"reblogs_count"`
	FavouritesCount    int64         `json:"favourites_count"`
	Reblog             interface{}   `json:"reblog"`
	Favourited         interface{}   `json:"favourited"`
	Reblogged          interface{}   `json:"reblogged"`
}

// GetAccount return Account.
func (c *Client) GetAccount(id int) (*Account, error) {
	var account Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetTimelineHome return statuses from home timeline.
func (c *Client) GetTimelineHome() ([]*Status, error) {
	var statuses []*Status
	err := c.doAPI("GET", "/api/v1/timelines/home", nil, &statuses)
	if err != nil {
		return nil, err
	}
	return statuses, nil
}

// PostStatus post the toot.
func (c *Client) PostStatus(toot *Toot) (*Status, error) {
	params := url.Values{}
	params.Set("status", toot.Status)
	if toot.InReplyToID > 0 {
		params.Set("in_reply_to_id", fmt.Sprint(toot.InReplyToID))
	}
	// TODO: media_ids, senstitive, spoiler_text, visibility
	//params.Set("visibility", "public")

	var status Status
	err := c.doAPI("POST", "/api/v1/statuses", params, &status)
	if err != nil {
		return nil, err
	}
	return &status, nil
}

// UpdateEvent is struct for passing status event to app.
type UpdateEvent struct {
	Status *Status
}

func (e *UpdateEvent) event() {}

// NotificationEvent is struct for passing notification event to app.
type NotificationEvent struct {
}

func (e *NotificationEvent) event() {}

// DeleteEvent is struct for passing deletion event to app.
type DeleteEvent struct {
	ID int64
}

func (e *DeleteEvent) event() {}

// ErrorEvent is struct for passing errors to app.
type ErrorEvent struct {
	err error
}

func (e *ErrorEvent) Error() string { return e.err.Error() }
func (e *ErrorEvent) event()        {}

// Event is interface passing events to app.
type Event interface {
	event()
}

// StreamingPublic return channel to read events.
func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/streaming/public")

	var resp *http.Response

	q := make(chan Event, 10)
	go func() {
		defer ctx.Done()

		for {
			req, err := http.NewRequest("GET", url.String(), nil)
			if err == nil {
				req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
				resp, err = c.Do(req)
			}
			if err == nil {
				name := ""
				s := bufio.NewScanner(io.TeeReader(resp.Body, os.Stdout))
				for s.Scan() {
					line := s.Text()
					token := strings.SplitN(line, ":", 2)
					if len(token) != 2 {
						continue
					}
					switch strings.TrimSpace(token[0]) {
					case "event":
						name = strings.TrimSpace(token[1])
					case "data":
						switch name {
						case "update":
							var status Status
							err = json.Unmarshal([]byte(token[1]), &status)
							if err == nil {
								q <- &UpdateEvent{&status}
							}
						case "notification":
						case "delete":
						}
					default:
					}
				}
				resp.Body.Close()
				err = ctx.Err()
				if err == nil {
					break
				}
			} else {
				q <- &ErrorEvent{err}
			}
			time.Sleep(3 * time.Second)
		}
	}()
	go func() {
		<-ctx.Done()
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
	}()
	return q, nil
}
<|endoftext|>


<commit_before>// This is a library to manage Microchip MCP23008 This chip is used on onion.io Omega2 relay expension
package mcp23008

import (
	"golang.org/x/exp/io/i2c"
	"math"
	"log"
)

const (
	iodir   = 0x00
	ipol    = 0x01
	gpinten = 0x02
	defval  = 0x03
	intcon  = 0x04
	iocon   = 0x05
	gppu    = 0x06
	intf    = 0x07
	intcap  = 0x08
	gpio    = 0x09
	olat    = 0x0A
)

// McpInit function initialize MCP28003 after boot or restart of device
func McpInit(d *i2c.Device) error {
	// SetAllDirection
	err := d.WriteReg(iodir, []byte{0})
	if err != nil {
		return err
	}

	// SetAllPullUp
	err = d.WriteReg(gppu, []byte{0})
	return err
}

// McpGpioToggle change state of selected GPIO other one are unchanged
func McpGpioToggle(d *i2c.Device, gpio byte) {
	regValue := []byte{0}

	// Set 1 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio)))

	// Read current state of all GPIO's
	d.ReadReg(gpio, regValue)

	// Write ON to selected GPIO other one keep unchanged
	d.WriteReg(gpio,[]byte{regValue[0] ^ mask})
}

// McpGpioOn set GPIO to ON/High state other one are unchanged
func McpGpioOn(d *i2c.Device, gpio byte) {
	regValue := []byte{0}

	// Set 1 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio)))

	// Read current state of all GPIO's
	d.ReadReg(gpio, regValue)

	// Write ON to selected GPIO other one keep unchanged
	d.WriteReg(gpio,[]byte{mask | regValue[0]})
}

// Set all GPIO to ON/High state
func McpGpioAllOn(d *i2c.Device) {
	// Write ON to all GPIO
	d.WriteReg(gpio,[]byte{0xf})
}

// McpGpioOff set GPIO to OFF/Low state other one are unchanged
func McpGpioOff(d *i2c.Device, gpio byte) {
	regValue := []byte{0}

	// Set 0 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio))) ^ 0xf

	// Read current state of all GPIO's
	d.ReadReg(gpio, regValue)

	// Write OFF to selected GPIO other one keep unchanged
	d.WriteReg(gpio,[]byte{mask & regValue[0]})
}

// Set all GPIO to OFF/Low state
func McpGpioAllOff(d *i2c.Device) {
	// Write ON to all GPIO
	d.WriteReg(gpio,[]byte{0x0})
}

// This function return state of selected GPIO 1 for ON/High or 0 for OFF/Low state
func McpReadGpio(d *i2c.Device, gpio byte) byte {
	regValue := []byte{0}

	// Set 1 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio)))

	d.ReadReg(gpio, regValue)
	log.Print("McpReadGpio gpio value -> ", string(regValue[0]))
	return (regValue[0] & mask) >> gpio
}
<commit_msg>Add log TODO: - Error Handling - Extand function - Test file<commit_after>// This is a library to manage Microchip MCP23008 This chip is used on onion.io Omega2 relay expension
package mcp23008

import (
	"golang.org/x/exp/io/i2c"
	"math"
	"log"
)

const (
	iodir   = 0x00
	ipol    = 0x01
	gpinten = 0x02
	defval  = 0x03
	intcon  = 0x04
	iocon   = 0x05
	gppu    = 0x06
	intf    = 0x07
	intcap  = 0x08
	gpio    = 0x09
	olat    = 0x0A
)

// McpInit function initialize MCP28003 after boot or restart of device
func McpInit(d *i2c.Device) error {
	// SetAllDirection
	err := d.WriteReg(iodir, []byte{0})
	if err != nil {
		return err
	}

	// SetAllPullUp
	err = d.WriteReg(gppu, []byte{0})
	return err
}

// McpGpioToggle change state of selected GPIO other one are unchanged
func McpGpioToggle(d *i2c.Device, gpio byte) {
	regValue := []byte{0}

	// Set 1 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio)))

	// Read current state of all GPIO's
	d.ReadReg(gpio, regValue)

	// Write ON to selected GPIO other one keep unchanged
	d.WriteReg(gpio,[]byte{regValue[0] ^ mask})
}

// McpGpioOn set GPIO to ON/High state other one are unchanged
func McpGpioOn(d *i2c.Device, gpio byte) {
	regValue := []byte{0}

	// Set 1 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio)))

	// Read current state of all GPIO's
	d.ReadReg(gpio, regValue)

	// Write ON to selected GPIO other one keep unchanged
	d.WriteReg(gpio,[]byte{mask | regValue[0]})
}

// Set all GPIO to ON/High state
func McpGpioAllOn(d *i2c.Device) {
	// Write ON to all GPIO
	d.WriteReg(gpio,[]byte{0xf})
}

// McpGpioOff set GPIO to OFF/Low state other one are unchanged
func McpGpioOff(d *i2c.Device, gpio byte) {
	regValue := []byte{0}

	// Set 0 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio))) ^ 0xf

	// Read current state of all GPIO's
	d.ReadReg(gpio, regValue)

	// Write OFF to selected GPIO other one keep unchanged
	d.WriteReg(gpio,[]byte{mask & regValue[0]})
}

// Set all GPIO to OFF/Low state
func McpGpioAllOff(d *i2c.Device) {
	// Write ON to all GPIO
	d.WriteReg(gpio,[]byte{0x0})
}

// This function return state of selected GPIO 1 for ON/High or 0 for OFF/Low state
func McpReadGpio(d *i2c.Device, gpio byte) byte {
	regValue := []byte{0}

	// Set 1 to corresponding BIT of GPIO
	mask := byte(math.Pow(2, float64(gpio)))

	d.ReadReg(gpio, regValue)
	log.Printf("McpReadGpio gpio value -> %b", string(regValue[0]))
	return (regValue[0] & mask) >> gpio
}
<|endoftext|>


<commit_before>/**
 * Package main provides all the functionality behind the mementor tool.
 *
 * Mementor is a command line utility for displaying, adding and deleting mementos. Mementos are
 * messages describing things we need to be reminded of regularly.
 *
 *
 * @author Richard Laffers <rlaffers@gmail.com
 * @copyright Richard Laffers <rlaffers@gmail.com,
 * @package default
 * @version $Id$
 */
package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"math/rand"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/xeonx/timeago"
)

// Memento represents a record in mementor.
type Memento struct {
	Id       int
	Msg      string
	Time     int64
	Priority int
}

const (
	version = "0.1.3"
)

type print struct {
}

func (p *print) info(msg string, args ...interface{}) {
	fmt.Printf("\x1b[36;1m"+msg+"\n\x1b[0m", args...)
}

func (p *print) error(msg string, args ...interface{}) {
	fmt.Printf("\x1b[31;1m"+msg+"\n\x1b[0m", args...)
}

func (p *print) underscore(msg string, args ...interface{}) {
	fmt.Printf("\x1b[4;1m"+msg+"\n\x1b[0m", args...)
}

var (
	dataFile *string
	debug    = flag.Bool("debug", false, "Turn debugging on.")
	logger   *logrus.Logger
	pr       = print{}
)

func init() {
	home := os.Getenv("HOME")
	if home == "" {
		panic("HOME variable is not set")
	}

	dataFile = flag.String("f", home+"/.mementor/mementos.json", "Path to the mementos storage file.")
	// parse flags
	flag.Parse()
	logger = logrus.New()
	if *debug {
		logger.Level = logrus.DebugLevel
	} else {
		logger.Level = logrus.InfoLevel
	}
	formatter := new(logrus.TextFormatter)
	//formatter.FullTimestamp = true
	//formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	logger.Formatter = formatter

	// create the mementos file if it does not exist
	if _, err := os.Stat(*dataFile); err != nil {
		if os.IsNotExist(err) {
			_, err = createFile()
			if err != nil {
				panic("Failed to create data file: " + *dataFile)
			}
			pr.info("%s was be created", *dataFile)

		} else {
			panic(err)
		}
	}

}

func main() {
	args := flag.Args()

	var command string
	if len(args) > 0 {
		command = args[0]
	} else {
		command = "fetch"
	}
	var err error
	switch command {
	case "add":
		err = add()
	case "fetch":
		fetch()
	case "rm", "del":
		err = remove()
	case "modify", "mod":
		err = modify()
	case "list", "ls":
		err = list()
	case "version":
		fmt.Println(version)
	case "help":
		help()
	default:
		pr.error("Action `%s` is invalid", command)
		help()
	}
	if err != nil {
		pr.error(err.Error())
	}

	return
}

// print help screen
func help() {
	usage := `
Usage: mementor [OPTIONS...] ACTION [arguments...]

ACTIONS
	add		Add new memento.
	fetch		Display a random memento.
	modify		Modify an existing memento.
	rm		Remove a memento.
	help		Display this help.
	list		List all mementos.
	version		Display the current version.

OPTIONS
`
	fmt.Print(usage)
	flag.PrintDefaults()
}

// list all mementos
// TODO color lines according to their priority
func list() error {
	mementos, err := readMementos()
	if err != nil {
		return err
	}

	pr.underscore(" ID   Age         Pri  Description")
	cfg := timeago.NoMax(timeago.English)
	cfg.PastSuffix = ""
	for _, m := range mementos {
		t := time.Unix(m.Time, 0)
		fmt.Printf("%3d   %10s  %3d  %s\n", m.Id, cfg.Format(t), m.Priority, m.Msg)
	}

	pr.info("\n%d mementos total.\n", len(mementos))
	return nil
}

// print a single random memento message
// TODO higher priority items should be fetched more often
func fetch() (err error) {
	var n int
	mementos, err := readMementos()
	if err != nil {
		return err
	}
	if len(mementos) < 1 {
		return
	} else {
		rand.Seed(time.Now().Unix())
		n = rand.Intn(len(mementos))
	}
	fmt.Println(mementos[n].Msg)
	return
}

// modifies an existing memento
// Example:
// mementor mod 123 priority:3
func modify() error {
	var args []string = flag.Args()
	if len(args) < 3 {
		return errors.New("Not enough arguments")
	}
	id, err := parseId()
	if err != nil {
		return err
	}
	// read all mementos
	mementos, err := readMementos()
	if err != nil {
		return err
	}
	n, ok := findMementoById(mementos, id)
	if !ok {
		return fmt.Errorf("Memento %d does not exist", id)
	}
	logger.Debugf("found memento at %d", n)
	m := mementos[n]
	mod := strings.Split(args[2], ":")
	if len(mod) < 2 {
		return fmt.Errorf("Your modification must be in the form of property:value")
	}

	rePriority := regexp.MustCompile("^pri.*")
	reMsg := regexp.MustCompile("^m.*")
	switch {
	case rePriority.MatchString(mod[0]):
		value, err := strconv.ParseInt(mod[1], 10, 0)
		if err != nil {
			return fmt.Errorf("Not a number: %v", mod[1])
		}
		m.Priority = int(value)
		if err := writeMementos(mementos); err != nil {
			return err
		}
	case reMsg.MatchString(mod[0]):
		m.Msg = mod[0]
		if err := writeMementos(mementos); err != nil {
			return err
		}

	default:
		return fmt.Errorf("You are trying to modify invalid property: %s", mod[0])
	}

	return nil
}

// add a new memento to the stack
func add() error {
	var args []string = flag.Args()
	if len(args) < 2 {
		return errors.New("Please specify the message.")
	}
	// concat the remaining arguments as a message string
	msg := strings.Join(args[1:], " ")
	mementos, err := readMementos()
	var lastId int
	if len(mementos) < 1 {
		lastId = 0
	} else {
		lastMemento := mementos[len(mementos)-1]
		lastId = lastMemento.Id
	}
	m := Memento{
		Id:       lastId + 1,
		Msg:      msg,
		Time:     time.Now().Unix(),
		Priority: 1,
	}
	logger.Debugf("Writing %+v", m)
	if err != nil {
		return err
	}
	mementos = append(mementos, &m)
	err = writeMementos(mementos)
	return err
}

// remove a memento from the stack
func remove() error {
	id, err := parseId()
	if err != nil {
		return err
	}

	// read all mementos
	mementos, err := readMementos()
	if err != nil {
		return err
	}
	// do a binary search for the memento. It should be sorted in ascending order
	n, ok := findMementoById(mementos, id)
	if !ok {
		return fmt.Errorf("Memento %d does not exist", id)
	}
	logger.Debugf("found memento at %d", n)

	before := mementos[:n]
	after := mementos[n+1:]
	mementos = append(before, after...)
	writeMementos(mementos)
	return nil
}

// return parsed mementos from the passed file
//func readMementos() ([]*Memento, error) {
func readMementos() ([]*Memento, error) {
	logger.Debugf("opening %s", *dataFile)
	r, err := os.Open(*dataFile)
	if err != nil {
		return nil, err
	}
	dec := json.NewDecoder(r)
	var mementos []*Memento
	if err := dec.Decode(&mementos); err != nil && err != io.EOF {
		return nil, err
	}
	return mementos, nil
}

// write mementos into the file as a JSON string
func writeMementos(mementos []*Memento) (err error) {
	var file *os.File
	// truncate the file
	file, err = createFile()
	if err != nil {
		return err
	}

	s, err := json.Marshal(mementos)
	if err != nil {
		return err
	}
	written, err := file.Write(s)
	fmt.Printf("%d bytes written\n", written)
	return
}

// creates an empty file or truncates an existing file
func createFile() (file *os.File, err error) {
	// create directory if necessary
	dir := filepath.Dir(*dataFile)
	if _, err = os.Stat(dir); err != nil {
		fmt.Printf("Creating directory %s\n", dir)
		err = os.MkdirAll(dir, os.ModeDir|0700)
		if err != nil {
			return nil, fmt.Errorf("Failed to create directory for the data file at %s.\n%s", dir, err)
		}
	}
	file, err = os.Create(*dataFile)
	if err != nil {
		return nil, err
	}
	return file, nil
}

// returns index at which the specified memento is. The
// second result is false if the memento is not found.
func findMementoById(mementos []*Memento, id int) (int, bool) {
	count := len(mementos)
	n := sort.Search(count, func(i int) bool {
		return mementos[i].Id >= id
	})
	if n < count && mementos[n].Id == id {
		return n, true
	}
	// not found
	return 0, false
}

// parses arguments, retrieves an ID
func parseId() (int, error) {
	var args []string = flag.Args()
	if len(args) < 2 {
		return 0, errors.New("Missing memento Id in arguments")
	}
	id, err := strconv.ParseInt(args[1], 10, 0)
	if err != nil || id < 0 {
		return 0, fmt.Errorf("Invalid memento Id: %v", args[1])
	}
	return int(id), nil

}
<commit_msg>fixed version<commit_after>/**
 * Package main provides all the functionality behind the mementor tool.
 *
 * Mementor is a command line utility for displaying, adding and deleting mementos. Mementos are
 * messages describing things we need to be reminded of regularly.
 *
 *
 * @author Richard Laffers <rlaffers@gmail.com
 * @copyright Richard Laffers <rlaffers@gmail.com,
 * @package default
 * @version $Id$
 */
package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"math/rand"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/xeonx/timeago"
)

// Memento represents a record in mementor.
type Memento struct {
	Id       int
	Msg      string
	Time     int64
	Priority int
}

const (
	version = "0.1.2"
)

type print struct {
}

func (p *print) info(msg string, args ...interface{}) {
	fmt.Printf("\x1b[36;1m"+msg+"\n\x1b[0m", args...)
}

func (p *print) error(msg string, args ...interface{}) {
	fmt.Printf("\x1b[31;1m"+msg+"\n\x1b[0m", args...)
}

func (p *print) underscore(msg string, args ...interface{}) {
	fmt.Printf("\x1b[4;1m"+msg+"\n\x1b[0m", args...)
}

var (
	dataFile *string
	debug    = flag.Bool("debug", false, "Turn debugging on.")
	logger   *logrus.Logger
	pr       = print{}
)

func init() {
	home := os.Getenv("HOME")
	if home == "" {
		panic("HOME variable is not set")
	}

	dataFile = flag.String("f", home+"/.mementor/mementos.json", "Path to the mementos storage file.")
	// parse flags
	flag.Parse()
	logger = logrus.New()
	if *debug {
		logger.Level = logrus.DebugLevel
	} else {
		logger.Level = logrus.InfoLevel
	}
	formatter := new(logrus.TextFormatter)
	//formatter.FullTimestamp = true
	//formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	logger.Formatter = formatter

	// create the mementos file if it does not exist
	if _, err := os.Stat(*dataFile); err != nil {
		if os.IsNotExist(err) {
			_, err = createFile()
			if err != nil {
				panic("Failed to create data file: " + *dataFile)
			}
			pr.info("%s was be created", *dataFile)

		} else {
			panic(err)
		}
	}

}

func main() {
	args := flag.Args()

	var command string
	if len(args) > 0 {
		command = args[0]
	} else {
		command = "fetch"
	}
	var err error
	switch command {
	case "add":
		err = add()
	case "fetch":
		fetch()
	case "rm", "del":
		err = remove()
	case "modify", "mod":
		err = modify()
	case "list", "ls":
		err = list()
	case "version":
		fmt.Println(version)
	case "help":
		help()
	default:
		pr.error("Action `%s` is invalid", command)
		help()
	}
	if err != nil {
		pr.error(err.Error())
	}

	return
}

// print help screen
func help() {
	usage := `
Usage: mementor [OPTIONS...] ACTION [arguments...]

ACTIONS
	add		Add new memento.
	fetch		Display a random memento.
	modify		Modify an existing memento.
	rm		Remove a memento.
	help		Display this help.
	list		List all mementos.
	version		Display the current version.

OPTIONS
`
	fmt.Print(usage)
	flag.PrintDefaults()
}

// list all mementos
// TODO color lines according to their priority
func list() error {
	mementos, err := readMementos()
	if err != nil {
		return err
	}

	pr.underscore(" ID   Age         Pri  Description")
	cfg := timeago.NoMax(timeago.English)
	cfg.PastSuffix = ""
	for _, m := range mementos {
		t := time.Unix(m.Time, 0)
		fmt.Printf("%3d   %10s  %3d  %s\n", m.Id, cfg.Format(t), m.Priority, m.Msg)
	}

	pr.info("\n%d mementos total.\n", len(mementos))
	return nil
}

// print a single random memento message
// TODO higher priority items should be fetched more often
func fetch() (err error) {
	var n int
	mementos, err := readMementos()
	if err != nil {
		return err
	}
	if len(mementos) < 1 {
		return
	} else {
		rand.Seed(time.Now().Unix())
		n = rand.Intn(len(mementos))
	}
	fmt.Println(mementos[n].Msg)
	return
}

// modifies an existing memento
// Example:
// mementor mod 123 priority:3
func modify() error {
	var args []string = flag.Args()
	if len(args) < 3 {
		return errors.New("Not enough arguments")
	}
	id, err := parseId()
	if err != nil {
		return err
	}
	// read all mementos
	mementos, err := readMementos()
	if err != nil {
		return err
	}
	n, ok := findMementoById(mementos, id)
	if !ok {
		return fmt.Errorf("Memento %d does not exist", id)
	}
	logger.Debugf("found memento at %d", n)
	m := mementos[n]
	mod := strings.Split(args[2], ":")
	if len(mod) < 2 {
		return fmt.Errorf("Your modification must be in the form of property:value")
	}

	rePriority := regexp.MustCompile("^pri.*")
	reMsg := regexp.MustCompile("^m.*")
	switch {
	case rePriority.MatchString(mod[0]):
		value, err := strconv.ParseInt(mod[1], 10, 0)
		if err != nil {
			return fmt.Errorf("Not a number: %v", mod[1])
		}
		m.Priority = int(value)
		if err := writeMementos(mementos); err != nil {
			return err
		}
	case reMsg.MatchString(mod[0]):
		m.Msg = mod[0]
		if err := writeMementos(mementos); err != nil {
			return err
		}

	default:
		return fmt.Errorf("You are trying to modify invalid property: %s", mod[0])
	}

	return nil
}

// add a new memento to the stack
func add() error {
	var args []string = flag.Args()
	if len(args) < 2 {
		return errors.New("Please specify the message.")
	}
	// concat the remaining arguments as a message string
	msg := strings.Join(args[1:], " ")
	mementos, err := readMementos()
	var lastId int
	if len(mementos) < 1 {
		lastId = 0
	} else {
		lastMemento := mementos[len(mementos)-1]
		lastId = lastMemento.Id
	}
	m := Memento{
		Id:       lastId + 1,
		Msg:      msg,
		Time:     time.Now().Unix(),
		Priority: 1,
	}
	logger.Debugf("Writing %+v", m)
	if err != nil {
		return err
	}
	mementos = append(mementos, &m)
	err = writeMementos(mementos)
	return err
}

// remove a memento from the stack
func remove() error {
	id, err := parseId()
	if err != nil {
		return err
	}

	// read all mementos
	mementos, err := readMementos()
	if err != nil {
		return err
	}
	// do a binary search for the memento. It should be sorted in ascending order
	n, ok := findMementoById(mementos, id)
	if !ok {
		return fmt.Errorf("Memento %d does not exist", id)
	}
	logger.Debugf("found memento at %d", n)

	before := mementos[:n]
	after := mementos[n+1:]
	mementos = append(before, after...)
	writeMementos(mementos)
	return nil
}

// return parsed mementos from the passed file
//func readMementos() ([]*Memento, error) {
func readMementos() ([]*Memento, error) {
	logger.Debugf("opening %s", *dataFile)
	r, err := os.Open(*dataFile)
	if err != nil {
		return nil, err
	}
	dec := json.NewDecoder(r)
	var mementos []*Memento
	if err := dec.Decode(&mementos); err != nil && err != io.EOF {
		return nil, err
	}
	return mementos, nil
}

// write mementos into the file as a JSON string
func writeMementos(mementos []*Memento) (err error) {
	var file *os.File
	// truncate the file
	file, err = createFile()
	if err != nil {
		return err
	}

	s, err := json.Marshal(mementos)
	if err != nil {
		return err
	}
	written, err := file.Write(s)
	fmt.Printf("%d bytes written\n", written)
	return
}

// creates an empty file or truncates an existing file
func createFile() (file *os.File, err error) {
	// create directory if necessary
	dir := filepath.Dir(*dataFile)
	if _, err = os.Stat(dir); err != nil {
		fmt.Printf("Creating directory %s\n", dir)
		err = os.MkdirAll(dir, os.ModeDir|0700)
		if err != nil {
			return nil, fmt.Errorf("Failed to create directory for the data file at %s.\n%s", dir, err)
		}
	}
	file, err = os.Create(*dataFile)
	if err != nil {
		return nil, err
	}
	return file, nil
}

// returns index at which the specified memento is. The
// second result is false if the memento is not found.
func findMementoById(mementos []*Memento, id int) (int, bool) {
	count := len(mementos)
	n := sort.Search(count, func(i int) bool {
		return mementos[i].Id >= id
	})
	if n < count && mementos[n].Id == id {
		return n, true
	}
	// not found
	return 0, false
}

// parses arguments, retrieves an ID
func parseId() (int, error) {
	var args []string = flag.Args()
	if len(args) < 2 {
		return 0, errors.New("Missing memento Id in arguments")
	}
	id, err := strconv.ParseInt(args[1], 10, 0)
	if err != nil || id < 0 {
		return 0, fmt.Errorf("Invalid memento Id: %v", args[1])
	}
	return int(id), nil

}
<|endoftext|>


<commit_before>package paymentlog

import (
	"sync"
)

type MemoryStore struct {
	paymentLogs map[string]*PaymentLog
	failureLogs map[string]*FailureLog
	sync.Mutex
}

func NewMemoryStore() *MemoryStore {
	return &MemoryStore{
		paymentLogs: make(map[string]*PaymentLog),
		failureLogs: make(map[string]*FailureLog),
	}
}

func (store *MemoryStore) StorePaymentLog(log PaymentLog) error {
	store.Lock()
	defer store.Unlock()
	if _, ok := store.paymentLogs[log.ID]; ok {
		return AlreadyExists
	}
	store.paymentLogs[log.ID] = &log
	return nil
}

func (store *MemoryStore) UpdatePaymentLog(id string, change PaymentLogChange) error {
	store.Lock()
	defer store.Unlock()
	if _, ok := store.paymentLogs[id]; !ok {
		return LogNotFound
	}
	if change.Amount != nil {
		store.paymentLogs[id].Amount = *change.Amount
	}
	if change.Description != nil {
		store.paymentLogs[id].Description = *change.Description
	}
	if change.Source != nil {
		store.paymentLogs[id].Source = *change.Source
	}
	if change.SourceID != nil {
		store.paymentLogs[id].SourceID = *change.SourceID
	}
	if change.Created != nil {
		store.paymentLogs[id].Created = *change.Created
	}
	if change.Updated != nil {
		store.paymentLogs[id].Updated = *change.Updated
	}
	if change.Status != nil {
		store.paymentLogs[id].Status = *change.Status
	}
	if change.Currency != nil {
		store.paymentLogs[id].Currency = *change.Currency
	}
	return nil
}

func (store *MemoryStore) DeletePaymentLog(id string) error {
	store.Lock()
	defer store.Unlock()
	if _, ok := store.paymentLogs[id]; !ok {
		return LogNotFound
	}
	delete(store.paymentLogs, id)
	return nil
}

func (store *MemoryStore) GetPaymentLog(id string) (PaymentLog, error) {
	store.Lock()
	defer store.Unlock()
	if log, ok := store.paymentLogs[id]; !ok || log == nil {
		return PaymentLog{}, LogNotFound
	} else {
		return *log, nil
	}
}

func (store *MemoryStore) ListPaymentLogsByCampaign(id string, num, offset int) ([]PaymentLog, error) {
	store.Lock()
	defer store.Unlock()
	results := make([]PaymentLog, 0)
	for _, log := range store.paymentLogs {
		if log == nil {
			continue
		}
		if log.CampaignID == id {
			results = append(results, *log)
		}
	}
	return SortLogsByCreated(results), nil
}
<commit_msg>Purposefully failing build.<commit_after>package paymentlog

import (
	"sync"
)

type MemoryStore struct {
	paymentLogs map[string]*PaymentLog
	failureLogs map[string]*FailureLog
	sync.Mutex
}

func NewMemoryStore() *MemoryStore {
	return &MemoryStore{
		paymentLogs: make(map[string]*PaymentLog),
		failureLogs: make(map[string]*FailureLog),
	}
}

func (store *MemoryStore) StorePaymentLog(log PaymentLog) error {
	store.Lock()
	defer store.Unlock()
	if _, ok := store.paymentLogs[log.ID]; ok {
		return AlreadyExists
	}
	store.paymentLogs[log.ID] = &log
	return nil
}

func (store *MemoryStore) UpdatePaymentLog(id string, change PaymentLogChange) error {
	store.Lock()
	defer store.Unlock()
	if _, ok := store.paymentLogs[id]; !ok {
		return LogNotFound
	}
	if change.Amount != nil {
		store.paymentLogs[id].Amount = *change.Amount
	}
	if change.Description != nil {
		store.paymentLogs[id].Description = *change.Description
	}
	if change.Source != nil {
		store.paymentLogs[id].Source = *change.Source
	}
	if change.SourceID != nil {
		store.paymentLogs[id].SourceID = *change.SourceID
	}
	if change.Created != nil {
		store.paymentLogs[id].Created = *change.Created
	}
	if change.Updated != nil {
		store.paymentLogs[id].Updated = *change.Updated
	}
	if change.Status != nil {
		store.paymentLogs[id].Status = *change.Status
	}
	if change.Currency != nil {
		store.paymentLogs[id].Currency = *change.Currency
	}
	return nil
}

func (store *MemoryStore) DeletePaymentLog(id string) error {
	store.Lock()
	defer store.Unlock()
	if _, ok := store.paymentLogs[id]; !ok {
		return LogNotFound
	}
	delete(store.paymentLogs, id)
	return nil
}

func (store *MemoryStore) GetPaymentLog(id string) (PaymentLog, error) {
	store.Lock()
	defer store.Unlock()
	if log, ok := store.paymentLogs[id]; !ok || log == nil {
		return PaymentLog{}, LogNotFound
	} else {
		return *log, nil
	}
}

func (store *MemoryStore) ListPaymentLogsByCampaign(id string, num, offset int) ([]PaymentLog, error) {
	store.Lock()
	defer store.Unlock()
	results := make([]PaymentLog, 0)
	for _, log := range store.paymentLogs {
		if log == nil {
			continue
		}
		if log.CampaignID == id {
			results = append(results, *log)
		}
	}
	//return SortLogsByCreated(results), nil
	return results, nil
}
<|endoftext|>


<commit_before>package tui

import "image"

var _ Widget = &List{}

// List is a widget for displaying and selecting items.
type List struct {
	WidgetBase

	items    []string
	selected int
	pos      int

	onItemActivated    func(*List)
	onSelectionChanged func(*List)
}

// NewList returns a new List with no selection.
func NewList() *List {
	return &List{
		selected: -1,
	}
}

// Draw draws the list.
func (l *List) Draw(p *Painter) {
	for i, item := range l.items {
		style := "list.item"
		if i == l.selected-l.pos {
			style += ".selected"
		}
		p.WithStyle(style, func(p *Painter) {
			p.FillRect(0, i, l.Size().X, 1)
			p.DrawText(0, i, item)
		})
	}
}

// SizeHint returns the recommended size for the list.
func (l *List) SizeHint() image.Point {
	var width int
	for _, item := range l.items {
		if w := stringWidth(item); w > width {
			width = w
		}
	}
	return image.Point{width, len(l.items)}
}

func (l *List) OnEvent(ev Event) {
	if !l.IsFocused() || ev.Type != EventKey {
		return
	}

	switch ev.Key {
	case KeyArrowUp:
		l.moveUp()
	case KeyArrowDown:
		l.moveDown()
	case KeyEnter:
		if l.onItemActivated != nil {
			l.onItemActivated(l)
		}
	}

	switch ev.Ch {
	case 'k':
		l.moveUp()
	case 'j':
		l.moveDown()
	}
}

func (l *List) moveUp() {
	if l.selected > 0 {
		l.selected--

		if l.selected < l.pos {
			l.pos--
		}
	}
	if l.onSelectionChanged != nil {
		l.onSelectionChanged(l)
	}
}

func (l *List) moveDown() {
	if l.selected < len(l.items)-1 {
		l.selected++
		if l.selected >= l.pos+len(l.items) {
			l.pos++
		}
	}
	if l.onSelectionChanged != nil {
		l.onSelectionChanged(l)
	}
}

func (l *List) AddItems(items ...string) {
	l.items = append(l.items, items...)
}

func (l *List) RemoveItems() {
	l.items = []string{}
	l.pos = 0
	l.selected = -1
	if l.onSelectionChanged != nil {
		l.onSelectionChanged(l)
	}
}

func (l *List) RemoveItem(i int) {
	// Adjust pos and selected before removing.
	if l.pos >= len(l.items) {
		l.pos--
	}
	if l.selected == i {
		l.selected = -1
	} else if l.selected > i {
		l.selected--
	}

	// Copy items following i to position i.
	copy(l.items[i:], l.items[i+1:])

	// Shrink items by one.
	l.items[len(l.items)-1] = ""
	l.items = l.items[:len(l.items)-1]
}

func (l *List) Length() int {
	return len(l.items)
}

func (l *List) SetSelected(i int) {
	l.selected = i
}

func (l *List) Selected() int {
	return l.selected
}

func (l *List) SelectedItem() string {
	return l.items[l.selected]
}

func (l *List) OnItemActivated(fn func(*List)) {
	l.onItemActivated = fn
}

func (l *List) OnSelectionChanged(fn func(*List)) {
	l.onSelectionChanged = fn
}
<commit_msg>Add call to "onChange" handler in RemoveItem()<commit_after>package tui

import "image"

var _ Widget = &List{}

// List is a widget for displaying and selecting items.
type List struct {
	WidgetBase

	items    []string
	selected int
	pos      int

	onItemActivated    func(*List)
	onSelectionChanged func(*List)
}

// NewList returns a new List with no selection.
func NewList() *List {
	return &List{
		selected: -1,
	}
}

// Draw draws the list.
func (l *List) Draw(p *Painter) {
	for i, item := range l.items {
		style := "list.item"
		if i == l.selected-l.pos {
			style += ".selected"
		}
		p.WithStyle(style, func(p *Painter) {
			p.FillRect(0, i, l.Size().X, 1)
			p.DrawText(0, i, item)
		})
	}
}

// SizeHint returns the recommended size for the list.
func (l *List) SizeHint() image.Point {
	var width int
	for _, item := range l.items {
		if w := stringWidth(item); w > width {
			width = w
		}
	}
	return image.Point{width, len(l.items)}
}

func (l *List) OnEvent(ev Event) {
	if !l.IsFocused() || ev.Type != EventKey {
		return
	}

	switch ev.Key {
	case KeyArrowUp:
		l.moveUp()
	case KeyArrowDown:
		l.moveDown()
	case KeyEnter:
		if l.onItemActivated != nil {
			l.onItemActivated(l)
		}
	}

	switch ev.Ch {
	case 'k':
		l.moveUp()
	case 'j':
		l.moveDown()
	}
}

func (l *List) moveUp() {
	if l.selected > 0 {
		l.selected--

		if l.selected < l.pos {
			l.pos--
		}
	}
	if l.onSelectionChanged != nil {
		l.onSelectionChanged(l)
	}
}

func (l *List) moveDown() {
	if l.selected < len(l.items)-1 {
		l.selected++
		if l.selected >= l.pos+len(l.items) {
			l.pos++
		}
	}
	if l.onSelectionChanged != nil {
		l.onSelectionChanged(l)
	}
}

func (l *List) AddItems(items ...string) {
	l.items = append(l.items, items...)
}

func (l *List) RemoveItems() {
	l.items = []string{}
	l.pos = 0
	l.selected = -1
	if l.onSelectionChanged != nil {
		l.onSelectionChanged(l)
	}
}

func (l *List) RemoveItem(i int) {
	// Adjust pos and selected before removing.
	if l.pos >= len(l.items) {
		l.pos--
	}
	if l.selected == i {
		l.selected = -1
	} else if l.selected > i {
		l.selected--
	}

	// Copy items following i to position i.
	copy(l.items[i:], l.items[i+1:])

	// Shrink items by one.
	l.items[len(l.items)-1] = ""
	l.items = l.items[:len(l.items)-1]

	if l.onSelectionChanged != nil {
		l.onSelectionChanged(l)
	}
}

func (l *List) Length() int {
	return len(l.items)
}

func (l *List) SetSelected(i int) {
	l.selected = i
}

func (l *List) Selected() int {
	return l.selected
}

func (l *List) SelectedItem() string {
	return l.items[l.selected]
}

func (l *List) OnItemActivated(fn func(*List)) {
	l.onItemActivated = fn
}

func (l *List) OnSelectionChanged(fn func(*List)) {
	l.onSelectionChanged = fn
}
<|endoftext|>


<commit_before>package magick

// #include <magick/api.h>
import "C"

func (im *Image) IsOrphan() bool {
	return im.image.next == nil && im.image.previous == nil
}

func (im *Image) NFrames() int {
	if im.parent != nil {
		return 1
	}
	return int(C.GetImageListLength(im.image))
}

func (im *Image) FrameIndex() int {
	return int(C.GetImageIndexInList(im.image))
}

func (im *Image) Duration() int {
	if im.parent != nil {
		return im.Delay()
	}
	duration := 0
	for cur := C.GetFirstImageInList(im.image); cur != nil; cur = (*C.Image)(cur.next) {
		duration += int(cur.delay)
	}
	return duration
}

func (im *Image) Frame(idx int) (*Image, error) {
	if im.IsOrphan() && idx == 0 {
		return im, nil
	}
	image := C.GetImageFromList(im.image, magickLong(idx))
	if image == nil {
		return nil, ErrNoSuchFrame
	}
	return newImage(image, im), nil
}

func (im *Image) First() *Image {
	first, _ := im.Frame(0)
	return first
}

func (im *Image) Last() *Image {
	last, _ := im.Frame(im.NFrames() - 1)
	return last
}

func (im *Image) Prev() *Image {
	prev := C.GetPreviousImageInList(im.image)
	if prev == nil {
		return nil
	}
	return newImage(prev, im)
}

func (im *Image) Next() *Image {
	next := C.GetNextImageInList(im.image)
	if next == nil {
		return nil
	}
	return newImage(next, im)
}

func (im *Image) Reverse() {
	C.ReverseImageList(&im.image)
}

/*func (im *Image) InsertAfter(image *Image) error {
	cloned, err := image.Clone()
	if err != nil {
		return err
	}
	first := cloned.First()
	first.prev = im
	if im.next != nil {
		im.next.prev = cloned.Last()
	}
	im.next = first
	return nil
}

func (im *Image) InsertBefore(image *Image) error {
	cloned, err := image.CloneAll()
	if err != nil {
		return err
	}
	last := cloned.Last()
	last.next = im
	if im.prev != nil {
		im.prev.next = cloned.First()
	}
	image.prev = last
	return nil
}
*/

func (im *Image) Prepend(image *Image) error {
	// TODO: Don't clone, refcount
	cloned, err := image.Clone()
	if err != nil {
		return err
	}
	dontFree(cloned)
	C.PrependImageToList(&im.image, cloned.image)
	return nil
}

func (im *Image) Append(image *Image) error {
	// TODO: Don't clone, refcount
	cloned, err := image.Clone()
	if err != nil {
		return err
	}
	dontFree(cloned)
	C.AppendImageToList(&im.image, cloned.image)
	return nil
}

func (im *Image) RemoveIndex(idx int) bool {
	if frame, _ := im.Frame(idx); frame != nil {
		return frame.Remove()
	}
	return false
}

func (im *Image) RemoveFirst() bool {
	return im.RemoveIndex(0)
}

func (im *Image) RemoveLast() bool {
	return im.RemoveIndex(im.NFrames() - 1)
}

func (im *Image) Remove() bool {
	if im.parent != nil && !im.parent.IsOrphan() {
		// Don't use DeleteImageFromList, since it calls DestroyImage,
		// bypassing our refcounting.
		if im.image == im.parent.image {
			im.parent.image = (*C.Image)(im.parent.image.next)
		}
		if p := (*C.Image)(im.image.previous); p != nil {
			p.next = im.image.next
		}
		if n := (*C.Image)(im.image.next); n != nil {
			n.previous = im.image.previous
		}
		im.image.previous = nil
		im.image.next = nil
		im.parent = nil
		// im already holds a reference, remove the reference that
		// the parent had.
		unrefImage(im.image)
		return true
	}
	return false
}

func (im *Image) Apply(f func(*Image) (*Image, error)) (*Image, error) {
	if im.IsOrphan() || im.parent != nil {
		return f(im)
	}
	var ex C.ExceptionInfo
	C.GetExceptionInfo(&ex)
	defer C.DestroyExceptionInfo(&ex)
	cur, free := im.coalescedCImage(&ex)
	if cur == nil {
		return nil, exError(&ex, "coalescing")
	}
	if free {
		defer C.DestroyImageList(cur)
	}
	buf := &Image{
		image: cur,
	}
	first, err := f(buf)
	if err != nil {
		return nil, err
	}
	prev := first.image
	for cur := (*C.Image)(im.image.next); cur != nil; cur = (*C.Image)(cur.next) {
		buf.image = cur
		res, err := f(buf)
		if err != nil {
			return nil, err
		}
		dontFree(res)
		img := res.image
		prev.next = (*C.struct__Image)(img)
		img.previous = (*C.struct__Image)(img)
		prev = img
	}
	return first, nil
}
<commit_msg>Initial the buf Image in Apply with its parent.<commit_after>package magick

// #include <magick/api.h>
import "C"

func (im *Image) IsOrphan() bool {
	return im.image.next == nil && im.image.previous == nil
}

func (im *Image) NFrames() int {
	if im.parent != nil {
		return 1
	}
	return int(C.GetImageListLength(im.image))
}

func (im *Image) FrameIndex() int {
	return int(C.GetImageIndexInList(im.image))
}

func (im *Image) Duration() int {
	if im.parent != nil {
		return im.Delay()
	}
	duration := 0
	for cur := C.GetFirstImageInList(im.image); cur != nil; cur = (*C.Image)(cur.next) {
		duration += int(cur.delay)
	}
	return duration
}

func (im *Image) Frame(idx int) (*Image, error) {
	if im.IsOrphan() && idx == 0 {
		return im, nil
	}
	image := C.GetImageFromList(im.image, magickLong(idx))
	if image == nil {
		return nil, ErrNoSuchFrame
	}
	return newImage(image, im), nil
}

func (im *Image) First() *Image {
	first, _ := im.Frame(0)
	return first
}

func (im *Image) Last() *Image {
	last, _ := im.Frame(im.NFrames() - 1)
	return last
}

func (im *Image) Prev() *Image {
	prev := C.GetPreviousImageInList(im.image)
	if prev == nil {
		return nil
	}
	return newImage(prev, im)
}

func (im *Image) Next() *Image {
	next := C.GetNextImageInList(im.image)
	if next == nil {
		return nil
	}
	return newImage(next, im)
}

func (im *Image) Reverse() {
	C.ReverseImageList(&im.image)
}

/*func (im *Image) InsertAfter(image *Image) error {
	cloned, err := image.Clone()
	if err != nil {
		return err
	}
	first := cloned.First()
	first.prev = im
	if im.next != nil {
		im.next.prev = cloned.Last()
	}
	im.next = first
	return nil
}

func (im *Image) InsertBefore(image *Image) error {
	cloned, err := image.CloneAll()
	if err != nil {
		return err
	}
	last := cloned.Last()
	last.next = im
	if im.prev != nil {
		im.prev.next = cloned.First()
	}
	image.prev = last
	return nil
}
*/

func (im *Image) Prepend(image *Image) error {
	// TODO: Don't clone, refcount
	cloned, err := image.Clone()
	if err != nil {
		return err
	}
	dontFree(cloned)
	C.PrependImageToList(&im.image, cloned.image)
	return nil
}

func (im *Image) Append(image *Image) error {
	// TODO: Don't clone, refcount
	cloned, err := image.Clone()
	if err != nil {
		return err
	}
	dontFree(cloned)
	C.AppendImageToList(&im.image, cloned.image)
	return nil
}

func (im *Image) RemoveIndex(idx int) bool {
	if frame, _ := im.Frame(idx); frame != nil {
		return frame.Remove()
	}
	return false
}

func (im *Image) RemoveFirst() bool {
	return im.RemoveIndex(0)
}

func (im *Image) RemoveLast() bool {
	return im.RemoveIndex(im.NFrames() - 1)
}

func (im *Image) Remove() bool {
	if im.parent != nil && !im.parent.IsOrphan() {
		// Don't use DeleteImageFromList, since it calls DestroyImage,
		// bypassing our refcounting.
		if im.image == im.parent.image {
			im.parent.image = (*C.Image)(im.parent.image.next)
		}
		if p := (*C.Image)(im.image.previous); p != nil {
			p.next = im.image.next
		}
		if n := (*C.Image)(im.image.next); n != nil {
			n.previous = im.image.previous
		}
		im.image.previous = nil
		im.image.next = nil
		im.parent = nil
		// im already holds a reference, remove the reference that
		// the parent had.
		unrefImage(im.image)
		return true
	}
	return false
}

func (im *Image) Apply(f func(*Image) (*Image, error)) (*Image, error) {
	if im.IsOrphan() || im.parent != nil {
		return f(im)
	}
	var ex C.ExceptionInfo
	C.GetExceptionInfo(&ex)
	defer C.DestroyExceptionInfo(&ex)
	cur, free := im.coalescedCImage(&ex)
	if cur == nil {
		return nil, exError(&ex, "coalescing")
	}
	if free {
		defer C.DestroyImageList(cur)
	}
	buf := &Image{
		parent: im,
		image:  cur,
	}
	first, err := f(buf)
	if err != nil {
		return nil, err
	}
	prev := first.image
	for cur := (*C.Image)(im.image.next); cur != nil; cur = (*C.Image)(cur.next) {
		buf.image = cur
		res, err := f(buf)
		if err != nil {
			return nil, err
		}
		dontFree(res)
		img := res.image
		prev.next = (*C.struct__Image)(img)
		img.previous = (*C.struct__Image)(img)
		prev = img
	}
	return first, nil
}
<|endoftext|>


<commit_before>// This package implements the Levenshtein algorithm for computing the
// similarity between two strings. The central function is MatrixForStrings,
// which computes the Levenshtein matrix. The functions DistanceForMatrix,
// EditScriptForMatrix and RatioForMatrix read various interesting properties
// off the matrix. The package also provides the convenience functions
// DistanceForStrings, EditScriptForStrings and RatioForStrings for going
// directly from two strings to the property of interest.
package levenshtein

import (
	"fmt"
	"io"
	"os"
)

type EditOperation int

const (
	Ins = iota
	Del
	Sub
	Match
)

type EditScript []EditOperation

type MatchFunction func(rune, rune) bool

type Options struct {
	InsCost int
	DelCost int
	SubCost int
	Matches MatchFunction
}

// DefaultOptions is the default options: insertion cost is 1, deletion cost is
// 1, substitution cost is 2, and two runes match iff they are the same.
var DefaultOptions Options = Options{
	InsCost: 1,
	DelCost: 1,
	SubCost: 2,
	Matches: func(sourceCharacter rune, targetCharacter rune) bool {
		return sourceCharacter == targetCharacter
	},
}

func (operation EditOperation) String() string {
	if operation == Match {
		return "match"
	} else if operation == Ins {
		return "ins"
	} else if operation == Sub {
		return "sub"
	}
	return "del"
}

// DistanceForStrings returns the edit distance between source and target.
//
// It has a runtime proportional to len(source) * len(target) and memory use
// proportional to len(target).
func DistanceForStrings(source []rune, target []rune, op Options) int {
	// We only use the current and previous row, so the matrix height is only 2.
	height := len(source) + 1
	width := len(target) + 1
	matrix := make([][]int, 2)

	// Initialize trivial distances (from/to empty string). That is, fill
	// the left column and the top row with row/column indices.
	for i := 0; i < 2; i++ {
		matrix[i] = make([]int, width)
		matrix[i][0] = i
	}
	for j := 1; j < width; j++ {
		matrix[0][j] = j
	}

	// Fill in the remaining cells: for each prefix pair, choose the
	// (edit history, operation) pair with the lowest cost.
	for i := 1; i < height; i++ {
		cur := matrix[i%2]
		prev := matrix[(i-1)%2]
		cur[0] = i
		for j := 1; j < width; j++ {
			delCost := prev[j] + op.DelCost
			matchSubCost := prev[j-1]
			if !op.Matches(source[i-1], target[j-1]) {
				matchSubCost += op.SubCost
			}
			insCost := cur[j-1] + op.InsCost
			cur[j] = min(delCost, min(matchSubCost, insCost))
		}
	}
	return matrix[(height-1)%2][width-1]
}

// DistanceForMatrix reads the edit distance off the given Levenshtein matrix.
func DistanceForMatrix(matrix [][]int) int {
	return matrix[len(matrix)-1][len(matrix[0])-1]
}

// RatioForStrings returns the Levenshtein ratio for the given strings. The
// ratio is computed as follows:
//
//     (sourceLength + targetLength - distance) / (sourceLength + targetLength)
func RatioForStrings(source []rune, target []rune, op Options) float64 {
	matrix := MatrixForStrings(source, target, op)
	return RatioForMatrix(matrix)
}

// RatioForMatrix returns the Levenshtein ratio for the given matrix. The ratio
// is computed as follows:
//
//     (sourceLength + targetLength - distance) / (sourceLength + targetLength)
func RatioForMatrix(matrix [][]int) float64 {
	sourcelength := len(matrix) - 1
	targetlength := len(matrix[0]) - 1
	sum := sourcelength + targetlength

	if sum == 0 {
		return 0
	}

	dist := DistanceForMatrix(matrix)
	return float64(sum-dist) / float64(sum)
}

// MatrixForStrings generates a 2-D array representing the dynamic programming
// table used by the Levenshtein algorithm, as described e.g. here:
// http://www.let.rug.nl/kleiweg/lev/
// The reason for putting the creation of the table into a separate function is
// that it cannot only be used for reading of the edit distance between two
// strings, but also e.g. to backtrace an edit script that provides an
// alignment between the characters of both strings.
func MatrixForStrings(source []rune, target []rune, op Options) [][]int {
	// Make a 2-D matrix. Rows correspond to prefixes of source, columns to
	// prefixes of target. Cells will contain edit distances.
	// Cf. http://www.let.rug.nl/~kleiweg/lev/levenshtein.html
	height := len(source) + 1
	width := len(target) + 1
	matrix := make([][]int, height)

	// Initialize trivial distances (from/to empty string). That is, fill
	// the left column and the top row with row/column indices.
	for i := 0; i < height; i++ {
		matrix[i] = make([]int, width)
		matrix[i][0] = i
	}
	for j := 1; j < width; j++ {
		matrix[0][j] = j
	}

	// Fill in the remaining cells: for each prefix pair, choose the
	// (edit history, operation) pair with the lowest cost.
	for i := 1; i < height; i++ {
		for j := 1; j < width; j++ {
			delCost := matrix[i-1][j] + op.DelCost
			matchSubCost := matrix[i-1][j-1]
			if !op.Matches(source[i-1], target[j-1]) {
				matchSubCost += op.SubCost
			}
			insCost := matrix[i][j-1] + op.InsCost
			matrix[i][j] = min(delCost, min(matchSubCost,
				insCost))
		}
	}
	//LogMatrix(source, target, matrix)
	return matrix
}

// EditScriptForStrings returns an optimal edit script to turn source into
// target.
func EditScriptForStrings(source []rune, target []rune, op Options) EditScript {
	return backtrace(len(source), len(target),
		MatrixForStrings(source, target, op), op)
}

// EditScriptForMatrix returns an optimal edit script based on the given
// Levenshtein matrix.
func EditScriptForMatrix(matrix [][]int, op Options) EditScript {
	return backtrace(len(matrix)-1, len(matrix[0])-1, matrix, op)
}

// WriteMatrix writes a visual representation of the given matrix for the given
// strings to the given writer.
func WriteMatrix(source []rune, target []rune, matrix [][]int, writer io.Writer) {
	fmt.Fprintf(writer, "    ")
	for _, targetRune := range target {
		fmt.Fprintf(writer, "  %c", targetRune)
	}
	fmt.Fprintf(writer, "\n")
	fmt.Fprintf(writer, "  %2d", matrix[0][0])
	for j, _ := range target {
		fmt.Fprintf(writer, " %2d", matrix[0][j+1])
	}
	fmt.Fprintf(writer, "\n")
	for i, sourceRune := range source {
		fmt.Fprintf(writer, "%c %2d", sourceRune, matrix[i+1][0])
		for j, _ := range target {
			fmt.Fprintf(writer, " %2d", matrix[i+1][j+1])
		}
		fmt.Fprintf(writer, "\n")
	}
}

// LogMatrix writes a visual representation of the given matrix for the given
// strings to os.Stderr. This function is deprecated, use
// WriteMatrix(source, target, matrix, os.Stderr) instead.
func LogMatrix(source []rune, target []rune, matrix [][]int) {
	WriteMatrix(source, target, matrix, os.Stderr)
}

func backtrace(i int, j int, matrix [][]int, op Options) EditScript {
	if i > 0 && matrix[i-1][j]+op.DelCost == matrix[i][j] {
		return append(backtrace(i-1, j, matrix, op), Del)
	}
	if j > 0 && matrix[i][j-1]+op.InsCost == matrix[i][j] {
		return append(backtrace(i, j-1, matrix, op), Ins)
	}
	if i > 0 && j > 0 && matrix[i-1][j-1]+op.SubCost == matrix[i][j] {
		return append(backtrace(i-1, j-1, matrix, op), Sub)
	}
	if i > 0 && j > 0 && matrix[i-1][j-1] == matrix[i][j] {
		return append(backtrace(i-1, j-1, matrix, op), Match)
	}
	return []EditOperation{}
}

func min(a int, b int) int {
	if b < a {
		return b
	}
	return a
}

func max(a int, b int) int {
	if b > a {
		return b
	}
	return a
}
<commit_msg>A more detailed implementation comment<commit_after>// This package implements the Levenshtein algorithm for computing the
// similarity between two strings. The central function is MatrixForStrings,
// which computes the Levenshtein matrix. The functions DistanceForMatrix,
// EditScriptForMatrix and RatioForMatrix read various interesting properties
// off the matrix. The package also provides the convenience functions
// DistanceForStrings, EditScriptForStrings and RatioForStrings for going
// directly from two strings to the property of interest.
package levenshtein

import (
	"fmt"
	"io"
	"os"
)

type EditOperation int

const (
	Ins = iota
	Del
	Sub
	Match
)

type EditScript []EditOperation

type MatchFunction func(rune, rune) bool

type Options struct {
	InsCost int
	DelCost int
	SubCost int
	Matches MatchFunction
}

// DefaultOptions is the default options: insertion cost is 1, deletion cost is
// 1, substitution cost is 2, and two runes match iff they are the same.
var DefaultOptions Options = Options{
	InsCost: 1,
	DelCost: 1,
	SubCost: 2,
	Matches: func(sourceCharacter rune, targetCharacter rune) bool {
		return sourceCharacter == targetCharacter
	},
}

func (operation EditOperation) String() string {
	if operation == Match {
		return "match"
	} else if operation == Ins {
		return "ins"
	} else if operation == Sub {
		return "sub"
	}
	return "del"
}

// DistanceForStrings returns the edit distance between source and target.
//
// It has a runtime proportional to len(source) * len(target) and memory use
// proportional to len(target).
func DistanceForStrings(source []rune, target []rune, op Options) int {
	// Note: This algorithm is a specialization of MatrixForStrings.
	// MatrixForStrings returns the full edit matrix. However, we only need a
	// single value (see DistanceForMatrix) and the main loop of the algorithm
	// only uses the current and previous row. As such we create a 2D matrix,
	// but with height 2 (enough to store current and previous row).
	height := len(source) + 1
	width := len(target) + 1
	matrix := make([][]int, 2)

	// Initialize trivial distances (from/to empty string). That is, fill
	// the left column and the top row with row/column indices.
	for i := 0; i < 2; i++ {
		matrix[i] = make([]int, width)
		matrix[i][0] = i
	}
	for j := 1; j < width; j++ {
		matrix[0][j] = j
	}

	// Fill in the remaining cells: for each prefix pair, choose the
	// (edit history, operation) pair with the lowest cost.
	for i := 1; i < height; i++ {
		cur := matrix[i%2]
		prev := matrix[(i-1)%2]
		cur[0] = i
		for j := 1; j < width; j++ {
			delCost := prev[j] + op.DelCost
			matchSubCost := prev[j-1]
			if !op.Matches(source[i-1], target[j-1]) {
				matchSubCost += op.SubCost
			}
			insCost := cur[j-1] + op.InsCost
			cur[j] = min(delCost, min(matchSubCost, insCost))
		}
	}
	return matrix[(height-1)%2][width-1]
}

// DistanceForMatrix reads the edit distance off the given Levenshtein matrix.
func DistanceForMatrix(matrix [][]int) int {
	return matrix[len(matrix)-1][len(matrix[0])-1]
}

// RatioForStrings returns the Levenshtein ratio for the given strings. The
// ratio is computed as follows:
//
//     (sourceLength + targetLength - distance) / (sourceLength + targetLength)
func RatioForStrings(source []rune, target []rune, op Options) float64 {
	matrix := MatrixForStrings(source, target, op)
	return RatioForMatrix(matrix)
}

// RatioForMatrix returns the Levenshtein ratio for the given matrix. The ratio
// is computed as follows:
//
//     (sourceLength + targetLength - distance) / (sourceLength + targetLength)
func RatioForMatrix(matrix [][]int) float64 {
	sourcelength := len(matrix) - 1
	targetlength := len(matrix[0]) - 1
	sum := sourcelength + targetlength

	if sum == 0 {
		return 0
	}

	dist := DistanceForMatrix(matrix)
	return float64(sum-dist) / float64(sum)
}

// MatrixForStrings generates a 2-D array representing the dynamic programming
// table used by the Levenshtein algorithm, as described e.g. here:
// http://www.let.rug.nl/kleiweg/lev/
// The reason for putting the creation of the table into a separate function is
// that it cannot only be used for reading of the edit distance between two
// strings, but also e.g. to backtrace an edit script that provides an
// alignment between the characters of both strings.
func MatrixForStrings(source []rune, target []rune, op Options) [][]int {
	// Make a 2-D matrix. Rows correspond to prefixes of source, columns to
	// prefixes of target. Cells will contain edit distances.
	// Cf. http://www.let.rug.nl/~kleiweg/lev/levenshtein.html
	height := len(source) + 1
	width := len(target) + 1
	matrix := make([][]int, height)

	// Initialize trivial distances (from/to empty string). That is, fill
	// the left column and the top row with row/column indices.
	for i := 0; i < height; i++ {
		matrix[i] = make([]int, width)
		matrix[i][0] = i
	}
	for j := 1; j < width; j++ {
		matrix[0][j] = j
	}

	// Fill in the remaining cells: for each prefix pair, choose the
	// (edit history, operation) pair with the lowest cost.
	for i := 1; i < height; i++ {
		for j := 1; j < width; j++ {
			delCost := matrix[i-1][j] + op.DelCost
			matchSubCost := matrix[i-1][j-1]
			if !op.Matches(source[i-1], target[j-1]) {
				matchSubCost += op.SubCost
			}
			insCost := matrix[i][j-1] + op.InsCost
			matrix[i][j] = min(delCost, min(matchSubCost,
				insCost))
		}
	}
	//LogMatrix(source, target, matrix)
	return matrix
}

// EditScriptForStrings returns an optimal edit script to turn source into
// target.
func EditScriptForStrings(source []rune, target []rune, op Options) EditScript {
	return backtrace(len(source), len(target),
		MatrixForStrings(source, target, op), op)
}

// EditScriptForMatrix returns an optimal edit script based on the given
// Levenshtein matrix.
func EditScriptForMatrix(matrix [][]int, op Options) EditScript {
	return backtrace(len(matrix)-1, len(matrix[0])-1, matrix, op)
}

// WriteMatrix writes a visual representation of the given matrix for the given
// strings to the given writer.
func WriteMatrix(source []rune, target []rune, matrix [][]int, writer io.Writer) {
	fmt.Fprintf(writer, "    ")
	for _, targetRune := range target {
		fmt.Fprintf(writer, "  %c", targetRune)
	}
	fmt.Fprintf(writer, "\n")
	fmt.Fprintf(writer, "  %2d", matrix[0][0])
	for j, _ := range target {
		fmt.Fprintf(writer, " %2d", matrix[0][j+1])
	}
	fmt.Fprintf(writer, "\n")
	for i, sourceRune := range source {
		fmt.Fprintf(writer, "%c %2d", sourceRune, matrix[i+1][0])
		for j, _ := range target {
			fmt.Fprintf(writer, " %2d", matrix[i+1][j+1])
		}
		fmt.Fprintf(writer, "\n")
	}
}

// LogMatrix writes a visual representation of the given matrix for the given
// strings to os.Stderr. This function is deprecated, use
// WriteMatrix(source, target, matrix, os.Stderr) instead.
func LogMatrix(source []rune, target []rune, matrix [][]int) {
	WriteMatrix(source, target, matrix, os.Stderr)
}

func backtrace(i int, j int, matrix [][]int, op Options) EditScript {
	if i > 0 && matrix[i-1][j]+op.DelCost == matrix[i][j] {
		return append(backtrace(i-1, j, matrix, op), Del)
	}
	if j > 0 && matrix[i][j-1]+op.InsCost == matrix[i][j] {
		return append(backtrace(i, j-1, matrix, op), Ins)
	}
	if i > 0 && j > 0 && matrix[i-1][j-1]+op.SubCost == matrix[i][j] {
		return append(backtrace(i-1, j-1, matrix, op), Sub)
	}
	if i > 0 && j > 0 && matrix[i-1][j-1] == matrix[i][j] {
		return append(backtrace(i-1, j-1, matrix, op), Match)
	}
	return []EditOperation{}
}

func min(a int, b int) int {
	if b < a {
		return b
	}
	return a
}

func max(a int, b int) int {
	if b > a {
		return b
	}
	return a
}
<|endoftext|>