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

package grpc

import (
	"fmt"
	"net"
	"sync"

	"golang.org/x/net/context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	"google.golang.org/grpc/naming"
)

// Address represents a server the client connects to.
// This is the EXPERIMENTAL API and may be changed or extended in the future.
type Address struct {
	// Addr is the server address on which a connection will be established.
	Addr string
	// Metadata is the information associated with Addr, which may be used
	// to make load balancing decision.
	Metadata interface{}
}

// BalancerConfig specifies the configurations for Balancer.
type BalancerConfig struct {
	// DialCreds is the transport credential the Balancer implementation can
	// use to dial to a remote load balancer server. The Balancer implementations
	// can ignore this if it does not need to talk to another party securely.
	DialCreds credentials.TransportCredentials
	// Dialer is the custom dialer the Balancer implementation can use to dial
	// to a remote load balancer server. The Balancer implementations
	// can ignore this if it doesn't need to talk to remote balancer.
	Dialer func(context.Context, string) (net.Conn, error)
}

// BalancerGetOptions configures a Get call.
// This is the EXPERIMENTAL API and may be changed or extended in the future.
type BalancerGetOptions struct {
	// BlockingWait specifies whether Get should block when there is no
	// connected address.
	BlockingWait bool
}

// Balancer chooses network addresses for RPCs.
// This is the EXPERIMENTAL API and may be changed or extended in the future.
type Balancer interface {
	// Start does the initialization work to bootstrap a Balancer. For example,
	// this function may start the name resolution and watch the updates. It will
	// be called when dialing.
	Start(target string, config BalancerConfig) error
	// Up informs the Balancer that gRPC has a connection to the server at
	// addr. It returns down which is called once the connection to addr gets
	// lost or closed.
	// TODO: It is not clear how to construct and take advantage of the meaningful error
	// parameter for down. Need realistic demands to guide.
	Up(addr Address) (down func(error))
	// Get gets the address of a server for the RPC corresponding to ctx.
	// i) If it returns a connected address, gRPC internals issues the RPC on the
	// connection to this address;
	// ii) If it returns an address on which the connection is under construction
	// (initiated by Notify(...)) but not connected, gRPC internals
	//  * fails RPC if the RPC is fail-fast and connection is in the TransientFailure or
	//  Shutdown state;
	//  or
	//  * issues RPC on the connection otherwise.
	// iii) If it returns an address on which the connection does not exist, gRPC
	// internals treats it as an error and will fail the corresponding RPC.
	//
	// Therefore, the following is the recommended rule when writing a custom Balancer.
	// If opts.BlockingWait is true, it should return a connected address or
	// block if there is no connected address. It should respect the timeout or
	// cancellation of ctx when blocking. If opts.BlockingWait is false (for fail-fast
	// RPCs), it should return an address it has notified via Notify(...) immediately
	// instead of blocking.
	//
	// The function returns put which is called once the rpc has completed or failed.
	// put can collect and report RPC stats to a remote load balancer.
	//
	// This function should only return the errors Balancer cannot recover by itself.
	// gRPC internals will fail the RPC if an error is returned.
	Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error)
	// Notify returns a channel that is used by gRPC internals to watch the addresses
	// gRPC needs to connect. The addresses might be from a name resolver or remote
	// load balancer. gRPC internals will compare it with the existing connected
	// addresses. If the address Balancer notified is not in the existing connected
	// addresses, gRPC starts to connect the address. If an address in the existing
	// connected addresses is not in the notification list, the corresponding connection
	// is shutdown gracefully. Otherwise, there are no operations to take. Note that
	// the Address slice must be the full list of the Addresses which should be connected.
	// It is NOT delta.
	Notify() <-chan []Address
	// Close shuts down the balancer.
	Close() error
}

// downErr implements net.Error. It is constructed by gRPC internals and passed to the down
// call of Balancer.
type downErr struct {
	timeout   bool
	temporary bool
	desc      string
}

func (e downErr) Error() string   { return e.desc }
func (e downErr) Timeout() bool   { return e.timeout }
func (e downErr) Temporary() bool { return e.temporary }

func downErrorf(timeout, temporary bool, format string, a ...interface{}) downErr {
	return downErr{
		timeout:   timeout,
		temporary: temporary,
		desc:      fmt.Sprintf(format, a...),
	}
}

// RoundRobin returns a Balancer that selects addresses round-robin. It uses r to watch
// the name resolution updates and updates the addresses available correspondingly.
func RoundRobin(r naming.Resolver) Balancer {
	return &roundRobin{r: r}
}

type addrInfo struct {
	addr      Address
	connected bool
}

type roundRobin struct {
	r      naming.Resolver
	w      naming.Watcher
	addrs  []*addrInfo // all the addresses the client should potentially connect
	mu     sync.Mutex
	addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to.
	next   int            // index of the next address to return for Get()
	waitCh chan struct{}  // the channel to block when there is no connected address available
	done   bool           // The Balancer is closed.
}

func (rr *roundRobin) watchAddrUpdates() error {
	updates, err := rr.w.Next()
	if err != nil {
		grpclog.Printf("grpc: the naming watcher stops working due to %v.\n", err)
		return err
	}
	rr.mu.Lock()
	defer rr.mu.Unlock()
	for _, update := range updates {
		addr := Address{
			Addr:     update.Addr,
			Metadata: update.Metadata,
		}
		switch update.Op {
		case naming.Add:
			var exist bool
			for _, v := range rr.addrs {
				if addr == v.addr {
					exist = true
					grpclog.Println("grpc: The name resolver wanted to add an existing address: ", addr)
					break
				}
			}
			if exist {
				continue
			}
			rr.addrs = append(rr.addrs, &addrInfo{addr: addr})
		case naming.Delete:
			for i, v := range rr.addrs {
				if addr == v.addr {
					copy(rr.addrs[i:], rr.addrs[i+1:])
					rr.addrs = rr.addrs[:len(rr.addrs)-1]
					break
				}
			}
		default:
			grpclog.Println("Unknown update.Op ", update.Op)
		}
	}
	// Make a copy of rr.addrs and write it onto rr.addrCh so that gRPC internals gets notified.
	open := make([]Address, len(rr.addrs))
	for i, v := range rr.addrs {
		open[i] = v.addr
	}
	if rr.done {
		return ErrClientConnClosing
	}
	rr.addrCh <- open
	return nil
}

func (rr *roundRobin) Start(target string, config BalancerConfig) error {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	if rr.done {
		return ErrClientConnClosing
	}
	if rr.r == nil {
		// If there is no name resolver installed, it is not needed to
		// do name resolution. In this case, target is added into rr.addrs
		// as the only address available and rr.addrCh stays nil.
		rr.addrs = append(rr.addrs, &addrInfo{addr: Address{Addr: target}})
		return nil
	}
	w, err := rr.r.Resolve(target)
	if err != nil {
		return err
	}
	rr.w = w
	rr.addrCh = make(chan []Address)
	go func() {
		for {
			if err := rr.watchAddrUpdates(); err != nil {
				return
			}
		}
	}()
	return nil
}

// Up sets the connected state of addr and sends notification if there are pending
// Get() calls.
func (rr *roundRobin) Up(addr Address) func(error) {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	var cnt int
	for _, a := range rr.addrs {
		if a.addr == addr {
			if a.connected {
				return nil
			}
			a.connected = true
		}
		if a.connected {
			cnt++
		}
	}
	// addr is only one which is connected. Notify the Get() callers who are blocking.
	if cnt == 1 && rr.waitCh != nil {
		close(rr.waitCh)
		rr.waitCh = nil
	}
	return func(err error) {
		rr.down(addr, err)
	}
}

// down unsets the connected state of addr.
func (rr *roundRobin) down(addr Address, err error) {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	for _, a := range rr.addrs {
		if addr == a.addr {
			a.connected = false
			break
		}
	}
}

// Get returns the next addr in the rotation.
func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error) {
	var ch chan struct{}
	rr.mu.Lock()
	if rr.done {
		rr.mu.Unlock()
		err = ErrClientConnClosing
		return
	}

	if len(rr.addrs) > 0 {
		if rr.next >= len(rr.addrs) {
			rr.next = 0
		}
		next := rr.next
		for {
			a := rr.addrs[next]
			next = (next + 1) % len(rr.addrs)
			if a.connected {
				addr = a.addr
				rr.next = next
				rr.mu.Unlock()
				return
			}
			if next == rr.next {
				// Has iterated all the possible address but none is connected.
				break
			}
		}
	}
	if !opts.BlockingWait {
		if len(rr.addrs) == 0 {
			rr.mu.Unlock()
			err = Errorf(codes.Unavailable, "there is no address available")
			return
		}
		// Returns the next addr on rr.addrs for failfast RPCs.
		addr = rr.addrs[rr.next].addr
		rr.next++
		rr.mu.Unlock()
		return
	}
	// Wait on rr.waitCh for non-failfast RPCs.
	if rr.waitCh == nil {
		ch = make(chan struct{})
		rr.waitCh = ch
	} else {
		ch = rr.waitCh
	}
	rr.mu.Unlock()
	for {
		select {
		case <-ctx.Done():
			err = ctx.Err()
			return
		case <-ch:
			rr.mu.Lock()
			if rr.done {
				rr.mu.Unlock()
				err = ErrClientConnClosing
				return
			}

			if len(rr.addrs) > 0 {
				if rr.next >= len(rr.addrs) {
					rr.next = 0
				}
				next := rr.next
				for {
					a := rr.addrs[next]
					next = (next + 1) % len(rr.addrs)
					if a.connected {
						addr = a.addr
						rr.next = next
						rr.mu.Unlock()
						return
					}
					if next == rr.next {
						// Has iterated all the possible address but none is connected.
						break
					}
				}
			}
			// The newly added addr got removed by Down() again.
			if rr.waitCh == nil {
				ch = make(chan struct{})
				rr.waitCh = ch
			} else {
				ch = rr.waitCh
			}
			rr.mu.Unlock()
		}
	}
}

func (rr *roundRobin) Notify() <-chan []Address {
	return rr.addrCh
}

func (rr *roundRobin) Close() error {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	if rr.done {
		return errBalancerClosed
	}
	rr.done = true
	if rr.w != nil {
		rr.w.Close()
	}
	if rr.waitCh != nil {
		close(rr.waitCh)
		rr.waitCh = nil
	}
	if rr.addrCh != nil {
		close(rr.addrCh)
	}
	return nil
}
<commit_msg>fix deadlock of roundrobin balancer (#1353)<commit_after>/*
 *
 * Copyright 2016 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package grpc

import (
	"fmt"
	"net"
	"sync"

	"golang.org/x/net/context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	"google.golang.org/grpc/naming"
)

// Address represents a server the client connects to.
// This is the EXPERIMENTAL API and may be changed or extended in the future.
type Address struct {
	// Addr is the server address on which a connection will be established.
	Addr string
	// Metadata is the information associated with Addr, which may be used
	// to make load balancing decision.
	Metadata interface{}
}

// BalancerConfig specifies the configurations for Balancer.
type BalancerConfig struct {
	// DialCreds is the transport credential the Balancer implementation can
	// use to dial to a remote load balancer server. The Balancer implementations
	// can ignore this if it does not need to talk to another party securely.
	DialCreds credentials.TransportCredentials
	// Dialer is the custom dialer the Balancer implementation can use to dial
	// to a remote load balancer server. The Balancer implementations
	// can ignore this if it doesn't need to talk to remote balancer.
	Dialer func(context.Context, string) (net.Conn, error)
}

// BalancerGetOptions configures a Get call.
// This is the EXPERIMENTAL API and may be changed or extended in the future.
type BalancerGetOptions struct {
	// BlockingWait specifies whether Get should block when there is no
	// connected address.
	BlockingWait bool
}

// Balancer chooses network addresses for RPCs.
// This is the EXPERIMENTAL API and may be changed or extended in the future.
type Balancer interface {
	// Start does the initialization work to bootstrap a Balancer. For example,
	// this function may start the name resolution and watch the updates. It will
	// be called when dialing.
	Start(target string, config BalancerConfig) error
	// Up informs the Balancer that gRPC has a connection to the server at
	// addr. It returns down which is called once the connection to addr gets
	// lost or closed.
	// TODO: It is not clear how to construct and take advantage of the meaningful error
	// parameter for down. Need realistic demands to guide.
	Up(addr Address) (down func(error))
	// Get gets the address of a server for the RPC corresponding to ctx.
	// i) If it returns a connected address, gRPC internals issues the RPC on the
	// connection to this address;
	// ii) If it returns an address on which the connection is under construction
	// (initiated by Notify(...)) but not connected, gRPC internals
	//  * fails RPC if the RPC is fail-fast and connection is in the TransientFailure or
	//  Shutdown state;
	//  or
	//  * issues RPC on the connection otherwise.
	// iii) If it returns an address on which the connection does not exist, gRPC
	// internals treats it as an error and will fail the corresponding RPC.
	//
	// Therefore, the following is the recommended rule when writing a custom Balancer.
	// If opts.BlockingWait is true, it should return a connected address or
	// block if there is no connected address. It should respect the timeout or
	// cancellation of ctx when blocking. If opts.BlockingWait is false (for fail-fast
	// RPCs), it should return an address it has notified via Notify(...) immediately
	// instead of blocking.
	//
	// The function returns put which is called once the rpc has completed or failed.
	// put can collect and report RPC stats to a remote load balancer.
	//
	// This function should only return the errors Balancer cannot recover by itself.
	// gRPC internals will fail the RPC if an error is returned.
	Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error)
	// Notify returns a channel that is used by gRPC internals to watch the addresses
	// gRPC needs to connect. The addresses might be from a name resolver or remote
	// load balancer. gRPC internals will compare it with the existing connected
	// addresses. If the address Balancer notified is not in the existing connected
	// addresses, gRPC starts to connect the address. If an address in the existing
	// connected addresses is not in the notification list, the corresponding connection
	// is shutdown gracefully. Otherwise, there are no operations to take. Note that
	// the Address slice must be the full list of the Addresses which should be connected.
	// It is NOT delta.
	Notify() <-chan []Address
	// Close shuts down the balancer.
	Close() error
}

// downErr implements net.Error. It is constructed by gRPC internals and passed to the down
// call of Balancer.
type downErr struct {
	timeout   bool
	temporary bool
	desc      string
}

func (e downErr) Error() string   { return e.desc }
func (e downErr) Timeout() bool   { return e.timeout }
func (e downErr) Temporary() bool { return e.temporary }

func downErrorf(timeout, temporary bool, format string, a ...interface{}) downErr {
	return downErr{
		timeout:   timeout,
		temporary: temporary,
		desc:      fmt.Sprintf(format, a...),
	}
}

// RoundRobin returns a Balancer that selects addresses round-robin. It uses r to watch
// the name resolution updates and updates the addresses available correspondingly.
func RoundRobin(r naming.Resolver) Balancer {
	return &roundRobin{r: r}
}

type addrInfo struct {
	addr      Address
	connected bool
}

type roundRobin struct {
	r      naming.Resolver
	w      naming.Watcher
	addrs  []*addrInfo // all the addresses the client should potentially connect
	mu     sync.Mutex
	addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to.
	next   int            // index of the next address to return for Get()
	waitCh chan struct{}  // the channel to block when there is no connected address available
	done   bool           // The Balancer is closed.
}

func (rr *roundRobin) watchAddrUpdates() error {
	updates, err := rr.w.Next()
	if err != nil {
		grpclog.Printf("grpc: the naming watcher stops working due to %v.\n", err)
		return err
	}
	rr.mu.Lock()
	defer rr.mu.Unlock()
	for _, update := range updates {
		addr := Address{
			Addr:     update.Addr,
			Metadata: update.Metadata,
		}
		switch update.Op {
		case naming.Add:
			var exist bool
			for _, v := range rr.addrs {
				if addr == v.addr {
					exist = true
					grpclog.Println("grpc: The name resolver wanted to add an existing address: ", addr)
					break
				}
			}
			if exist {
				continue
			}
			rr.addrs = append(rr.addrs, &addrInfo{addr: addr})
		case naming.Delete:
			for i, v := range rr.addrs {
				if addr == v.addr {
					copy(rr.addrs[i:], rr.addrs[i+1:])
					rr.addrs = rr.addrs[:len(rr.addrs)-1]
					break
				}
			}
		default:
			grpclog.Println("Unknown update.Op ", update.Op)
		}
	}
	// Make a copy of rr.addrs and write it onto rr.addrCh so that gRPC internals gets notified.
	open := make([]Address, len(rr.addrs))
	for i, v := range rr.addrs {
		open[i] = v.addr
	}
	if rr.done {
		return ErrClientConnClosing
	}
	select {
	case <-rr.addrCh:
	default:
	}
	rr.addrCh <- open
	return nil
}

func (rr *roundRobin) Start(target string, config BalancerConfig) error {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	if rr.done {
		return ErrClientConnClosing
	}
	if rr.r == nil {
		// If there is no name resolver installed, it is not needed to
		// do name resolution. In this case, target is added into rr.addrs
		// as the only address available and rr.addrCh stays nil.
		rr.addrs = append(rr.addrs, &addrInfo{addr: Address{Addr: target}})
		return nil
	}
	w, err := rr.r.Resolve(target)
	if err != nil {
		return err
	}
	rr.w = w
	rr.addrCh = make(chan []Address, 1)
	go func() {
		for {
			if err := rr.watchAddrUpdates(); err != nil {
				return
			}
		}
	}()
	return nil
}

// Up sets the connected state of addr and sends notification if there are pending
// Get() calls.
func (rr *roundRobin) Up(addr Address) func(error) {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	var cnt int
	for _, a := range rr.addrs {
		if a.addr == addr {
			if a.connected {
				return nil
			}
			a.connected = true
		}
		if a.connected {
			cnt++
		}
	}
	// addr is only one which is connected. Notify the Get() callers who are blocking.
	if cnt == 1 && rr.waitCh != nil {
		close(rr.waitCh)
		rr.waitCh = nil
	}
	return func(err error) {
		rr.down(addr, err)
	}
}

// down unsets the connected state of addr.
func (rr *roundRobin) down(addr Address, err error) {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	for _, a := range rr.addrs {
		if addr == a.addr {
			a.connected = false
			break
		}
	}
}

// Get returns the next addr in the rotation.
func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error) {
	var ch chan struct{}
	rr.mu.Lock()
	if rr.done {
		rr.mu.Unlock()
		err = ErrClientConnClosing
		return
	}

	if len(rr.addrs) > 0 {
		if rr.next >= len(rr.addrs) {
			rr.next = 0
		}
		next := rr.next
		for {
			a := rr.addrs[next]
			next = (next + 1) % len(rr.addrs)
			if a.connected {
				addr = a.addr
				rr.next = next
				rr.mu.Unlock()
				return
			}
			if next == rr.next {
				// Has iterated all the possible address but none is connected.
				break
			}
		}
	}
	if !opts.BlockingWait {
		if len(rr.addrs) == 0 {
			rr.mu.Unlock()
			err = Errorf(codes.Unavailable, "there is no address available")
			return
		}
		// Returns the next addr on rr.addrs for failfast RPCs.
		addr = rr.addrs[rr.next].addr
		rr.next++
		rr.mu.Unlock()
		return
	}
	// Wait on rr.waitCh for non-failfast RPCs.
	if rr.waitCh == nil {
		ch = make(chan struct{})
		rr.waitCh = ch
	} else {
		ch = rr.waitCh
	}
	rr.mu.Unlock()
	for {
		select {
		case <-ctx.Done():
			err = ctx.Err()
			return
		case <-ch:
			rr.mu.Lock()
			if rr.done {
				rr.mu.Unlock()
				err = ErrClientConnClosing
				return
			}

			if len(rr.addrs) > 0 {
				if rr.next >= len(rr.addrs) {
					rr.next = 0
				}
				next := rr.next
				for {
					a := rr.addrs[next]
					next = (next + 1) % len(rr.addrs)
					if a.connected {
						addr = a.addr
						rr.next = next
						rr.mu.Unlock()
						return
					}
					if next == rr.next {
						// Has iterated all the possible address but none is connected.
						break
					}
				}
			}
			// The newly added addr got removed by Down() again.
			if rr.waitCh == nil {
				ch = make(chan struct{})
				rr.waitCh = ch
			} else {
				ch = rr.waitCh
			}
			rr.mu.Unlock()
		}
	}
}

func (rr *roundRobin) Notify() <-chan []Address {
	return rr.addrCh
}

func (rr *roundRobin) Close() error {
	rr.mu.Lock()
	defer rr.mu.Unlock()
	if rr.done {
		return errBalancerClosed
	}
	rr.done = true
	if rr.w != nil {
		rr.w.Close()
	}
	if rr.waitCh != nil {
		close(rr.waitCh)
		rr.waitCh = nil
	}
	if rr.addrCh != nil {
		close(rr.addrCh)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
    "strconv"
    "time"
    "encoding/json"
    "strings"
    "sort"
    "os/user"
    "os"
    "bufio"
    "io/ioutil"
    "fmt"
    goopt "github.com/droundy/goopt"
)

var param_from = goopt.String([]string{"-f", "--from"}, "", "set \"from\" account")
var param_to = goopt.String([]string{"-t", "--to"}, "", "set \"to\" account")
var param_amount = goopt.String([]string{"-a", "--amount"}, "", "set amount")
var param_date = goopt.String([]string{"-d", "--date"}, "now", "set date")
var param_depth = goopt.Int([]string{"-D", "--depth"}, 5, "set from account")
var param_sincedate = goopt.String([]string{"-S", "--since-date"}, "start", "set SINCE date")
var param_todate = goopt.String([]string{"-T", "--to-date"}, "now", "set TO date")
var param_comment = goopt.String([]string{"-C", "--comment"}, "", "set comment")
var param_checkout_balance = goopt.Flag([]string{"-c", "--checkout"}, []string{"-b", "--balance"}, "checkout", "check balance")

type Config struct {
    BertrandFile string
}

func LoadConfig() (c *Config, err error) {
    var bfile []byte
    usr, _ := user.Current()
    cfgpath := usr.HomeDir + "/.bertrand/config.json"
    if bfile, err = ioutil.ReadFile(cfgpath); err != nil {
        return
    }
    c = new(Config)
    err = json.Unmarshal(bfile, c)
    return
}
func ReadTransactions (path string) ([]string, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var lines []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }
    return lines, scanner.Err()
}
func WriteTransaction(transaction, path string) error {
    file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
    if err != nil {
        return err
    }
    _, err = file.WriteString(transaction)
    file.Close()
    return err
}
func GetAccounts (data []string, depth int) ([]string) {
    accounts := make([]string, len(data))
    for n, i := range data {
        fields := strings.Split(i, ";")
        longAccount := strings.Split(fields[1], ".")
        var strippedAccount []string
        strippedAccount = append(strippedAccount, longAccount[0])
        if depth > 1 {
            for j := 1; j < len(longAccount) && j < depth; j++ {
                strippedAccount = append(strippedAccount, longAccount[j])
            }
        }
        accounts[n] = strings.Join(strippedAccount, ".")
    }
    sort.Strings(accounts)
    var uniqAccounts []string
    uniqAccounts = append(uniqAccounts, accounts[0])
    for i := 1; i < len(accounts); i++ {
        if accounts[i] != accounts[i-1] {
            uniqAccounts = append(uniqAccounts, accounts[i])
        }
    }
    return uniqAccounts
}

func main() {
    goopt.Description = func() string {
        return "Bertrand - advanced accounting program."
    }
    goopt.Version = "0.1"
    goopt.Summary = "advanced accounting program"
    goopt.Parse(nil)

    const shortForm = "2006-01-02"
    cfgparams, _ := LoadConfig()
    megalines, _ := ReadTransactions(cfgparams.BertrandFile)
    accounts := GetAccounts(megalines, *param_depth)

    if *param_checkout_balance {
        if *param_date == "now" {
            *param_date = time.Now().Format(shortForm)
        }
        var newTransaction string
        newTransaction = *param_date + ";" + *param_from + ";" + "-" + *param_amount + ";" + *param_comment + "\n"
        if err := WriteTransaction(newTransaction, cfgparams.BertrandFile); err != nil {
            fmt.Println(err)
        }
        newTransaction = *param_date + ";" + *param_to + ";" + *param_amount + ";" + *param_comment + "\n"
        if err := WriteTransaction(newTransaction, cfgparams.BertrandFile); err != nil {
            fmt.Println(err)
        }
    } else {
        spent := make([]float64, len(accounts))
        for n, i := range accounts {
            spent[n] = 0.0
            for _, j := range megalines {
                fields := strings.Split(j, ";")
                t, _ := time.Parse(shortForm, fields[0])
                date := t.Unix()
                acc := fields[1]
                amount, _ := strconv.ParseFloat(fields[2], 64)
                var sinceDate, toDate int64
                if *param_sincedate == "start" {
                    t, _ = time.Parse(shortForm, *param_sincedate)
                    sinceDate = t.Unix()
                } else {
                    t, _ = time.Parse(shortForm, *param_sincedate)
                    sinceDate = t.Unix()
                }
                if *param_todate == "now" {
                    toDate = time.Now().Unix()
                } else {
                    t, _ = time.Parse(shortForm, *param_todate)
                    toDate = t.Unix()
                }
                if date >= sinceDate && date <= toDate && strings.Contains(acc, accounts[n]) {
                    spent[n] += amount
                }
            }
            fmt.Printf("%s: %.2f\n", i, spent[n])
        }
    }
}
<commit_msg>--init implemented<commit_after>package main

import (
    "strconv"
    "time"
    "encoding/json"
    "strings"
    "sort"
    "os/user"
    "os"
    "bufio"
    "io/ioutil"
    "fmt"
    goopt "github.com/droundy/goopt"
)

var param_init = goopt.Flag([]string{"-i", "--init"}, []string{}, "initialize config file", "")
var param_undo = goopt.Flag([]string{"-u", "--undo"}, []string{}, "delete last transaction", "")
var param_from = goopt.String([]string{"-f", "--from"}, "", "set \"from\" account")
var param_to = goopt.String([]string{"-t", "--to"}, "", "set \"to\" account")
var param_amount = goopt.String([]string{"-a", "--amount"}, "", "set amount")
var param_date = goopt.String([]string{"-d", "--date"}, "now", "set date")
var param_depth = goopt.Int([]string{"-D", "--depth"}, 5, "set from account")
var param_sincedate = goopt.String([]string{"-S", "--since-date"}, "start", "set SINCE date")
var param_todate = goopt.String([]string{"-T", "--to-date"}, "now", "set TO date")
var param_comment = goopt.String([]string{"-C", "--comment"}, "", "set comment")
var param_checkout_balance = goopt.Flag([]string{"-c", "--checkout"}, []string{"-b", "--balance"}, "checkout", "check balance")

type Config struct {
    BertrandFile string
}

func LoadConfig() (c *Config, err error) {
    var bfile []byte
    usr, _ := user.Current()
    cfgpath := usr.HomeDir + "/.bertrand/config.json"
    if *param_init {
        Init()
    } else if bfile, err = ioutil.ReadFile(cfgpath); err != nil {
        fmt.Println("Config file not found. Please, invoke \"bertrand --init\".")
        os.Exit(1)
    }
    c = new(Config)
    err = json.Unmarshal(bfile, c)
    return
}

func Init() () {
    fmt.Println("Please, specify csv file path:")
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan()
    filePath := scanner.Text()
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading standard input:", err)
    }
    usr, _ := user.Current()
    os.Mkdir(usr.HomeDir+ "/.bertrand/", 0755)
    configFile, err := os.Create(usr.HomeDir + "/.bertrand/config.json")
    if err != nil {
        fmt.Println(err)
    }
    _, err = configFile.WriteString("{\n    \"bertrandFile\": \"" + filePath + "\"\n}\n")
    if err != nil {
        fmt.Println(err)
    }
    os.OpenFile(filePath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
    os.Exit(0)
}

func Undo(path string) () {
    fmt.Println("Not implemented yet")
}

func ReadTransactions (path string) ([]string, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var lines []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }
    return lines, scanner.Err()
}

func WriteTransaction(transaction, path string) error {
    file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
    if err != nil {
        return err
    }
    _, err = file.WriteString(transaction)
    file.Close()
    return err
}

func GetAccounts (data []string, depth int) ([]string) {
    accounts := make([]string, len(data))
    for n, i := range data {
        fields := strings.Split(i, ";")
        longAccount := strings.Split(fields[1], ".")
        var strippedAccount []string
        strippedAccount = append(strippedAccount, longAccount[0])
        if depth > 1 {
            for j := 1; j < len(longAccount) && j < depth; j++ {
                strippedAccount = append(strippedAccount, longAccount[j])
            }
        }
        accounts[n] = strings.Join(strippedAccount, ".")
    }
    sort.Strings(accounts)
    var uniqAccounts []string
    uniqAccounts = append(uniqAccounts, accounts[0])
    for i := 1; i < len(accounts); i++ {
        if accounts[i] != accounts[i-1] {
            uniqAccounts = append(uniqAccounts, accounts[i])
        }
    }
    return uniqAccounts
}

func main() {
    goopt.Description = func() string {
        return "Bertrand - advanced accounting program."
    }
    goopt.Version = "0.1"
    goopt.Summary = "advanced accounting program"
    goopt.Parse(nil)

    const shortForm = "2006-01-02"
    cfgparams, _ := LoadConfig()
    megalines, _ := ReadTransactions(cfgparams.BertrandFile)

    if *param_checkout_balance {
        if *param_date == "now" {
            *param_date = time.Now().Format(shortForm)
        }
        var newTransaction string
        newTransaction = *param_date + ";" + *param_from + ";" + "-" + *param_amount + ";" + *param_comment + "\n"
        if err := WriteTransaction(newTransaction, cfgparams.BertrandFile); err != nil {
            fmt.Println(err)
        }
        newTransaction = *param_date + ";" + *param_to + ";" + *param_amount + ";" + *param_comment + "\n"
        if err := WriteTransaction(newTransaction, cfgparams.BertrandFile); err != nil {
            fmt.Println(err)
        }
    } else if *param_undo {
        Undo(cfgparams.BertrandFile)
    } else {
        if len(megalines) == 0 {
            fmt.Println("Accounting file is empty!")
            os.Exit(1)
        }
        accounts := GetAccounts(megalines, *param_depth)
        spent := make([]float64, len(accounts))
        for n, i := range accounts {
            spent[n] = 0.0
            for _, j := range megalines {
                fields := strings.Split(j, ";")
                t, _ := time.Parse(shortForm, fields[0])
                date := t.Unix()
                acc := fields[1]
                amount, _ := strconv.ParseFloat(fields[2], 64)
                var sinceDate, toDate int64
                if *param_sincedate == "start" {
                    t, _ = time.Parse(shortForm, *param_sincedate)
                    sinceDate = t.Unix()
                } else {
                    t, _ = time.Parse(shortForm, *param_sincedate)
                    sinceDate = t.Unix()
                }
                if *param_todate == "now" {
                    toDate = time.Now().Unix()
                } else {
                    t, _ = time.Parse(shortForm, *param_todate)
                    toDate = t.Unix()
                }
                if date >= sinceDate && date <= toDate && strings.Contains(acc, accounts[n]) {
                    spent[n] += amount
                }
            }
            fmt.Printf("%s: %.2f\n", i, spent[n])
        }
    }
}
<|endoftext|>


<commit_before>package alfred

import "fmt"

// FilterResult is the final result of an Alfred script filter,
// to be rendered as JSON.
type FilterResult struct {
	Items     Items      `json:"items"`
	Rerun     float64    `json:"rerun,omitempty"`
	Variables *Variables `json:"variables,omitempty"`
}

// NewFilterResult provides an initialized FilterResult that contains the
// required (but empty) Items list
func NewFilterResult() *FilterResult {
	return &FilterResult{Items: Items{}}
}

// Items is a list of Item pointers.
type Items []*Item

func (is Items) String() string {
	s := "[\n"
	for _, i := range is {
		s += fmt.Sprintf("%#v\n", i)
	}
	s += "]\n"
	return s
}

// Variables is a map of string to string variables for alfred to pass on to a
// subsquent iteration of a script filter
type Variables map[string]string

// Item is an Alfred result item
type Item struct {
	UID          string `json:"uid,omitempty"`          // optional unique identifier for alfred to learn from
	Title        string `json:"title"`                  // title displayed in the result row
	Subtitle     string `json:"subtitle,omitempty"`     // optional subtitle displayed in the result row
	Arg          string `json:"arg,omitempty"`          // recommended string argument to pass through to output action
	Icon         *Icon  `json:"icon,omitempty"`         // optional icon argument
	Valid        bool   `json:"valid"`                  // valid means "actionable", false means "populate autocomplete text"
	Autocomplete string `json:"autocomplete,omitempty"` // recommended string to autocomplete with tab key
	// Type string // "default", "file", "file:skipcheck" to treat the result as a file
	Mods *Mods `json:"mods,omitempty"` // optional modifier keys arguments
	Text *Text `json:"text,omitempty"` // optional text if copied to clipboard or displayed as large text
	// Quicklook string // optional url for quicklook
}

// AppendItems is shorthand for adding more items to a FilterResult's Items list
func (result *FilterResult) AppendItems(items ...*Item) {
	result.Items = append(result.Items, items...)
}

// SetVariable to set a variable in the result output
func (result *FilterResult) SetVariable(name, value string) {
	if result.Variables == nil {
		result.Variables = &Variables{name: value}
	} else {
		(*result.Variables)[name] = value
	}
}

// Icon is a custom icon for an item
type Icon struct {
	Path string `json:"path"`           // the path to a file
	Type string `json:"type,omitempty"` // optional, "fileicon" for a path, "filetype" for a specific file
}

// Text defines copy text and/or large type display
type Text struct {
	Copy      string `json:"copy,omitempty"`
	LargeType string `json:"largetype,omitempty"`
}

// ModItem defines an alternate action for an item
type ModItem struct {
	Valid    bool   `json:"valid"`
	Arg      string `json:"arg,omitempty"`
	Subtitle string `json:"subtitle,omitempty"`
}

// Mods define alternate actions for an item, with alt or cmd held down
type Mods struct {
	Alt *ModItem `json:"alt,omitempty"`
	Cmd *ModItem `json:"cmd,omitempty"`
}

func (t *Text) String() string {
	return fmt.Sprintf("%#v", *t)
}

func (m *Mods) String() string {
	return fmt.Sprintf("%#v", *m)
}

func (m *ModItem) String() string {
	return fmt.Sprintf("%#v", *m)
}
<commit_msg>Add icon to ModItem<commit_after>package alfred

import "fmt"

// FilterResult is the final result of an Alfred script filter,
// to be rendered as JSON.
type FilterResult struct {
	Items     Items      `json:"items"`
	Rerun     float64    `json:"rerun,omitempty"`
	Variables *Variables `json:"variables,omitempty"`
}

// NewFilterResult provides an initialized FilterResult that contains the
// required (but empty) Items list
func NewFilterResult() *FilterResult {
	return &FilterResult{Items: Items{}}
}

// Items is a list of Item pointers.
type Items []*Item

func (is Items) String() string {
	s := "[\n"
	for _, i := range is {
		s += fmt.Sprintf("%#v\n", i)
	}
	s += "]\n"
	return s
}

// Variables is a map of string to string variables for alfred to pass on to a
// subsquent iteration of a script filter
type Variables map[string]string

// Item is an Alfred result item
type Item struct {
	UID          string `json:"uid,omitempty"`          // optional unique identifier for alfred to learn from
	Title        string `json:"title"`                  // title displayed in the result row
	Subtitle     string `json:"subtitle,omitempty"`     // optional subtitle displayed in the result row
	Arg          string `json:"arg,omitempty"`          // recommended string argument to pass through to output action
	Icon         *Icon  `json:"icon,omitempty"`         // optional icon argument
	Valid        bool   `json:"valid"`                  // valid means "actionable", false means "populate autocomplete text"
	Autocomplete string `json:"autocomplete,omitempty"` // recommended string to autocomplete with tab key
	// Type string // "default", "file", "file:skipcheck" to treat the result as a file
	Mods *Mods `json:"mods,omitempty"` // optional modifier keys arguments
	Text *Text `json:"text,omitempty"` // optional text if copied to clipboard or displayed as large text
	// Quicklook string // optional url for quicklook
}

// AppendItems is shorthand for adding more items to a FilterResult's Items list
func (result *FilterResult) AppendItems(items ...*Item) {
	result.Items = append(result.Items, items...)
}

// SetVariable to set a variable in the result output
func (result *FilterResult) SetVariable(name, value string) {
	if result.Variables == nil {
		result.Variables = &Variables{name: value}
	} else {
		(*result.Variables)[name] = value
	}
}

// Icon is a custom icon for an item
type Icon struct {
	Path string `json:"path"`           // the path to a file
	Type string `json:"type,omitempty"` // optional, "fileicon" for a path, "filetype" for a specific file
}

// Text defines copy text and/or large type display
type Text struct {
	Copy      string `json:"copy,omitempty"`
	LargeType string `json:"largetype,omitempty"`
}

// ModItem defines an alternate action for an item
type ModItem struct {
	Valid    bool   `json:"valid"`
	Arg      string `json:"arg,omitempty"`
	Subtitle string `json:"subtitle,omitempty"`
	Icon     *Icon  `json:"icon,omitempty"`
}

// Mods define alternate actions for an item, with alt or cmd held down
type Mods struct {
	Alt *ModItem `json:"alt,omitempty"`
	Cmd *ModItem `json:"cmd,omitempty"`
}

func (t *Text) String() string {
	return fmt.Sprintf("%#v", *t)
}

func (m *Mods) String() string {
	return fmt.Sprintf("%#v", *m)
}

func (m *ModItem) String() string {
	return fmt.Sprintf("%#v", *m)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"

	maas "github.com/juju/gomaasapi"
	"net/url"
)

// MaasNodeStatus MAAS lifecycle status for nodes
type MaasNodeStatus int

// MAAS Node Statuses
const (
	Invalid             MaasNodeStatus = -1
	New                 MaasNodeStatus = 0
	Commissioning       MaasNodeStatus = 1
	FailedCommissioning MaasNodeStatus = 2
	Missing             MaasNodeStatus = 3
	Ready               MaasNodeStatus = 4
	Reserved            MaasNodeStatus = 5
	Deployed            MaasNodeStatus = 6
	Retired             MaasNodeStatus = 7
	Broken              MaasNodeStatus = 8
	Deploying           MaasNodeStatus = 9
	Allocated           MaasNodeStatus = 10
	FailedDeployment    MaasNodeStatus = 11
	Releasing           MaasNodeStatus = 12
	FailedReleasing     MaasNodeStatus = 13
	DiskErasing         MaasNodeStatus = 14
	FailedDiskErasing   MaasNodeStatus = 15
)

var names = []string{"New", "Commissioning", "FailedCommissioning", "Missing", "Ready", "Reserved",
	"Deployed", "Retired", "Broken", "Deploying", "Allocated", "FailedDeployment",
	"Releasing", "FailedReleasing", "DiskErasing", "FailedDiskErasing"}

func (v MaasNodeStatus) String() string {
	return names[v]
}

// FromString lookup the constant value for a given node state name
func FromString(name string) (MaasNodeStatus, error) {
	for i, v := range names {
		if v == name {
			return MaasNodeStatus(i), nil
		}
	}
	return -1, fmt.Errorf("Unknown MAAS node state name, '%s'", name)
}

// MaasNode convenience wrapper for an MAAS node on top of a generic MAAS object
type MaasNode struct {
	maas.MAASObject
}

// GetString get attribute value as string
func (n *MaasNode) GetString(key string) (string, error) {
	return n.GetMap()[key].GetString()
}

// GetFloat64 get attribute value as float64
func (n *MaasNode) GetFloat64(key string) (float64, error) {
	return n.GetMap()[key].GetFloat64()
}

// ID get the system id of the node
func (n *MaasNode) ID() string {
	id, _ := n.GetString("system_id")
	return id
}

func (n *MaasNode) PowerType() string {
	ptype, _ := n.GetString("power_type")
	return ptype
}

func (n *MaasNode) PowerState() string {
	state, _ := n.GetString("power_state")
	return state
}

func (n *MaasNode) UpdatePowerParameters(ptype string, params map[string]string) {
	values := url.Values{}
	values.Add("power_type", ptype)
	for k, v := range params {
		values.Add("power_parameters_"+k, v)
	}
	_, err := n.Update(values)
	if err != nil {
		log.Printf("[error] error updating power settings : %s", err.Error())
	}
}

// Hostname get the hostname
func (n *MaasNode) Hostname() string {
	hn, _ := n.GetString("hostname")
	return hn
}

// IPs get the IP Addresses
func (n *MaasNode) IPs() []string {
	ifaceObj, _ := n.GetMap()["interface_set"]
	ifaces, _ := ifaceObj.GetArray()
	result := []string{}

	for _, iface := range ifaces {
		obj, _ := iface.GetMap()
		linksObj, _ := obj["links"]
		links, _ := linksObj.GetArray()
		for _, link := range links {
			linkObj, _ := link.GetMap()
			ipObj, _ := linkObj["ip_address"]
			ip, _ := ipObj.GetString()
			result = append(result, ip)
		}
	}

	return result
}

// MACs get the MAC Addresses
func (n *MaasNode) MACs() []string {
	macsObj, _ := n.GetMap()["macaddress_set"]
	macs, _ := macsObj.GetArray()
	if len(macs) == 0 {
		return []string{}
	}
	result := make([]string, len(macs))
	for i, mac := range macs {
		obj, _ := mac.GetMap()
		addr, _ := obj["mac_address"]
		s, _ := addr.GetString()
		result[i] = s
	}

	return result
}

// Zone get the zone
func (n *MaasNode) Zone() string {
	zone := n.GetMap()["zone"]
	attrs, _ := zone.GetMap()
	v, _ := attrs["name"].GetString()
	return v
}

// GetInteger get attribute value as integer
func (n *MaasNode) GetInteger(key string) (int, error) {
	v, err := n.GetMap()[key].GetFloat64()
	if err != nil {
		return 0, err
	}
	return int(v), nil
}
<commit_msg>only include non-empty IP addresses<commit_after>package main

import (
	"fmt"
	"log"

	maas "github.com/juju/gomaasapi"
	"net/url"
)

// MaasNodeStatus MAAS lifecycle status for nodes
type MaasNodeStatus int

// MAAS Node Statuses
const (
	Invalid             MaasNodeStatus = -1
	New                 MaasNodeStatus = 0
	Commissioning       MaasNodeStatus = 1
	FailedCommissioning MaasNodeStatus = 2
	Missing             MaasNodeStatus = 3
	Ready               MaasNodeStatus = 4
	Reserved            MaasNodeStatus = 5
	Deployed            MaasNodeStatus = 6
	Retired             MaasNodeStatus = 7
	Broken              MaasNodeStatus = 8
	Deploying           MaasNodeStatus = 9
	Allocated           MaasNodeStatus = 10
	FailedDeployment    MaasNodeStatus = 11
	Releasing           MaasNodeStatus = 12
	FailedReleasing     MaasNodeStatus = 13
	DiskErasing         MaasNodeStatus = 14
	FailedDiskErasing   MaasNodeStatus = 15
)

var names = []string{"New", "Commissioning", "FailedCommissioning", "Missing", "Ready", "Reserved",
	"Deployed", "Retired", "Broken", "Deploying", "Allocated", "FailedDeployment",
	"Releasing", "FailedReleasing", "DiskErasing", "FailedDiskErasing"}

func (v MaasNodeStatus) String() string {
	return names[v]
}

// FromString lookup the constant value for a given node state name
func FromString(name string) (MaasNodeStatus, error) {
	for i, v := range names {
		if v == name {
			return MaasNodeStatus(i), nil
		}
	}
	return -1, fmt.Errorf("Unknown MAAS node state name, '%s'", name)
}

// MaasNode convenience wrapper for an MAAS node on top of a generic MAAS object
type MaasNode struct {
	maas.MAASObject
}

// GetString get attribute value as string
func (n *MaasNode) GetString(key string) (string, error) {
	return n.GetMap()[key].GetString()
}

// GetFloat64 get attribute value as float64
func (n *MaasNode) GetFloat64(key string) (float64, error) {
	return n.GetMap()[key].GetFloat64()
}

// ID get the system id of the node
func (n *MaasNode) ID() string {
	id, _ := n.GetString("system_id")
	return id
}

func (n *MaasNode) PowerType() string {
	ptype, _ := n.GetString("power_type")
	return ptype
}

func (n *MaasNode) PowerState() string {
	state, _ := n.GetString("power_state")
	return state
}

func (n *MaasNode) UpdatePowerParameters(ptype string, params map[string]string) {
	values := url.Values{}
	values.Add("power_type", ptype)
	for k, v := range params {
		values.Add("power_parameters_"+k, v)
	}
	_, err := n.Update(values)
	if err != nil {
		log.Printf("[error] error updating power settings : %s", err.Error())
	}
}

// Hostname get the hostname
func (n *MaasNode) Hostname() string {
	hn, _ := n.GetString("hostname")
	return hn
}

// IPs get the IP Addresses
func (n *MaasNode) IPs() []string {
	ifaceObj, _ := n.GetMap()["interface_set"]
	ifaces, _ := ifaceObj.GetArray()
	result := []string{}

	for _, iface := range ifaces {
		obj, _ := iface.GetMap()
		linksObj, _ := obj["links"]
		links, _ := linksObj.GetArray()
		for _, link := range links {
			linkObj, _ := link.GetMap()
			ipObj, _ := linkObj["ip_address"]
			ip, _ := ipObj.GetString()
			if ip != "" {
				result = append(result, ip)
			}
		}
	}

	return result
}

// MACs get the MAC Addresses
func (n *MaasNode) MACs() []string {
	macsObj, _ := n.GetMap()["macaddress_set"]
	macs, _ := macsObj.GetArray()
	if len(macs) == 0 {
		return []string{}
	}
	result := make([]string, len(macs))
	for i, mac := range macs {
		obj, _ := mac.GetMap()
		addr, _ := obj["mac_address"]
		s, _ := addr.GetString()
		result[i] = s
	}

	return result
}

// Zone get the zone
func (n *MaasNode) Zone() string {
	zone := n.GetMap()["zone"]
	attrs, _ := zone.GetMap()
	v, _ := attrs["name"].GetString()
	return v
}

// GetInteger get attribute value as integer
func (n *MaasNode) GetInteger(key string) (int, error) {
	v, err := n.GetMap()[key].GetFloat64()
	if err != nil {
		return 0, err
	}
	return int(v), nil
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2016, UPMC Enterprises
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name UPMC Enterprises nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL UPMC ENTERPRISES BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
*/

package main

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecr"
	flag "github.com/spf13/pflag"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

var (
	flags                = flag.NewFlagSet("", flag.ContinueOnError)
	cluster              = flags.Bool("use-kubernetes-cluster-service", true, `If true, use the built in kubernetes cluster for creating the client`)
	argKubecfgFile       = flags.String("kubecfg-file", "", `Location of kubecfg file for access to kubernetes master service; --kube_master_url overrides the URL part of this; if neither this nor --kube_master_url are provided, defaults to service account tokens`)
	argKubeMasterURL     = flags.String("kube-master-url", "", `URL to reach kubernetes master. Env variables in this flag will be expanded.`)
	argDefaultSecretName = flags.String("default-secret-name", "awsecr-creds", `Default secret name`)
	argDefaultNamespace  = flags.String("default-namespace", "default", `Default namespace`)
	argAWSRegion         = flags.String("aws-region", "us-east-1", `Default namespace`)
	argRefreshMinutes    = flags.Int("refresh-mins", 715, `Default time to wait before refreshing (11 hours 55 mins)`)
)

var (
	kubeClient   *unversioned.Client
	awsAccountID string
)

func getECRAuthorizationKey() (token *ecr.AuthorizationData, err error) {
	svc := ecr.New(session.New(), aws.NewConfig().WithRegion(*argAWSRegion))

	params := &ecr.GetAuthorizationTokenInput{
		RegistryIds: []*string{
			aws.String(awsAccountID),
		},
	}

	resp, err := svc.GetAuthorizationToken(params)

	if err != nil {
		// Print the error, cast err to awserr.Error to get the Code and
		// Message from an error.
		fmt.Println(err.Error())
		return
	}

	token = resp.AuthorizationData[0]

	return
}

// Gets the default secret from the api
func getSecret(namespace string) (*api.Secret, error) {
	secret, err := kubeClient.Secrets(namespace).Get(*argDefaultSecretName)
	return secret, err
}

func getSecretObj(dockerConfigJSONContent []byte) *api.Secret {
	secret := &api.Secret{
		ObjectMeta: api.ObjectMeta{
			Name: *argDefaultSecretName,
		},
		Data: map[string][]byte{
			".dockerconfigjson": dockerConfigJSONContent,
		},
		Type: "kubernetes.io/dockerconfigjson",
	}

	return secret
}

func process() {
	// Get new token to seed the secret
	dockerJSONTemplate := `{"auths":{"%v":{"auth":"%v","email":"none"}}}`

	newToken, _ := getECRAuthorizationKey()
	authToken := fmt.Sprintf(dockerJSONTemplate, *newToken.ProxyEndpoint, *newToken.AuthorizationToken)
	newSecret := getSecretObj([]byte(authToken))

	// Get all namespaces
	namespaces, _ := kubeClient.Namespaces().List(api.ListOptions{})

	for _, namespace := range namespaces.Items {

		if namespace.GetName() == "kube-system" {
			continue
		}

		// Check if the secret exists for the namespace
		_, err := getSecret(namespace.GetName())

		if err != nil {
			// Secret not found, create
			kubeClient.Secrets(namespace.GetName()).Create(newSecret)
		} else {
			// Existing secret needs updated
			kubeClient.Secrets(namespace.GetName()).Update(newSecret)
		}

		// Check if ServiceAccount has the imagePullSecrets
		serviceAccount, err := kubeClient.ServiceAccounts(namespace.GetName()).Get("default")

		if err != nil {
			log.Fatalf("Could get find default service account!")
		}

		serviceAccount.ImagePullSecrets = []api.LocalObjectReference{{Name: *argDefaultSecretName}}
		_, err = kubeClient.ServiceAccounts(namespace.GetName()).Update(serviceAccount)

		if err != nil {
			fmt.Println("err: ", err)
		}

	}
}

func main() {
	log.Print("Starting up...")
	flags.Parse(os.Args)

	awsAccountID = os.Getenv("awsaccount")

	log.Print("Using AWS Account: ", awsAccountID)
	log.Print("Refresh Interval (minutes): ", *argRefreshMinutes)

	clientConfig := kubectl_util.DefaultClientConfig(flags)

	var err error

	if *cluster {
		if kubeClient, err = unversioned.NewInCluster(); err != nil {
			log.Fatalf("Failed to create client: %v", err)
		}
	} else {
		config, err := clientConfig.ClientConfig()
		if err != nil {
			log.Fatalf("error connecting to the client: %v", err)
		}
		kubeClient, err = unversioned.New(config)
	}

	tick := time.Tick(time.Duration(*argRefreshMinutes) * time.Minute)

	// Process once now, then wait for tick
	process()

	for {
		select {
		case <-tick:
			log.Print("Refreshing credentials...")
			process()
		}
	}

}
<commit_msg>Append imagePullSecret to slice instead of making ECR token the only imagePullSecret<commit_after>/*
Copyright (c) 2016, UPMC Enterprises
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name UPMC Enterprises nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL UPMC ENTERPRISES BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
*/

package main

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecr"
	flag "github.com/spf13/pflag"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	kubectl_util "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

var (
	flags                = flag.NewFlagSet("", flag.ContinueOnError)
	cluster              = flags.Bool("use-kubernetes-cluster-service", true, `If true, use the built in kubernetes cluster for creating the client`)
	argKubecfgFile       = flags.String("kubecfg-file", "", `Location of kubecfg file for access to kubernetes master service; --kube_master_url overrides the URL part of this; if neither this nor --kube_master_url are provided, defaults to service account tokens`)
	argKubeMasterURL     = flags.String("kube-master-url", "", `URL to reach kubernetes master. Env variables in this flag will be expanded.`)
	argDefaultSecretName = flags.String("default-secret-name", "awsecr-creds", `Default secret name`)
	argDefaultNamespace  = flags.String("default-namespace", "default", `Default namespace`)
	argAWSRegion         = flags.String("aws-region", "us-east-1", `Default namespace`)
	argRefreshMinutes    = flags.Int("refresh-mins", 715, `Default time to wait before refreshing (11 hours 55 mins)`)
)

var (
	kubeClient   *unversioned.Client
	awsAccountID string
)

func getECRAuthorizationKey() (token *ecr.AuthorizationData, err error) {
	svc := ecr.New(session.New(), aws.NewConfig().WithRegion(*argAWSRegion))

	params := &ecr.GetAuthorizationTokenInput{
		RegistryIds: []*string{
			aws.String(awsAccountID),
		},
	}

	resp, err := svc.GetAuthorizationToken(params)

	if err != nil {
		// Print the error, cast err to awserr.Error to get the Code and
		// Message from an error.
		fmt.Println(err.Error())
		return
	}

	token = resp.AuthorizationData[0]

	return
}

// Gets the default secret from the api
func getSecret(namespace string) (*api.Secret, error) {
	secret, err := kubeClient.Secrets(namespace).Get(*argDefaultSecretName)
	return secret, err
}

func getSecretObj(dockerConfigJSONContent []byte) *api.Secret {
	secret := &api.Secret{
		ObjectMeta: api.ObjectMeta{
			Name: *argDefaultSecretName,
		},
		Data: map[string][]byte{
			".dockerconfigjson": dockerConfigJSONContent,
		},
		Type: "kubernetes.io/dockerconfigjson",
	}

	return secret
}

func process() {
	// Get new token to seed the secret
	dockerJSONTemplate := `{"auths":{"%v":{"auth":"%v","email":"none"}}}`

	newToken, _ := getECRAuthorizationKey()
	authToken := fmt.Sprintf(dockerJSONTemplate, *newToken.ProxyEndpoint, *newToken.AuthorizationToken)
	newSecret := getSecretObj([]byte(authToken))

	// Get all namespaces
	namespaces, _ := kubeClient.Namespaces().List(api.ListOptions{})

	for _, namespace := range namespaces.Items {

		if namespace.GetName() == "kube-system" {
			continue
		}

		// Check if the secret exists for the namespace
		_, err := getSecret(namespace.GetName())

		if err != nil {
			// Secret not found, create
			kubeClient.Secrets(namespace.GetName()).Create(newSecret)
		} else {
			// Existing secret needs updated
			kubeClient.Secrets(namespace.GetName()).Update(newSecret)
		}

		// Check if ServiceAccount exists
		serviceAccount, err := kubeClient.ServiceAccounts(namespace.GetName()).Get("default")

		if err != nil {
			log.Fatalf("Couldn't get default service account!")
		}

		// Update existing one if image pull secrets already exists for aws ecr token
		imagePullSecretFound := false
		for i, imagePullSecret := range serviceAccount.ImagePullSecrets {
			if imagePullSecret.Name == *argDefaultSecretName {
				serviceAccount.ImagePullSecrets[i] = api.LocalObjectReference{Name: *argDefaultSecretName}
				imagePullSecretFound = true
				break
			}
		}

		// Append to list of existing service accounts if there isn't one already
		if !imagePullSecretFound {
			serviceAccount.ImagePullSecrets = append(serviceAccount.ImagePullSecrets, api.LocalObjectReference{Name: *argDefaultSecretName})
		}

		_, err = kubeClient.ServiceAccounts(namespace.GetName()).Update(serviceAccount)
		if err != nil {
			fmt.Println("err: ", err)
		}
	}
}

func main() {
	log.Print("Starting up...")
	flags.Parse(os.Args)

	awsAccountID = os.Getenv("awsaccount")

	log.Print("Using AWS Account: ", awsAccountID)
	log.Print("Refresh Interval (minutes): ", *argRefreshMinutes)

	clientConfig := kubectl_util.DefaultClientConfig(flags)

	var err error

	if *cluster {
		if kubeClient, err = unversioned.NewInCluster(); err != nil {
			log.Fatalf("Failed to create client: %v", err)
		}
	} else {
		config, err := clientConfig.ClientConfig()
		if err != nil {
			log.Fatalf("error connecting to the client: %v", err)
		}
		kubeClient, err = unversioned.New(config)
	}

	tick := time.Tick(time.Duration(*argRefreshMinutes) * time.Minute)

	// Process once now, then wait for tick
	process()

	for {
		select {
		case <-tick:
			log.Print("Refreshing credentials...")
			process()
		}
	}

}
<|endoftext|>


<commit_before>package fasthttp

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"math"
	"net"
	"reflect"
	"strings"
	"sync"
	"time"
	"unsafe"
)

// AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
func AppendHTMLEscape(dst []byte, s string) []byte {
	if strings.IndexByte(s, '<') < 0 &&
		strings.IndexByte(s, '>') < 0 &&
		strings.IndexByte(s, '"') < 0 &&
		strings.IndexByte(s, '\'') < 0 {

		// fast path - nothing to escape
		return append(dst, s...)
	}

	// slow path
	var prev int
	var sub string
	for i, n := 0, len(s); i < n; i++ {
		sub = ""
		switch s[i] {
		case '<':
			sub = "<"
		case '>':
			sub = ">"
		case '"':
			sub = """
		case '\'':
			sub = "'"
		}
		if len(sub) > 0 {
			dst = append(dst, s[prev:i]...)
			dst = append(dst, sub...)
			prev = i + 1
		}
	}
	return append(dst, s[prev:]...)
}

// AppendHTMLEscapeBytes appends html-escaped s to dst and returns
// the extended dst.
func AppendHTMLEscapeBytes(dst, s []byte) []byte {
	return AppendHTMLEscape(dst, b2s(s))
}

// AppendIPv4 appends string representation of the given ip v4 to dst
// and returns the extended dst.
func AppendIPv4(dst []byte, ip net.IP) []byte {
	ip = ip.To4()
	if ip == nil {
		return append(dst, "non-v4 ip passed to AppendIPv4"...)
	}

	dst = AppendUint(dst, int(ip[0]))
	for i := 1; i < 4; i++ {
		dst = append(dst, '.')
		dst = AppendUint(dst, int(ip[i]))
	}
	return dst
}

var errEmptyIPStr = errors.New("empty ip address string")

// ParseIPv4 parses ip address from ipStr into dst and returns the extended dst.
func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
	if len(ipStr) == 0 {
		return dst, errEmptyIPStr
	}
	if len(dst) < net.IPv4len {
		dst = make([]byte, net.IPv4len)
	}
	copy(dst, net.IPv4zero)
	dst = dst.To4()
	if dst == nil {
		panic("BUG: dst must not be nil")
	}

	b := ipStr
	for i := 0; i < 3; i++ {
		n := bytes.IndexByte(b, '.')
		if n < 0 {
			return dst, fmt.Errorf("cannot find dot in ipStr %q", ipStr)
		}
		v, err := ParseUint(b[:n])
		if err != nil {
			return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
		}
		if v > 255 {
			return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
		}
		dst[i] = byte(v)
		b = b[n+1:]
	}
	v, err := ParseUint(b)
	if err != nil {
		return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
	}
	if v > 255 {
		return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
	}
	dst[3] = byte(v)

	return dst, nil
}

// AppendHTTPDate appends HTTP-compliant (RFC1123) representation of date
// to dst and returns the extended dst.
func AppendHTTPDate(dst []byte, date time.Time) []byte {
	dst = date.In(time.UTC).AppendFormat(dst, time.RFC1123)
	copy(dst[len(dst)-3:], strGMT)
	return dst
}

// ParseHTTPDate parses HTTP-compliant (RFC1123) date.
func ParseHTTPDate(date []byte) (time.Time, error) {
	return time.Parse(time.RFC1123, b2s(date))
}

// AppendUint appends n to dst and returns the extended dst.
func AppendUint(dst []byte, n int) []byte {
	if n < 0 {
		panic("BUG: int must be positive")
	}

	var b [20]byte
	buf := b[:]
	i := len(buf)
	var q int
	for n >= 10 {
		i--
		q = n / 10
		buf[i] = '0' + byte(n-q*10)
		n = q
	}
	i--
	buf[i] = '0' + byte(n)

	dst = append(dst, buf[i:]...)
	return dst
}

// ParseUint parses uint from buf.
func ParseUint(buf []byte) (int, error) {
	v, n, err := parseUintBuf(buf)
	if n != len(buf) {
		return -1, errUnexpectedTrailingChar
	}
	return v, err
}

var (
	errEmptyInt               = errors.New("empty integer")
	errUnexpectedFirstChar    = errors.New("unexpected first char found. Expecting 0-9")
	errUnexpectedTrailingChar = errors.New("unexpected trailing char found. Expecting 0-9")
	errTooLongInt             = errors.New("too long int")
)

func parseUintBuf(b []byte) (int, int, error) {
	n := len(b)
	if n == 0 {
		return -1, 0, errEmptyInt
	}
	v := 0
	for i := 0; i < n; i++ {
		c := b[i]
		k := c - '0'
		if k > 9 {
			if i == 0 {
				return -1, i, errUnexpectedFirstChar
			}
			return v, i, nil
		}
		// Test for overflow.
		if v*10 < v {
			return -1, i, errTooLongInt
		}
		v = 10*v + int(k)
	}
	return v, n, nil
}

var (
	errEmptyFloat           = errors.New("empty float number")
	errDuplicateFloatPoint  = errors.New("duplicate point found in float number")
	errUnexpectedFloatEnd   = errors.New("unexpected end of float number")
	errInvalidFloatExponent = errors.New("invalid float number exponent")
	errUnexpectedFloatChar  = errors.New("unexpected char found in float number")
)

// ParseUfloat parses unsigned float from buf.
func ParseUfloat(buf []byte) (float64, error) {
	if len(buf) == 0 {
		return -1, errEmptyFloat
	}
	b := buf
	var v uint64
	var offset = 1.0
	var pointFound bool
	for i, c := range b {
		if c < '0' || c > '9' {
			if c == '.' {
				if pointFound {
					return -1, errDuplicateFloatPoint
				}
				pointFound = true
				continue
			}
			if c == 'e' || c == 'E' {
				if i+1 >= len(b) {
					return -1, errUnexpectedFloatEnd
				}
				b = b[i+1:]
				minus := -1
				switch b[0] {
				case '+':
					b = b[1:]
					minus = 1
				case '-':
					b = b[1:]
				default:
					minus = 1
				}
				vv, err := ParseUint(b)
				if err != nil {
					return -1, errInvalidFloatExponent
				}
				return float64(v) * offset * math.Pow10(minus*int(vv)), nil
			}
			return -1, errUnexpectedFloatChar
		}
		v = 10*v + uint64(c-'0')
		if pointFound {
			offset /= 10
		}
	}
	return float64(v) * offset, nil
}

var (
	errEmptyHexNum    = errors.New("empty hex number")
	errTooLargeHexNum = errors.New("too large hex number")
)

func readHexInt(r *bufio.Reader) (int, error) {
	n := 0
	i := 0
	var k int
	for {
		c, err := r.ReadByte()
		if err != nil {
			if err == io.EOF && i > 0 {
				return n, nil
			}
			return -1, err
		}
		k = int(hex2intTable[c])
		if k == 16 {
			if i == 0 {
				return -1, errEmptyHexNum
			}
			r.UnreadByte()
			return n, nil
		}
		if i >= maxHexIntChars {
			return -1, errTooLargeHexNum
		}
		n = (n << 4) | k
		i++
	}
}

var hexIntBufPool sync.Pool

func writeHexInt(w *bufio.Writer, n int) error {
	if n < 0 {
		panic("BUG: int must be positive")
	}

	v := hexIntBufPool.Get()
	if v == nil {
		v = make([]byte, maxHexIntChars+1)
	}
	buf := v.([]byte)
	i := len(buf) - 1
	for {
		buf[i] = int2hexbyte(n & 0xf)
		n >>= 4
		if n == 0 {
			break
		}
		i--
	}
	_, err := w.Write(buf[i:])
	hexIntBufPool.Put(v)
	return err
}

func int2hexbyte(n int) byte {
	if n < 10 {
		return '0' + byte(n)
	}
	return 'a' + byte(n) - 10
}

func hexCharUpper(c byte) byte {
	if c < 10 {
		return '0' + c
	}
	return c - 10 + 'A'
}

var hex2intTable = func() []byte {
	b := make([]byte, 256)
	for i := 0; i < 256; i++ {
		c := byte(16)
		if i >= '0' && i <= '9' {
			c = byte(i) - '0'
		} else if i >= 'a' && i <= 'f' {
			c = byte(i) - 'a' + 10
		} else if i >= 'A' && i <= 'F' {
			c = byte(i) - 'A' + 10
		}
		b[i] = c
	}
	return b
}()

const toLower = 'a' - 'A'

var toLowerTable = func() [256]byte {
	var a [256]byte
	for i := 0; i < 256; i++ {
		c := byte(i)
		if c >= 'A' && c <= 'Z' {
			c += toLower
		}
		a[i] = c
	}
	return a
}()

var toUpperTable = func() [256]byte {
	var a [256]byte
	for i := 0; i < 256; i++ {
		c := byte(i)
		if c >= 'a' && c <= 'z' {
			c -= toLower
		}
		a[i] = c
	}
	return a
}()

func lowercaseBytes(b []byte) {
	for i := 0; i < len(b); i++ {
		p := &b[i]
		*p = toLowerTable[*p]
	}
}

// b2s converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func b2s(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

// s2b converts string to a byte slice without memory allocation.
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func s2b(s string) []byte {
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
	bh := reflect.SliceHeader{
		Data: sh.Data,
		Len:  sh.Len,
		Cap:  sh.Len,
	}
	return *(*[]byte)(unsafe.Pointer(&bh))
}

// AppendUnquotedArg appends url-decoded src to dst and returns appended dst.
//
// dst may point to src. In this case src will be overwritten.
func AppendUnquotedArg(dst, src []byte) []byte {
	return decodeArgAppend(dst, src)
}

// AppendQuotedArg appends url-encoded src to dst and returns appended dst.
func AppendQuotedArg(dst, src []byte) []byte {
	for _, c := range src {
		// See http://www.w3.org/TR/html5/forms.html#form-submission-algorithm
		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
			c == '*' || c == '-' || c == '.' || c == '_' {
			dst = append(dst, c)
		} else {
			dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
		}
	}
	return dst
}

func appendQuotedPath(dst, src []byte) []byte {
	for _, c := range src {
		// From the spec: http://tools.ietf.org/html/rfc3986#section-3.3
		// an path can contain zero or more of pchar that is defined as follows:
		// pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"
		// pct-encoded = "%" HEXDIG HEXDIG
		// unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
		// sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
		//             / "*" / "+" / "," / ";" / "="
		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
			c == '-' || c == '.' || c == '_' || c == '~' || c == '!' || c == '$' ||
			c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' ||
			c == ',' || c == ';' || c == '=' || c == ':' || c == '@' || c == '/' {
			dst = append(dst, c)
		} else {
			dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
		}
	}
	return dst
}
<commit_msg>A faster s2b function (#637)<commit_after>package fasthttp

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"math"
	"net"
	"reflect"
	"strings"
	"sync"
	"time"
	"unsafe"
)

// AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
func AppendHTMLEscape(dst []byte, s string) []byte {
	if strings.IndexByte(s, '<') < 0 &&
		strings.IndexByte(s, '>') < 0 &&
		strings.IndexByte(s, '"') < 0 &&
		strings.IndexByte(s, '\'') < 0 {

		// fast path - nothing to escape
		return append(dst, s...)
	}

	// slow path
	var prev int
	var sub string
	for i, n := 0, len(s); i < n; i++ {
		sub = ""
		switch s[i] {
		case '<':
			sub = "<"
		case '>':
			sub = ">"
		case '"':
			sub = """
		case '\'':
			sub = "'"
		}
		if len(sub) > 0 {
			dst = append(dst, s[prev:i]...)
			dst = append(dst, sub...)
			prev = i + 1
		}
	}
	return append(dst, s[prev:]...)
}

// AppendHTMLEscapeBytes appends html-escaped s to dst and returns
// the extended dst.
func AppendHTMLEscapeBytes(dst, s []byte) []byte {
	return AppendHTMLEscape(dst, b2s(s))
}

// AppendIPv4 appends string representation of the given ip v4 to dst
// and returns the extended dst.
func AppendIPv4(dst []byte, ip net.IP) []byte {
	ip = ip.To4()
	if ip == nil {
		return append(dst, "non-v4 ip passed to AppendIPv4"...)
	}

	dst = AppendUint(dst, int(ip[0]))
	for i := 1; i < 4; i++ {
		dst = append(dst, '.')
		dst = AppendUint(dst, int(ip[i]))
	}
	return dst
}

var errEmptyIPStr = errors.New("empty ip address string")

// ParseIPv4 parses ip address from ipStr into dst and returns the extended dst.
func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
	if len(ipStr) == 0 {
		return dst, errEmptyIPStr
	}
	if len(dst) < net.IPv4len {
		dst = make([]byte, net.IPv4len)
	}
	copy(dst, net.IPv4zero)
	dst = dst.To4()
	if dst == nil {
		panic("BUG: dst must not be nil")
	}

	b := ipStr
	for i := 0; i < 3; i++ {
		n := bytes.IndexByte(b, '.')
		if n < 0 {
			return dst, fmt.Errorf("cannot find dot in ipStr %q", ipStr)
		}
		v, err := ParseUint(b[:n])
		if err != nil {
			return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
		}
		if v > 255 {
			return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
		}
		dst[i] = byte(v)
		b = b[n+1:]
	}
	v, err := ParseUint(b)
	if err != nil {
		return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
	}
	if v > 255 {
		return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
	}
	dst[3] = byte(v)

	return dst, nil
}

// AppendHTTPDate appends HTTP-compliant (RFC1123) representation of date
// to dst and returns the extended dst.
func AppendHTTPDate(dst []byte, date time.Time) []byte {
	dst = date.In(time.UTC).AppendFormat(dst, time.RFC1123)
	copy(dst[len(dst)-3:], strGMT)
	return dst
}

// ParseHTTPDate parses HTTP-compliant (RFC1123) date.
func ParseHTTPDate(date []byte) (time.Time, error) {
	return time.Parse(time.RFC1123, b2s(date))
}

// AppendUint appends n to dst and returns the extended dst.
func AppendUint(dst []byte, n int) []byte {
	if n < 0 {
		panic("BUG: int must be positive")
	}

	var b [20]byte
	buf := b[:]
	i := len(buf)
	var q int
	for n >= 10 {
		i--
		q = n / 10
		buf[i] = '0' + byte(n-q*10)
		n = q
	}
	i--
	buf[i] = '0' + byte(n)

	dst = append(dst, buf[i:]...)
	return dst
}

// ParseUint parses uint from buf.
func ParseUint(buf []byte) (int, error) {
	v, n, err := parseUintBuf(buf)
	if n != len(buf) {
		return -1, errUnexpectedTrailingChar
	}
	return v, err
}

var (
	errEmptyInt               = errors.New("empty integer")
	errUnexpectedFirstChar    = errors.New("unexpected first char found. Expecting 0-9")
	errUnexpectedTrailingChar = errors.New("unexpected trailing char found. Expecting 0-9")
	errTooLongInt             = errors.New("too long int")
)

func parseUintBuf(b []byte) (int, int, error) {
	n := len(b)
	if n == 0 {
		return -1, 0, errEmptyInt
	}
	v := 0
	for i := 0; i < n; i++ {
		c := b[i]
		k := c - '0'
		if k > 9 {
			if i == 0 {
				return -1, i, errUnexpectedFirstChar
			}
			return v, i, nil
		}
		// Test for overflow.
		if v*10 < v {
			return -1, i, errTooLongInt
		}
		v = 10*v + int(k)
	}
	return v, n, nil
}

var (
	errEmptyFloat           = errors.New("empty float number")
	errDuplicateFloatPoint  = errors.New("duplicate point found in float number")
	errUnexpectedFloatEnd   = errors.New("unexpected end of float number")
	errInvalidFloatExponent = errors.New("invalid float number exponent")
	errUnexpectedFloatChar  = errors.New("unexpected char found in float number")
)

// ParseUfloat parses unsigned float from buf.
func ParseUfloat(buf []byte) (float64, error) {
	if len(buf) == 0 {
		return -1, errEmptyFloat
	}
	b := buf
	var v uint64
	var offset = 1.0
	var pointFound bool
	for i, c := range b {
		if c < '0' || c > '9' {
			if c == '.' {
				if pointFound {
					return -1, errDuplicateFloatPoint
				}
				pointFound = true
				continue
			}
			if c == 'e' || c == 'E' {
				if i+1 >= len(b) {
					return -1, errUnexpectedFloatEnd
				}
				b = b[i+1:]
				minus := -1
				switch b[0] {
				case '+':
					b = b[1:]
					minus = 1
				case '-':
					b = b[1:]
				default:
					minus = 1
				}
				vv, err := ParseUint(b)
				if err != nil {
					return -1, errInvalidFloatExponent
				}
				return float64(v) * offset * math.Pow10(minus*int(vv)), nil
			}
			return -1, errUnexpectedFloatChar
		}
		v = 10*v + uint64(c-'0')
		if pointFound {
			offset /= 10
		}
	}
	return float64(v) * offset, nil
}

var (
	errEmptyHexNum    = errors.New("empty hex number")
	errTooLargeHexNum = errors.New("too large hex number")
)

func readHexInt(r *bufio.Reader) (int, error) {
	n := 0
	i := 0
	var k int
	for {
		c, err := r.ReadByte()
		if err != nil {
			if err == io.EOF && i > 0 {
				return n, nil
			}
			return -1, err
		}
		k = int(hex2intTable[c])
		if k == 16 {
			if i == 0 {
				return -1, errEmptyHexNum
			}
			r.UnreadByte()
			return n, nil
		}
		if i >= maxHexIntChars {
			return -1, errTooLargeHexNum
		}
		n = (n << 4) | k
		i++
	}
}

var hexIntBufPool sync.Pool

func writeHexInt(w *bufio.Writer, n int) error {
	if n < 0 {
		panic("BUG: int must be positive")
	}

	v := hexIntBufPool.Get()
	if v == nil {
		v = make([]byte, maxHexIntChars+1)
	}
	buf := v.([]byte)
	i := len(buf) - 1
	for {
		buf[i] = int2hexbyte(n & 0xf)
		n >>= 4
		if n == 0 {
			break
		}
		i--
	}
	_, err := w.Write(buf[i:])
	hexIntBufPool.Put(v)
	return err
}

func int2hexbyte(n int) byte {
	if n < 10 {
		return '0' + byte(n)
	}
	return 'a' + byte(n) - 10
}

func hexCharUpper(c byte) byte {
	if c < 10 {
		return '0' + c
	}
	return c - 10 + 'A'
}

var hex2intTable = func() []byte {
	b := make([]byte, 256)
	for i := 0; i < 256; i++ {
		c := byte(16)
		if i >= '0' && i <= '9' {
			c = byte(i) - '0'
		} else if i >= 'a' && i <= 'f' {
			c = byte(i) - 'a' + 10
		} else if i >= 'A' && i <= 'F' {
			c = byte(i) - 'A' + 10
		}
		b[i] = c
	}
	return b
}()

const toLower = 'a' - 'A'

var toLowerTable = func() [256]byte {
	var a [256]byte
	for i := 0; i < 256; i++ {
		c := byte(i)
		if c >= 'A' && c <= 'Z' {
			c += toLower
		}
		a[i] = c
	}
	return a
}()

var toUpperTable = func() [256]byte {
	var a [256]byte
	for i := 0; i < 256; i++ {
		c := byte(i)
		if c >= 'a' && c <= 'z' {
			c -= toLower
		}
		a[i] = c
	}
	return a
}()

func lowercaseBytes(b []byte) {
	for i := 0; i < len(b); i++ {
		p := &b[i]
		*p = toLowerTable[*p]
	}
}

// b2s converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func b2s(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

// s2b converts string to a byte slice without memory allocation.
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func s2b(s string) (b []byte) {
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
	bh.Data = sh.Data
	bh.Len = sh.Len
	bh.Cap = sh.Len
	return b
}

// AppendUnquotedArg appends url-decoded src to dst and returns appended dst.
//
// dst may point to src. In this case src will be overwritten.
func AppendUnquotedArg(dst, src []byte) []byte {
	return decodeArgAppend(dst, src)
}

// AppendQuotedArg appends url-encoded src to dst and returns appended dst.
func AppendQuotedArg(dst, src []byte) []byte {
	for _, c := range src {
		// See http://www.w3.org/TR/html5/forms.html#form-submission-algorithm
		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
			c == '*' || c == '-' || c == '.' || c == '_' {
			dst = append(dst, c)
		} else {
			dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
		}
	}
	return dst
}

func appendQuotedPath(dst, src []byte) []byte {
	for _, c := range src {
		// From the spec: http://tools.ietf.org/html/rfc3986#section-3.3
		// an path can contain zero or more of pchar that is defined as follows:
		// pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"
		// pct-encoded = "%" HEXDIG HEXDIG
		// unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
		// sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
		//             / "*" / "+" / "," / ";" / "="
		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
			c == '-' || c == '.' || c == '_' || c == '~' || c == '!' || c == '$' ||
			c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' ||
			c == ',' || c == ';' || c == '=' || c == ':' || c == '@' || c == '/' {
			dst = append(dst, c)
		} else {
			dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
		}
	}
	return dst
}
<|endoftext|>