<commit_before>// Package convertkit provides a client to the ConvertKit API v3.
// See http://help.convertkit.com/article/33-api-documentation-v3
package convertkit

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

type Config struct {
	Endpoint string
	Key      string
	Secret   string

	HTTPClient *http.Client
}

func DefaultConfig() *Config {
	c := Config{
		Endpoint:   "https://api.convertkit.com",
		HTTPClient: http.DefaultClient,
	}
	if v := os.Getenv("CONVERTKIT_ENDPOINT"); v != "" {
		c.Endpoint = v
	}
	if v := os.Getenv("CONVERTKIT_API_KEY"); v != "" {
		c.Key = v
	}
	if v := os.Getenv("CONVERTKIT_API_SECRET"); v != "" {
		c.Secret = v
	}
	return &c
}

type Client struct {
	config *Config
}

func NewClient(c *Config) (*Client, error) {
	defConfig := DefaultConfig()
	if c.Endpoint == "" {
		c.Endpoint = defConfig.Endpoint
	}
	if c.Key == "" {
		c.Key = defConfig.Key
	}
	if c.Secret == "" {
		c.Secret = defConfig.Secret
	}
	if c.HTTPClient == nil {
		c.HTTPClient = defConfig.HTTPClient
	}
	return &Client{config: c}, nil
}

type Subscriber struct {
	ID           int               `json:"id"`
	FirstName    string            `json:"first_name"`
	EmailAddress string            `json:"email_address"`
	State        string            `json:"state"`
	CreatedAt    time.Time         `json:"created_at"`
	Fields       map[string]string `json:"fields"`
}

type subscriberResponse struct {
	TotalSubscribers int          `json:"total_subscribers"`
	Page             int          `json:"page"`
	TotalPages       int          `json:"total_pages"`
	Subscribers      []Subscriber `json:"subscribers"`
}

func (c *Client) Subscribers() ([]Subscriber, error) {
	var subscribers []Subscriber
	page := 1

	for {
		url := fmt.Sprintf("%s/v3/subscribers?api_secret=%s&page=%d",
			c.config.Endpoint, c.config.Secret, page)

		var resp subscriberResponse
		if err := c.sendRequest("GET", url, nil, &resp); err != nil {
			return nil, err
		}
		subscribers = append(subscribers, resp.Subscribers...)

		if page >= resp.TotalPages {
			break
		}
		page += 1
	}

	return subscribers, nil
}

func (c *Client) TotalSubscribers() (int, error) {
	url := fmt.Sprintf("%s/v3/subscribers?api_secret=%s",
		c.config.Endpoint, c.config.Secret)

	var resp subscriberResponse
	if err := c.sendRequest("GET", url, nil, &resp); err != nil {
		return 0, err
	}

	return resp.TotalSubscribers, nil
}

func (c *Client) sendRequest(method, url string, body io.Reader, out interface{}) error {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return err
	}

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

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP error: %s", resp.Status)
	}

	return json.NewDecoder(resp.Body).Decode(out)
}
<commit_msg>Document public API<commit_after>// Package convertkit provides a client to the ConvertKit API v3.
// See http://help.convertkit.com/article/33-api-documentation-v3
package convertkit

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

// Config is used to configure the creation of the client.
type Config struct {
	Endpoint string
	Key      string
	Secret   string

	HTTPClient *http.Client
}

// DefaultConfig returns a default configuration for the client. It parses the
// environment variables CONVERTKIT_ENDPOINT, CONVERTKIT_API_KEY, and
// CONVERTKIT_API_SECRET.
func DefaultConfig() *Config {
	c := Config{
		Endpoint:   "https://api.convertkit.com",
		HTTPClient: http.DefaultClient,
	}
	if v := os.Getenv("CONVERTKIT_ENDPOINT"); v != "" {
		c.Endpoint = v
	}
	if v := os.Getenv("CONVERTKIT_API_KEY"); v != "" {
		c.Key = v
	}
	if v := os.Getenv("CONVERTKIT_API_SECRET"); v != "" {
		c.Secret = v
	}
	return &c
}

// Client is the client to the ConvertKit API. Create a client with NewClient.
type Client struct {
	config *Config
}

// NewClient returns a new client for the given configuration.
func NewClient(c *Config) (*Client, error) {
	defConfig := DefaultConfig()
	if c.Endpoint == "" {
		c.Endpoint = defConfig.Endpoint
	}
	if c.Key == "" {
		c.Key = defConfig.Key
	}
	if c.Secret == "" {
		c.Secret = defConfig.Secret
	}
	if c.HTTPClient == nil {
		c.HTTPClient = defConfig.HTTPClient
	}
	return &Client{config: c}, nil
}

// Subscriber describes a ConvertKit subscriber.
type Subscriber struct {
	ID           int               `json:"id"`
	FirstName    string            `json:"first_name"`
	EmailAddress string            `json:"email_address"`
	State        string            `json:"state"`
	CreatedAt    time.Time         `json:"created_at"`
	Fields       map[string]string `json:"fields"`
}

type subscriberResponse struct {
	TotalSubscribers int          `json:"total_subscribers"`
	Page             int          `json:"page"`
	TotalPages       int          `json:"total_pages"`
	Subscribers      []Subscriber `json:"subscribers"`
}

// Subscribers returns a list of all confirmed subscribers.
func (c *Client) Subscribers() ([]Subscriber, error) {
	var subscribers []Subscriber
	page := 1

	for {
		url := fmt.Sprintf("%s/v3/subscribers?api_secret=%s&page=%d",
			c.config.Endpoint, c.config.Secret, page)

		var resp subscriberResponse
		if err := c.sendRequest("GET", url, nil, &resp); err != nil {
			return nil, err
		}
		subscribers = append(subscribers, resp.Subscribers...)

		if page >= resp.TotalPages {
			break
		}
		page += 1
	}

	return subscribers, nil
}

// TotalSubscribers returns the number of confirmed subscribers.
func (c *Client) TotalSubscribers() (int, error) {
	url := fmt.Sprintf("%s/v3/subscribers?api_secret=%s",
		c.config.Endpoint, c.config.Secret)

	var resp subscriberResponse
	if err := c.sendRequest("GET", url, nil, &resp); err != nil {
		return 0, err
	}

	return resp.TotalSubscribers, nil
}

func (c *Client) sendRequest(method, url string, body io.Reader, out interface{}) error {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return err
	}

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

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP error: %s", resp.Status)
	}

	return json.NewDecoder(resp.Body).Decode(out)
}
<|endoftext|>


<commit_before>package commands

import (
	"bytes"
	"fmt"
	"io"

	cmds "github.com/ipfs/go-ipfs/commands"
	bitswap "github.com/ipfs/go-ipfs/exchange/bitswap"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
	u "github.com/ipfs/go-ipfs/util"
)

var BitswapCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "A set of commands to manipulate the bitswap agent",
		ShortDescription: ``,
	},
	Subcommands: map[string]*cmds.Command{
		"wantlist": showWantlistCmd,
		"stat":     bitswapStatCmd,
	},
}

var showWantlistCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Show blocks currently on the wantlist",
		ShortDescription: `
Print out all blocks currently on the bitswap wantlist for the local peer`,
	},
	Options: []cmds.Option{
		cmds.StringOption("peer", "p", "specify which peer to show wantlist for (default self)"),
	},
	Type: KeyList{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		pstr, found, err := req.Option("peer").String()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if found {
			pid, err := peer.IDB58Decode(pstr)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			res.SetOutput(&KeyList{bs.WantlistForPeer(pid)})
		} else {
			res.SetOutput(&KeyList{bs.GetWantlist()})
		}
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: KeyListTextMarshaler,
	},
}

var bitswapStatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "show some diagnostic information on the bitswap agent",
		ShortDescription: ``,
	},
	Type: bitswap.Stat{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		st, err := bs.Stat()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(st)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			out, ok := res.Output().(*bitswap.Stat)
			if !ok {
				return nil, u.ErrCast()
			}
			buf := new(bytes.Buffer)
			fmt.Fprintln(buf, "bitswap status")
			fmt.Fprintf(buf, "\tprovides buffer: %d / %d\n", out.ProvideBufLen, bitswap.HasBlockBufferSize)
			fmt.Fprintf(buf, "\tblocks received: %d\n", out.BlocksReceived)
			fmt.Fprintf(buf, "\tdup blocks received: %d\n", out.DupBlksReceived)
			fmt.Fprintf(buf, "\twantlist [%d keys]\n", len(out.Wantlist))
			for _, k := range out.Wantlist {
				fmt.Fprintf(buf, "\t\t%s\n", k.B58String())
			}
			fmt.Fprintf(buf, "\tpartners [%d]\n", len(out.Peers))
			for _, p := range out.Peers {
				fmt.Fprintf(buf, "\t\t%s\n", p)
			}
			return buf, nil
		},
	},
}
<commit_msg>make bitswap commands error out properly offline<commit_after>package commands

import (
	"bytes"
	"fmt"
	"io"

	cmds "github.com/ipfs/go-ipfs/commands"
	bitswap "github.com/ipfs/go-ipfs/exchange/bitswap"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
	u "github.com/ipfs/go-ipfs/util"
)

var BitswapCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "A set of commands to manipulate the bitswap agent",
		ShortDescription: ``,
	},
	Subcommands: map[string]*cmds.Command{
		"wantlist": showWantlistCmd,
		"stat":     bitswapStatCmd,
	},
}

var showWantlistCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Show blocks currently on the wantlist",
		ShortDescription: `
Print out all blocks currently on the bitswap wantlist for the local peer`,
	},
	Options: []cmds.Option{
		cmds.StringOption("peer", "p", "specify which peer to show wantlist for (default self)"),
	},
	Type: KeyList{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		pstr, found, err := req.Option("peer").String()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if found {
			pid, err := peer.IDB58Decode(pstr)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			res.SetOutput(&KeyList{bs.WantlistForPeer(pid)})
		} else {
			res.SetOutput(&KeyList{bs.GetWantlist()})
		}
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: KeyListTextMarshaler,
	},
}

var bitswapStatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "show some diagnostic information on the bitswap agent",
		ShortDescription: ``,
	},
	Type: bitswap.Stat{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		st, err := bs.Stat()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(st)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			out, ok := res.Output().(*bitswap.Stat)
			if !ok {
				return nil, u.ErrCast()
			}
			buf := new(bytes.Buffer)
			fmt.Fprintln(buf, "bitswap status")
			fmt.Fprintf(buf, "\tprovides buffer: %d / %d\n", out.ProvideBufLen, bitswap.HasBlockBufferSize)
			fmt.Fprintf(buf, "\tblocks received: %d\n", out.BlocksReceived)
			fmt.Fprintf(buf, "\tdup blocks received: %d\n", out.DupBlksReceived)
			fmt.Fprintf(buf, "\twantlist [%d keys]\n", len(out.Wantlist))
			for _, k := range out.Wantlist {
				fmt.Fprintf(buf, "\t\t%s\n", k.B58String())
			}
			fmt.Fprintf(buf, "\tpartners [%d]\n", len(out.Peers))
			for _, p := range out.Peers {
				fmt.Fprintf(buf, "\t\t%s\n", p)
			}
			return buf, nil
		},
	},
}
<|endoftext|>


<commit_before>package dagcmd

import (
	"bytes"
	"fmt"
	"io"
	"math"
	"strings"

	cmds "github.com/ipfs/go-ipfs/commands"
	coredag "github.com/ipfs/go-ipfs/core/coredag"
	path "github.com/ipfs/go-ipfs/path"
	pin "github.com/ipfs/go-ipfs/pin"

	cid "gx/ipfs/QmTprEaAA2A9bst5XH7exuyi5KzNMK3SEDNN8rBDnKWcUS/go-cid"
	mh "gx/ipfs/QmU9a9NV9RdPNwZQDYd5uKsm6N6LJLSvLbywDDYFbaaC6P/go-multihash"
)

var DagCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Interact with ipld dag objects.",
		ShortDescription: `
'ipfs dag' is used for creating and manipulating dag objects.

This subcommand is currently an experimental feature, but it is intended
to deprecate and replace the existing 'ipfs object' command moving forward.
		`,
	},
	Subcommands: map[string]*cmds.Command{
		"put":     DagPutCmd,
		"get":     DagGetCmd,
		"resolve": DagResolveCmd,
	},
}

type OutputObject struct {
	Cid *cid.Cid
}

type ResolveOutput struct {
	Cid     *cid.Cid
	RemPath string
}

var DagPutCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Add a dag node to ipfs.",
		ShortDescription: `
'ipfs dag put' accepts input from a file or stdin and parses it
into an object of the specified format.
`,
	},
	Arguments: []cmds.Argument{
		cmds.FileArg("object data", true, false, "The object to put").EnableStdin(),
	},
	Options: []cmds.Option{
		cmds.StringOption("format", "f", "Format that the object will be added as.").Default("cbor"),
		cmds.StringOption("input-enc", "Format that the input object will be.").Default("json"),
		cmds.BoolOption("pin", "Pin this object when adding.").Default(false),
		cmds.StringOption("hash", "Hash function to use").Default(""),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		fi, err := req.Files().NextFile()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		ienc, _, _ := req.Option("input-enc").String()
		format, _, _ := req.Option("format").String()
		hash, _, err := req.Option("hash").String()
		dopin, _, err := req.Option("pin").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		// mhType tells inputParser which hash should be used. MaxUint64 means 'use
		// default hash' (sha256 for cbor, sha1 for git..)
		mhType := uint64(math.MaxUint64)

		if hash != "" {
			var ok bool
			mhType, ok = mh.Names[hash]
			if !ok {
				res.SetError(fmt.Errorf("%s in not a valid multihash name", hash), cmds.ErrNormal)
				return
			}
		}

		if dopin {
			defer n.Blockstore.PinLock().Unlock()
		}

		nds, err := coredag.ParseInputs(ienc, format, fi, mhType, -1)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if len(nds) == 0 {
			res.SetError(fmt.Errorf("no node returned from ParseInputs"), cmds.ErrNormal)
			return
		}

		b := n.DAG.Batch()
		for _, nd := range nds {
			_, err := b.Add(nd)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}

		if err := b.Commit(); err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		root := nds[0].Cid()
		if dopin {
			n.Pinning.PinWithMode(root, pin.Recursive)

			err := n.Pinning.Flush()
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}

		res.SetOutput(&OutputObject{Cid: root})
	},
	Type: OutputObject{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			oobj, ok := res.Output().(*OutputObject)
			if !ok {
				return nil, fmt.Errorf("expected a different object in marshaler")
			}

			return strings.NewReader(oobj.Cid.String()), nil
		},
	},
}

var DagGetCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Get a dag node from ipfs.",
		ShortDescription: `
'ipfs dag get' fetches a dag node from ipfs and prints it out in the specifed format.
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("ref", true, false, "The object to get").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		obj, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		var out interface{} = obj
		if len(rem) > 0 {
			final, _, err := obj.Resolve(rem)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			out = final
		}

		res.SetOutput(out)
	},
}

var DagResolveCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Resolve ipld block",
		ShortDescription: `
'ipfs dag resolve' fetches a dag node from ipfs, prints it's address and remaining path.
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("ref", true, false, "The path to resolve").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		obj, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(&ResolveOutput{
			Cid:     obj.Cid(),
			RemPath: path.Join(rem),
		})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			output := res.Output().(*ResolveOutput)
			buf := new(bytes.Buffer)
			p := output.Cid.String()
			if output.RemPath != "" {
				p = path.Join([]string{p, output.RemPath})
			}

			buf.WriteString(p)

			return buf, nil
		},
	},
	Type: ResolveOutput{},
}
<commit_msg>dag: fix codeclimate<commit_after>package dagcmd

import (
	"bytes"
	"fmt"
	"io"
	"math"
	"strings"

	cmds "github.com/ipfs/go-ipfs/commands"
	coredag "github.com/ipfs/go-ipfs/core/coredag"
	path "github.com/ipfs/go-ipfs/path"
	pin "github.com/ipfs/go-ipfs/pin"

	cid "gx/ipfs/QmTprEaAA2A9bst5XH7exuyi5KzNMK3SEDNN8rBDnKWcUS/go-cid"
	mh "gx/ipfs/QmU9a9NV9RdPNwZQDYd5uKsm6N6LJLSvLbywDDYFbaaC6P/go-multihash"
)

var DagCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Interact with ipld dag objects.",
		ShortDescription: `
'ipfs dag' is used for creating and manipulating dag objects.

This subcommand is currently an experimental feature, but it is intended
to deprecate and replace the existing 'ipfs object' command moving forward.
		`,
	},
	Subcommands: map[string]*cmds.Command{
		"put":     DagPutCmd,
		"get":     DagGetCmd,
		"resolve": DagResolveCmd,
	},
}

// OutputObject is the output type of 'dag put' command
type OutputObject struct {
	Cid *cid.Cid
}

// ResolveOutput is the output type of 'dag resolve' command
type ResolveOutput struct {
	Cid     *cid.Cid
	RemPath string
}

var DagPutCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Add a dag node to ipfs.",
		ShortDescription: `
'ipfs dag put' accepts input from a file or stdin and parses it
into an object of the specified format.
`,
	},
	Arguments: []cmds.Argument{
		cmds.FileArg("object data", true, false, "The object to put").EnableStdin(),
	},
	Options: []cmds.Option{
		cmds.StringOption("format", "f", "Format that the object will be added as.").Default("cbor"),
		cmds.StringOption("input-enc", "Format that the input object will be.").Default("json"),
		cmds.BoolOption("pin", "Pin this object when adding.").Default(false),
		cmds.StringOption("hash", "Hash function to use").Default(""),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		fi, err := req.Files().NextFile()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		ienc, _, _ := req.Option("input-enc").String()
		format, _, _ := req.Option("format").String()
		hash, _, err := req.Option("hash").String()
		dopin, _, err := req.Option("pin").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		// mhType tells inputParser which hash should be used. MaxUint64 means 'use
		// default hash' (sha256 for cbor, sha1 for git..)
		mhType := uint64(math.MaxUint64)

		if hash != "" {
			var ok bool
			mhType, ok = mh.Names[hash]
			if !ok {
				res.SetError(fmt.Errorf("%s in not a valid multihash name", hash), cmds.ErrNormal)
				return
			}
		}

		if dopin {
			defer n.Blockstore.PinLock().Unlock()
		}

		nds, err := coredag.ParseInputs(ienc, format, fi, mhType, -1)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if len(nds) == 0 {
			res.SetError(fmt.Errorf("no node returned from ParseInputs"), cmds.ErrNormal)
			return
		}

		b := n.DAG.Batch()
		for _, nd := range nds {
			_, err := b.Add(nd)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}

		if err := b.Commit(); err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		root := nds[0].Cid()
		if dopin {
			n.Pinning.PinWithMode(root, pin.Recursive)

			err := n.Pinning.Flush()
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}

		res.SetOutput(&OutputObject{Cid: root})
	},
	Type: OutputObject{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			oobj, ok := res.Output().(*OutputObject)
			if !ok {
				return nil, fmt.Errorf("expected a different object in marshaler")
			}

			return strings.NewReader(oobj.Cid.String()), nil
		},
	},
}

var DagGetCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Get a dag node from ipfs.",
		ShortDescription: `
'ipfs dag get' fetches a dag node from ipfs and prints it out in the specifed format.
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("ref", true, false, "The object to get").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		obj, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		var out interface{} = obj
		if len(rem) > 0 {
			final, _, err := obj.Resolve(rem)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			out = final
		}

		res.SetOutput(out)
	},
}

// DagResolveCmd returns address of highest block within a path and a path remainder
var DagResolveCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Resolve ipld block",
		ShortDescription: `
'ipfs dag resolve' fetches a dag node from ipfs, prints it's address and remaining path.
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("ref", true, false, "The path to resolve").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		obj, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(&ResolveOutput{
			Cid:     obj.Cid(),
			RemPath: path.Join(rem),
		})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			output := res.Output().(*ResolveOutput)
			buf := new(bytes.Buffer)
			p := output.Cid.String()
			if output.RemPath != "" {
				p = path.Join([]string{p, output.RemPath})
			}

			buf.WriteString(p)

			return buf, nil
		},
	},
	Type: ResolveOutput{},
}
<|endoftext|>


<commit_before>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package handler

import (
	"time"

	ttnlog "github.com/TheThingsNetwork/go-utils/log"
	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	"github.com/TheThingsNetwork/ttn/api/trace"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/TheThingsNetwork/ttn/utils/errors"
)

func (h *handler) EnqueueDownlink(appDownlink *types.DownlinkMessage) (err error) {
	appID, devID := appDownlink.AppID, appDownlink.DevID
	ctx := h.Ctx.WithFields(ttnlog.Fields{
		"AppID": appID,
		"DevID": devID,
	})

	start := time.Now()
	defer func() {
		if err != nil {
			ctx.WithError(err).Warn("Could not enqueue downlink")
		} else {
			ctx.WithField("Duration", time.Now().Sub(start)).Debug("Enqueued downlink")
		}
	}()

	// Check if device exists
	dev, err := h.devices.Get(appID, devID)
	if err != nil {
		return err
	}
	dev.StartUpdate()

	defer func() {
		if err != nil {
			h.qEvent <- &types.DeviceEvent{
				AppID: appID,
				DevID: devID,
				Event: types.DownlinkErrorEvent,
				Data: types.DownlinkEventData{
					ErrorEventData: types.ErrorEventData{Error: err.Error()},
					Message:        appDownlink,
				},
			}
		}
	}()

	// Clear redundant fields
	appDownlink.AppID = ""
	appDownlink.DevID = ""

	queue, err := h.devices.DownlinkQueue(appID, devID)
	if err != nil {
		return err
	}

	schedule := appDownlink.Schedule
	appDownlink.Schedule = ""

	switch schedule {
	case types.ScheduleReplace, "": // Empty string for default
		dev.CurrentDownlink = nil
		err = queue.Replace(appDownlink)
	case types.ScheduleFirst:
		err = queue.PushFirst(appDownlink)
	case types.ScheduleLast:
		err = queue.PushLast(appDownlink)
	default:
		return errors.NewErrInvalidArgument("ScheduleType", "unknown")
	}

	if err != nil {
		return err
	}

	if err := h.devices.Set(dev); err != nil {
		return err
	}

	h.qEvent <- &types.DeviceEvent{
		AppID: appID,
		DevID: devID,
		Event: types.DownlinkScheduledEvent,
		Data: types.DownlinkEventData{
			Message: appDownlink,
		},
	}
	return nil
}

func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *pb_broker.DownlinkMessage) (err error) {
	appID, devID := appDownlink.AppID, appDownlink.DevID

	ctx := h.Ctx.WithFields(ttnlog.Fields{
		"AppID":  appID,
		"DevID":  devID,
		"AppEUI": downlink.AppEui,
		"DevEUI": downlink.DevEui,
	})

	defer func() {
		if err != nil {
			h.qEvent <- &types.DeviceEvent{
				AppID: appID,
				DevID: devID,
				Event: types.DownlinkErrorEvent,
				Data: types.DownlinkEventData{
					ErrorEventData: types.ErrorEventData{Error: err.Error()},
					Message:        appDownlink,
				},
			}
			ctx.WithError(err).Warn("Could not handle downlink")
			downlink.Trace = downlink.Trace.WithEvent(trace.DropEvent, "reason", err)
		}
		if downlink != nil && h.monitorStream != nil {
			h.monitorStream.Send(downlink)
		}
	}()

	dev, err := h.devices.Get(appID, devID)
	if err != nil {
		return err
	}
	dev.StartUpdate()
	defer func() {
		setErr := h.devices.Set(dev)
		if err == nil {
			err = setErr
		}
	}()

	// Get Processors
	processors := []DownlinkProcessor{
		h.ConvertFieldsDown,
		h.ConvertToLoRaWAN,
	}

	ctx.WithField("NumProcessors", len(processors)).Debug("Running Downlink Processors")
	downlink.Trace = downlink.Trace.WithEvent("process downlink")

	// Run Processors
	for _, processor := range processors {
		err = processor(ctx, appDownlink, downlink, dev)
		if err == ErrNotNeeded {
			err = nil
			return nil
		} else if err != nil {
			return err
		}
	}

	downlink.Message = nil
	downlink.UnmarshalPayload()

	h.status.downlink.Mark(1)

	ctx.Debug("Send Downlink")

	downlink.Trace = downlink.Trace.WithEvent(trace.ForwardEvent, "broker", h.ttnBrokerID)

	h.downlink <- downlink

	downlinkConfig := types.DownlinkEventConfigInfo{}

	if downlink.DownlinkOption.ProtocolConfig != nil {
		if lorawan := downlink.DownlinkOption.ProtocolConfig.GetLorawan(); lorawan != nil {
			downlinkConfig.Modulation = lorawan.Modulation.String()
			downlinkConfig.DataRate = lorawan.DataRate
			downlinkConfig.BitRate = uint(lorawan.BitRate)
			downlinkConfig.FCnt = uint(lorawan.FCnt)
		}
	}
	if gateway := downlink.DownlinkOption.GatewayConfig; gateway != nil {
		downlinkConfig.Frequency = uint(downlink.DownlinkOption.GatewayConfig.Frequency)
		downlinkConfig.Power = int(downlink.DownlinkOption.GatewayConfig.Power)
	}

	h.qEvent <- &types.DeviceEvent{
		AppID: appDownlink.AppID,
		DevID: appDownlink.DevID,
		Event: types.DownlinkSentEvent,
		Data: types.DownlinkEventData{
			Payload:   downlink.Payload,
			Message:   appDownlink,
			GatewayID: downlink.DownlinkOption.GatewayId,
			Config:    downlinkConfig,
		},
	}
	return nil
}
<commit_msg>Validate downlink payload<commit_after>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package handler

import (
	"time"

	ttnlog "github.com/TheThingsNetwork/go-utils/log"
	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	"github.com/TheThingsNetwork/ttn/api/trace"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/TheThingsNetwork/ttn/utils/errors"
)

func (h *handler) EnqueueDownlink(appDownlink *types.DownlinkMessage) (err error) {
	appID, devID := appDownlink.AppID, appDownlink.DevID
	ctx := h.Ctx.WithFields(ttnlog.Fields{
		"AppID": appID,
		"DevID": devID,
	})

	start := time.Now()
	defer func() {
		if err != nil {
			ctx.WithError(err).Warn("Could not enqueue downlink")
		} else {
			ctx.WithField("Duration", time.Now().Sub(start)).Debug("Enqueued downlink")
		}
	}()

	// Check if device exists
	dev, err := h.devices.Get(appID, devID)
	if err != nil {
		return err
	}
	dev.StartUpdate()

	defer func() {
		if err != nil {
			h.qEvent <- &types.DeviceEvent{
				AppID: appID,
				DevID: devID,
				Event: types.DownlinkErrorEvent,
				Data: types.DownlinkEventData{
					ErrorEventData: types.ErrorEventData{Error: err.Error()},
					Message:        appDownlink,
				},
			}
		}
	}()

	if len(appDownlink.PayloadRaw) == 0 && len(appDownlink.PayloadFields) == 0 {
		return errors.NewErrInvalidArgument("Downlink Payload", "empty")
	}

	// Clear redundant fields
	appDownlink.AppID = ""
	appDownlink.DevID = ""

	queue, err := h.devices.DownlinkQueue(appID, devID)
	if err != nil {
		return err
	}

	schedule := appDownlink.Schedule
	appDownlink.Schedule = ""

	switch schedule {
	case types.ScheduleReplace, "": // Empty string for default
		dev.CurrentDownlink = nil
		err = queue.Replace(appDownlink)
	case types.ScheduleFirst:
		err = queue.PushFirst(appDownlink)
	case types.ScheduleLast:
		err = queue.PushLast(appDownlink)
	default:
		return errors.NewErrInvalidArgument("ScheduleType", "unknown")
	}

	if err != nil {
		return err
	}

	if err := h.devices.Set(dev); err != nil {
		return err
	}

	h.qEvent <- &types.DeviceEvent{
		AppID: appID,
		DevID: devID,
		Event: types.DownlinkScheduledEvent,
		Data: types.DownlinkEventData{
			Message: appDownlink,
		},
	}
	return nil
}

func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *pb_broker.DownlinkMessage) (err error) {
	appID, devID := appDownlink.AppID, appDownlink.DevID

	ctx := h.Ctx.WithFields(ttnlog.Fields{
		"AppID":  appID,
		"DevID":  devID,
		"AppEUI": downlink.AppEui,
		"DevEUI": downlink.DevEui,
	})

	defer func() {
		if err != nil {
			h.qEvent <- &types.DeviceEvent{
				AppID: appID,
				DevID: devID,
				Event: types.DownlinkErrorEvent,
				Data: types.DownlinkEventData{
					ErrorEventData: types.ErrorEventData{Error: err.Error()},
					Message:        appDownlink,
				},
			}
			ctx.WithError(err).Warn("Could not handle downlink")
			downlink.Trace = downlink.Trace.WithEvent(trace.DropEvent, "reason", err)
		}
		if downlink != nil && h.monitorStream != nil {
			h.monitorStream.Send(downlink)
		}
	}()

	dev, err := h.devices.Get(appID, devID)
	if err != nil {
		return err
	}
	dev.StartUpdate()
	defer func() {
		setErr := h.devices.Set(dev)
		if err == nil {
			err = setErr
		}
	}()

	// Get Processors
	processors := []DownlinkProcessor{
		h.ConvertFieldsDown,
		h.ConvertToLoRaWAN,
	}

	ctx.WithField("NumProcessors", len(processors)).Debug("Running Downlink Processors")
	downlink.Trace = downlink.Trace.WithEvent("process downlink")

	// Run Processors
	for _, processor := range processors {
		err = processor(ctx, appDownlink, downlink, dev)
		if err == ErrNotNeeded {
			err = nil
			return nil
		} else if err != nil {
			return err
		}
	}

	downlink.Message = nil
	downlink.UnmarshalPayload()

	h.status.downlink.Mark(1)

	ctx.Debug("Send Downlink")

	downlink.Trace = downlink.Trace.WithEvent(trace.ForwardEvent, "broker", h.ttnBrokerID)

	h.downlink <- downlink

	downlinkConfig := types.DownlinkEventConfigInfo{}

	if downlink.DownlinkOption.ProtocolConfig != nil {
		if lorawan := downlink.DownlinkOption.ProtocolConfig.GetLorawan(); lorawan != nil {
			downlinkConfig.Modulation = lorawan.Modulation.String()
			downlinkConfig.DataRate = lorawan.DataRate
			downlinkConfig.BitRate = uint(lorawan.BitRate)
			downlinkConfig.FCnt = uint(lorawan.FCnt)
		}
	}
	if gateway := downlink.DownlinkOption.GatewayConfig; gateway != nil {
		downlinkConfig.Frequency = uint(downlink.DownlinkOption.GatewayConfig.Frequency)
		downlinkConfig.Power = int(downlink.DownlinkOption.GatewayConfig.Power)
	}

	h.qEvent <- &types.DeviceEvent{
		AppID: appDownlink.AppID,
		DevID: appDownlink.DevID,
		Event: types.DownlinkSentEvent,
		Data: types.DownlinkEventData{
			Payload:   downlink.Payload,
			Message:   appDownlink,
			GatewayID: downlink.DownlinkOption.GatewayId,
			Config:    downlinkConfig,
		},
	}
	return nil
}
<|endoftext|>


<commit_before>// HAR 1.2 implementation
// See [Spec](http://www.softwareishard.com/blog/har-12-spec/)
package har

import (
	"encoding/json"
	"io"
	"net/http"
	"os"
	"strings"
)

type Creator struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Comment string `json:"commment,omitempty"`
}

type Browser struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Comment string `json:"comment,omitempty"`
}

type PageTimings struct {
	OnContentLoad float64 `json:"onContentLoad,omitempty"`
	OnLoad        float64 `json:"onLoad,omitempty"`
	Comment       string  `json:"comment,omitempty"`
}

type Page struct {
	StartedDateTime string      `json:"startedDateTime"`
	ID              string      `json:"id"`
	Title           string      `json:"title"`
	PageTimings     PageTimings `json:"pageTimings"`
	Comment         string      `json:"comment,omitempty"`
}

type Cookie struct {
	Name     string `json:"name"`
	Value    string `json:"value"`
	Path     string `json:"path,omitempty"`
	Domain   string `json:"domain,omitempty"`
	Expires  string `json:"expires,omitempty"`
	HTTPOnly bool   `json:"httpOnly,omitempty"`
	Secure   bool   `json:"secure,omitempty"`
	Comment  string `json:"comment,omitempty"`
}

type Header struct {
	Name    string `json:"name"`
	Value   string `json:"value"`
	Comment string `json:"comment,omitempty"`
}

type QueryString struct {
	Name    string `json:"name"`
	Value   string `json:"value"`
	Comment string `json:"comment,omitempty"`
}

type Param struct {
	Name        string `json:"name"`
	Value       string `json:"value,omitempty"`
	FileName    string `json:"fileName,omitempty"`
	ContentType string `json:"contentType,omitempty"`
	Comment     string `json:"comment,omitempty"`
}

type PostData struct {
	MIMEType string  `json:"mimeType"`
	Params   []Param `json:"params"`
	Text     string  `json:"text"`
	Comment  string  `json:"comment,omitempty"`
}

type Request struct {
	Method      string        `json:"method"`
	URL         string        `json:"url"`
	httpVersion string        `json:"httpVersion"`
	Cookies     []Cookie      `json:"cookies"`
	Headers     []Header      `json:"headers"`
	QueryString []QueryString `json:"queryString"`
	PostData    PostData      `json:"postData,omitempty"`
	HeadersSize int           `json:"headersSize"`
	BodySize    int           `json:"bodySize"`
	Comment     string        `json:"comment,omitempty"`
}

// Convert a HAR Request struct to an net/http.Request struct
func (r *Request) Request() (httpreq *http.Request, err error) {

	if len(r.PostData.Text) > 0 {
		var body *strings.Reader
		body = strings.NewReader(r.PostData.Text)
		httpreq, err = http.NewRequest(r.Method, r.URL, body)
	} else {
		httpreq, err = http.NewRequest(r.Method, r.URL, nil)
	}

	if err != nil {
		return nil, err
	}

	for i := 0; i < len(r.Headers); i++ {
		httpreq.Header.Add(r.Headers[i].Name, r.Headers[i].Value)
	}

	return httpreq, nil
}

type Content struct {
	Size        int    `json:"size"`
	Compression int    `json:"compression,omitempty"`
	MIMEType    string `json:"mimeType"`
	Text        string `json:"text,omitempty"`
	Encoding    string `json:"encoding,omitempty"`
	Comment     string `json:"comment,omitempty"`
}

type Response struct {
	Status      int      `json:"status"`
	StatusText  string   `json:"statusText"`
	HTTPVersion string   `json:"httpVersion"`
	Cookies     []Cookie `json:"cookies"`
	Headers     []Header `json:"headers"`
	Content     Content  `json:"content"`
	RedirectURL string   `json:"redirectURL"`
	HeadersSize int      `json:"headersSize"`
	BodySize    int      `json:"bodySize"`
	Comment     string   `json:"comment,omitempty"`
}

type CacheRequest struct {
	Expires    string `json:"expires,omitempty"`
	LastAccess string `json:"lastAccess"`
	ETag       string `json:"eTag"`
	HitCount   int    `json:"hitCount"`
	Comment    string `json:"comment,omitempty"`
}

type Cache struct {
	BeforeRequest *CacheRequest `json:"beforeRequest,omitempty"`
	AfterRequest  *CacheRequest `json:"afterRequest,omitempty"`
	Comment       string        `json:"comment,omitempty"`
}

type Timings struct {
	Blocked float64 `json:"blocked,omitempty"`
	DNS     float64 `json:"dns,omitempty"`
	Connect float64 `json:"connect,omitempty"`
	Send    float64 `json:"send"`
	Wait    float64 `json:"wait"`
	Receive float64 `json:"receive"`
	SSL     float64 `json:"ssl,omitempty"`
	Comment string  `json:"comment,omitempty"`
}

type Entry struct {
	Pageref         string   `json:"pageref,omitempty"`
	StartedDateTime string   `json:"startedDateTime"`
	Time            float64  `json:"time"`
	Request         Request  `json:"request"`
	Response        Response `json:"response"`
	Cache           Cache    `json:"cache"`
	Timings         Timings  `json:"timings"`
	ServerIPAddress string   `json:"serverIPAddress,omitempty"`
	Connection      string   `json:"connection,omitempty"`
	Comment         string   `json:"comment,omitempty"`
}

type Log struct {
	Version string  `json:"version"`
	Creator Creator `json:"creator"`
	Browser Browser `json:"browser,omitempty"`
	Pages   []Page  `json:"pages,omitempty"`
	Entries []Entry `json:"entries"`
	Comment string  `json:"comment,omitempty"`
}

type HAR struct {
	Log Log `json:"log"`
}

// Load a HAR from a reader
func Load(r io.Reader) (*HAR, error) {
	dec := json.NewDecoder(r)
	x := &HAR{}
	if err := dec.Decode(&x); err != nil {
		return nil, err
	}

	return x, nil
}

// Load a HAR from a file
func LoadFile(path string) (*HAR, error) {
	fh, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	har, err := Load(fh)
	fh.Close()
	return har, err
}
<commit_msg>Do not send non-headers<commit_after>// HAR 1.2 implementation
// See [Spec](http://www.softwareishard.com/blog/har-12-spec/)
package har

import (
	"encoding/json"
	"io"
	"net/http"
	"os"
	"strings"
)

type Creator struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Comment string `json:"commment,omitempty"`
}

type Browser struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Comment string `json:"comment,omitempty"`
}

type PageTimings struct {
	OnContentLoad float64 `json:"onContentLoad,omitempty"`
	OnLoad        float64 `json:"onLoad,omitempty"`
	Comment       string  `json:"comment,omitempty"`
}

type Page struct {
	StartedDateTime string      `json:"startedDateTime"`
	ID              string      `json:"id"`
	Title           string      `json:"title"`
	PageTimings     PageTimings `json:"pageTimings"`
	Comment         string      `json:"comment,omitempty"`
}

type Cookie struct {
	Name     string `json:"name"`
	Value    string `json:"value"`
	Path     string `json:"path,omitempty"`
	Domain   string `json:"domain,omitempty"`
	Expires  string `json:"expires,omitempty"`
	HTTPOnly bool   `json:"httpOnly,omitempty"`
	Secure   bool   `json:"secure,omitempty"`
	Comment  string `json:"comment,omitempty"`
}

type Header struct {
	Name    string `json:"name"`
	Value   string `json:"value"`
	Comment string `json:"comment,omitempty"`
}

type QueryString struct {
	Name    string `json:"name"`
	Value   string `json:"value"`
	Comment string `json:"comment,omitempty"`
}

type Param struct {
	Name        string `json:"name"`
	Value       string `json:"value,omitempty"`
	FileName    string `json:"fileName,omitempty"`
	ContentType string `json:"contentType,omitempty"`
	Comment     string `json:"comment,omitempty"`
}

type PostData struct {
	MIMEType string  `json:"mimeType"`
	Params   []Param `json:"params"`
	Text     string  `json:"text"`
	Comment  string  `json:"comment,omitempty"`
}

type Request struct {
	Method      string        `json:"method"`
	URL         string        `json:"url"`
	httpVersion string        `json:"httpVersion"`
	Cookies     []Cookie      `json:"cookies"`
	Headers     []Header      `json:"headers"`
	QueryString []QueryString `json:"queryString"`
	PostData    PostData      `json:"postData,omitempty"`
	HeadersSize int           `json:"headersSize"`
	BodySize    int           `json:"bodySize"`
	Comment     string        `json:"comment,omitempty"`
}

// Convert a HAR Request struct to an net/http.Request struct
func (r *Request) Request() (httpreq *http.Request, err error) {

	if len(r.PostData.Text) > 0 {
		var body *strings.Reader
		body = strings.NewReader(r.PostData.Text)
		httpreq, err = http.NewRequest(r.Method, r.URL, body)
	} else {
		httpreq, err = http.NewRequest(r.Method, r.URL, nil)
	}

	if err != nil {
		return nil, err
	}

	for i := 0; i < len(r.Headers); i++ {
		if !strings.Contains(r.Headers[i].Name, ":") {
			httpreq.Header.Add(r.Headers[i].Name, r.Headers[i].Value)
		}
	}

	return httpreq, nil
}

type Content struct {
	Size        int    `json:"size"`
	Compression int    `json:"compression,omitempty"`
	MIMEType    string `json:"mimeType"`
	Text        string `json:"text,omitempty"`
	Encoding    string `json:"encoding,omitempty"`
	Comment     string `json:"comment,omitempty"`
}

type Response struct {
	Status      int      `json:"status"`
	StatusText  string   `json:"statusText"`
	HTTPVersion string   `json:"httpVersion"`
	Cookies     []Cookie `json:"cookies"`
	Headers     []Header `json:"headers"`
	Content     Content  `json:"content"`
	RedirectURL string   `json:"redirectURL"`
	HeadersSize int      `json:"headersSize"`
	BodySize    int      `json:"bodySize"`
	Comment     string   `json:"comment,omitempty"`
}

type CacheRequest struct {
	Expires    string `json:"expires,omitempty"`
	LastAccess string `json:"lastAccess"`
	ETag       string `json:"eTag"`
	HitCount   int    `json:"hitCount"`
	Comment    string `json:"comment,omitempty"`
}

type Cache struct {
	BeforeRequest *CacheRequest `json:"beforeRequest,omitempty"`
	AfterRequest  *CacheRequest `json:"afterRequest,omitempty"`
	Comment       string        `json:"comment,omitempty"`
}

type Timings struct {
	Blocked float64 `json:"blocked,omitempty"`
	DNS     float64 `json:"dns,omitempty"`
	Connect float64 `json:"connect,omitempty"`
	Send    float64 `json:"send"`
	Wait    float64 `json:"wait"`
	Receive float64 `json:"receive"`
	SSL     float64 `json:"ssl,omitempty"`
	Comment string  `json:"comment,omitempty"`
}

type Entry struct {
	Pageref         string   `json:"pageref,omitempty"`
	StartedDateTime string   `json:"startedDateTime"`
	Time            float64  `json:"time"`
	Request         Request  `json:"request"`
	Response        Response `json:"response"`
	Cache           Cache    `json:"cache"`
	Timings         Timings  `json:"timings"`
	ServerIPAddress string   `json:"serverIPAddress,omitempty"`
	Connection      string   `json:"connection,omitempty"`
	Comment         string   `json:"comment,omitempty"`
}

type Log struct {
	Version string  `json:"version"`
	Creator Creator `json:"creator"`
	Browser Browser `json:"browser,omitempty"`
	Pages   []Page  `json:"pages,omitempty"`
	Entries []Entry `json:"entries"`
	Comment string  `json:"comment,omitempty"`
}

type HAR struct {
	Log Log `json:"log"`
}

// Load a HAR from a reader
func Load(r io.Reader) (*HAR, error) {
	dec := json.NewDecoder(r)
	x := &HAR{}
	if err := dec.Decode(&x); err != nil {
		return nil, err
	}

	return x, nil
}

// Load a HAR from a file
func LoadFile(path string) (*HAR, error) {
	fh, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	har, err := Load(fh)
	fh.Close()
	return har, err
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
)

var (
	blog_config BlogConfig
)

func init() {
	if config_file_exist() == false {
		create_config_file()
		fmt.Fprintln(os.Stderr, "設定ファイルが見つからなかったため,~/.hbpを生成しました.")
		os.Exit(2)
	}
	var err error
	blog_config, err = load_config()
	if err != nil {
		fmt.Fprintln(os.Stderr, "設定ファイルの読み込みに失敗しました.\nホームディレクトリ以下の.hbpファイルを確認してください.")
		os.Exit(1)
	}
}

func main() {
	var (
		fileSpecify     string
		publishSpecify  bool
		categorySpecify string
		versionSpecify  bool
	)
	flag.Usage = func() {
		fmt.Fprintf(
			os.Stderr,
			`%s
はてなブログ用コマンドラインクライアント.

標準入力をはてなブログへ下書き投稿します.
1行目をタイトル, 3行目以降を本文として扱います.

アカウントの設定は~/.hbpファイルにて行ってください.

[オプション]
`, os.Args[0])
		flag.PrintDefaults()
	}
	flag.StringVar(&fileSpecify,
		"f",
		"",
		"指定ファイルを入力として利用します.")
	flag.BoolVar(&publishSpecify,
		"p",
		false,
		"下書きではなく直ちに公開します.")
	flag.StringVar(&categorySpecify,
		"c",
		"",
		"投稿時に付加するカテゴリを指定します.")
	flag.BoolVar(&versionSpecify,
		"v",
		false,
		"バージョンを表示します.")
	flag.Parse()

	if versionSpecify == true {
		fmt.Println("hbp: version 1.0")
		os.Exit(0)
	}

	p := PostConfig{
		SourceFilePath: fileSpecify,
		Publish:        publishSpecify,
		Category:       categorySpecify,
		Author:         blog_config.Hatena_id,
	}

	status, err_msg := post(p)

	if status != 0 {
		fmt.Fprintln(os.Stderr, err_msg)
	}

	os.Exit(status)
}

func post(pcon PostConfig) (status int, err_msg string) {
	var fp *os.File
	fp = os.Stdin
	if pcon.SourceFilePath != "" {
		var err error
		fp, err = os.Open(pcon.SourceFilePath)
		if err != nil {
			return 1, "ファイルオープンエラー."
		}
		defer fp.Close()
	}

	post_xml, xml_create_err := create_post_xml(
		fp,
		blog_config.Hatena_id,
		pcon.Category,
		pcon.Publish)
	if xml_create_err != nil {
		return 1, "投稿内容に問題があります.タイトルの指定などを確認してください."
	}

	api_call_err := call_atom_api(post_xml, blog_config)
	if api_call_err != nil {
		return 1, "APIコールエラー.通信状況を確認してください.アカウント設定は正しいですか?~/.hbpを確認してください."
	}

	return 0, ""
}
<commit_msg>エラーメッセージを修正<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
)

var (
	blog_config BlogConfig
)

func init() {
	if config_file_exist() == false {
		create_config_file()
		fmt.Fprintln(os.Stderr, "設定ファイルが見つからなかったため,~/.hbpを生成しました.")
		os.Exit(2)
	}
	var err error
	blog_config, err = load_config()
	if err != nil {
		fmt.Fprintln(os.Stderr, "設定ファイルの読み込みに失敗しました.\nホームディレクトリ以下の.hbpファイルを確認してください.")
		os.Exit(1)
	}
}

func main() {
	var (
		fileSpecify     string
		publishSpecify  bool
		categorySpecify string
		versionSpecify  bool
	)
	flag.Usage = func() {
		fmt.Fprintf(
			os.Stderr,
			`%s
はてなブログ用コマンドラインクライアント.

標準入力をはてなブログへ下書き投稿します.
1行目をタイトル, 3行目以降を本文として扱います.

アカウントの設定は~/.hbpファイルにて行ってください.

[オプション]
`, os.Args[0])
		flag.PrintDefaults()
	}
	flag.StringVar(&fileSpecify,
		"f",
		"",
		"指定ファイルを入力として利用します.")
	flag.BoolVar(&publishSpecify,
		"p",
		false,
		"下書きではなく直ちに公開します.")
	flag.StringVar(&categorySpecify,
		"c",
		"",
		"投稿時に付加するカテゴリを指定します.")
	flag.BoolVar(&versionSpecify,
		"v",
		false,
		"バージョンを表示します.")
	flag.Parse()

	if versionSpecify == true {
		fmt.Println("hbp: version 1.0")
		os.Exit(0)
	}

	p := PostConfig{
		SourceFilePath: fileSpecify,
		Publish:        publishSpecify,
		Category:       categorySpecify,
		Author:         blog_config.Hatena_id,
	}

	status, err_msg := post(p)

	if status != 0 {
		fmt.Fprintln(os.Stderr, err_msg)
	}

	os.Exit(status)
}

func post(pcon PostConfig) (status int, err_msg string) {
	var fp *os.File
	fp = os.Stdin
	if pcon.SourceFilePath != "" {
		var err error
		fp, err = os.Open(pcon.SourceFilePath)
		if err != nil {
			return 1, "ファイルオープンエラー."
		}
		defer fp.Close()
	}

	post_xml, xml_create_err := create_post_xml(
		fp,
		blog_config.Hatena_id,
		pcon.Category,
		pcon.Publish)
	if xml_create_err != nil {
		return 1, "投稿内容に問題があります.タイトルの指定などを確認してください."
	}

	api_call_err := call_atom_api(post_xml, blog_config)
	if api_call_err != nil {
		return 1, "APIコールエラー.\n1. 通信状況を確認してください\n2. アカウント設定は正しいですか?  ~/.hbpを確認してください."
	}

	return 0, ""
}
<|endoftext|>


<commit_before>package shell

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/buildkite/agent/env"
	"github.com/buildkite/agent/process"
	"github.com/nightlyone/lockfile"
	"github.com/pkg/errors"
)

var (
	lockRetryDuration = time.Second
)

// Shell represents a virtual shell, handles logging, executing commands and
// provides hooks for capturing output and exit conditions.
//
// Provides a lowest-common denominator abstraction over macOS, Linux and Windows
type Shell struct {
	Logger

	// The running environment for the shell
	Env *env.Environment

	// Whether the shell is a PTY
	PTY bool

	// Where stdout is written, defaults to os.Stdout
	Writer io.Writer

	// Whether to run the shell in debug mode
	Debug bool

	// Current working directory that shell commands get executed in
	wd string

	// The context for the shell
	ctx context.Context
}

// New returns a new Shell
func New() (*Shell, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to find current working directory")
	}

	return &Shell{
		Logger: StderrLogger,
		Env:    env.FromSlice(os.Environ()),
		Writer: os.Stdout,
		wd:     wd,
		ctx:    context.Background(),
	}, nil
}

// New returns a new Shell with provided context.Context
func NewWithContext(ctx context.Context) (*Shell, error) {
	sh, err := New()
	if err != nil {
		return nil, err
	}

	sh.ctx = ctx
	return sh, nil
}

// Getwd returns the current working directory of the shell
func (s *Shell) Getwd() string {
	return s.wd
}

// Chdir changes the working directory of the shell
func (s *Shell) Chdir(path string) error {
	// If the path isn't absolute, prefix it with the current working directory.
	if !filepath.IsAbs(path) {
		path = filepath.Join(s.wd, path)
	}

	s.Commentf("Changing working directory to \"%s\"", path)

	if _, err := os.Stat(path); err != nil {
		return fmt.Errorf("Failed to change working: directory does not exist")
	}

	s.wd = path
	return nil
}

// AbsolutePath returns the absolute path to an executable based on the PATH and
// PATHEXT of the Shell
func (s *Shell) AbsolutePath(executable string) (string, error) {
	// Is the path already absolute?
	if path.IsAbs(executable) {
		return executable, nil
	}

	envPath, _ := s.Env.Get("PATH")
	fileExtensions, _ := s.Env.Get("PATHEXT") // For searching .exe, .bat, etc on Windows

	// Use our custom lookPath that takes a specific path
	absolutePath, err := lookPath(executable, envPath, fileExtensions)
	if err != nil {
		return "", err
	}

	// Since the path returned by LookPath is relative to the current working
	// directory, we need to get the absolute version of that.
	return filepath.Abs(absolutePath)
}

// LockFile is a pid-based lock for cross-process locking
type LockFile interface {
	Unlock() error
}

// Create a cross-process file-based lock based on pid files
func (s *Shell) LockFile(path string, timeout time.Duration) (LockFile, error) {
	absolutePathToLock, err := filepath.Abs(path)
	if err != nil {
		return nil, fmt.Errorf("Failed to find absolute path to lock \"%s\" (%v)", path, err)
	}

	lock, err := lockfile.New(absolutePathToLock)
	if err != nil {
		return nil, fmt.Errorf("Failed to create lock \"%s\" (%s)", absolutePathToLock, err)
	}

	ctx, cancel := context.WithTimeout(s.ctx, timeout)
	defer cancel()

	for {
		// Keep trying the lock until we get it
		if err := lock.TryLock(); err != nil {
			s.Commentf("Could not acquire lock on \"%s\" (%s)", absolutePathToLock, err)
			s.Commentf("Trying again in %s...", lockRetryDuration)
			time.Sleep(lockRetryDuration)
		} else {
			break
		}

		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
			// No value ready, moving on
		}
	}

	return &lock, err
}

// Run runs a command, write stdout and stderr to the logger and return an error if it fails
func (s *Shell) Run(command string, arg ...string) error {
	s.Promptf("%s", process.FormatCommand(command, arg))

	cmd, err := s.buildCommand(command, arg...)
	if err != nil {
		s.Errorf("Error building command: %v", err)
		return err
	}

	return s.executeCommand(cmd, s.Writer, executeFlags{
		Stdout: true,
		Stderr: true,
		PTY:    s.PTY,
	})
}

// RunAndCapture runs a command and captures the output for processing. Stdout is captured, but
// stderr isn't. If the shell is in debug mode then the command will be eched and both stderr
// and stdout will be written to the logger. A PTY is never used for RunAndCapture.
func (s *Shell) RunAndCapture(command string, arg ...string) (string, error) {
	if s.Debug {
		s.Promptf("%s", process.FormatCommand(command, arg))
	}

	cmd, err := s.buildCommand(command, arg...)
	if err != nil {
		return "", err
	}

	var b bytes.Buffer

	err = s.executeCommand(cmd, &b, executeFlags{
		Stdout: true,
		Stderr: false,
		PTY:    false,
	})
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(b.String()), nil
}

// RunScript is like Run, but the target is an interpreted script which has
// some extra checks to ensure it gets to the correct interpreter. It also supports
// passing in extra environment just for that script
func (s *Shell) RunScript(path string, extra *env.Environment) error {
	// If you run a script on Linux that doesn't have the
	// #!/bin/bash shebang at the top, it will fail to run with a
	// "exec format error" error.

	// You can solve it by adding the
	// #!/bin/bash line to the top of the file, but that's
	// annoying, and people generally forget it, so we'll make it
	// easy on them and add it for them here.

	// We also need to make sure the script we pass has quotes
	// around it, otherwise `/bin/bash -c run script with space.sh` fails.

	var command string
	var args []string

	if runtime.GOOS == "windows" {
		command = path
		args = []string{}
	} else {
		command = "/bin/bash"
		args = []string{"-c", path}
	}

	s.Promptf("%s", process.FormatCommand(command, args))

	cmd, err := s.buildCommand(command, args...)
	if err != nil {
		s.Errorf("Error building command: %v", err)
		return err
	}

	// Combine the two slices of env, let the latter overwrite the former
	currentEnv := env.FromSlice(cmd.Env)
	customEnv := currentEnv.Merge(extra)
	cmd.Env = customEnv.ToSlice()

	return s.executeCommand(cmd, s.Writer, executeFlags{
		Stdout: true,
		Stderr: true,
		PTY:    s.PTY,
	})
}

// buildCommand returns an exec.Cmd that runs in the context of the shell
func (s *Shell) buildCommand(name string, arg ...string) (*exec.Cmd, error) {
	// Always use absolute path as Windows has a hard time finding executables in it's path
	absPath, err := s.AbsolutePath(name)
	if err != nil {
		return nil, err
	}

	cmd := exec.Command(absPath, arg...)
	cmd.Env = s.Env.ToSlice()
	cmd.Dir = s.wd

	return cmd, nil
}

type executeFlags struct {
	// Whether to capture stdout
	Stdout bool

	// Whether to capture stderr
	Stderr bool

	// Run the command in a PTY
	PTY bool
}

func (s *Shell) executeCommand(cmd *exec.Cmd, w io.Writer, flags executeFlags) error {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt,
		syscall.SIGHUP,
		syscall.SIGTERM,
		syscall.SIGINT,
		syscall.SIGQUIT)
	defer signal.Stop(signals)

	go func() {
		// forward signals to the process
		for sig := range signals {
			if err := signalProcess(cmd, sig); err != nil {
				s.Errorf("Error passing signal to child process: %v", err)
			}
		}
	}()

	cmdStr := process.FormatCommand(cmd.Path, cmd.Args[1:])

	if flags.PTY {
		pty, err := process.StartPTY(cmd)
		if err != nil {
			return fmt.Errorf("Error starting PTY: %v", err)
		}

		// Copy the pty to our buffer. This will block until it EOF's
		// or something breaks.
		_, err = io.Copy(w, pty)
		if e, ok := err.(*os.PathError); ok && e.Err == syscall.EIO {
			// We can safely ignore this error, because it's just the PTY telling us
			// that it closed successfully.
			// See https://github.com/buildkite/agent/pull/34#issuecomment-46080419
		}
	} else {
		cmd.Stdout = w
		cmd.Stderr = nil
		cmd.Stdin = nil

		if s.Debug {
			stdOutStreamer := NewLoggerStreamer(s.Logger)
			defer stdOutStreamer.Close()

			stdErrStreamer := NewLoggerStreamer(s.Logger)
			defer stdErrStreamer.Close()

			// write the stdout to the writer and stream both stdout and stderr to the logger
			cmd.Stdout = io.MultiWriter(stdOutStreamer, w)
			cmd.Stderr = stdErrStreamer
		}

		if err := cmd.Start(); err != nil {
			return errors.Wrapf(err, "Error starting `%s`", cmdStr)
		}
	}

	if err := cmd.Wait(); err != nil {
		if s.Debug {
			s.Printf("Exited with error: %v", err)
		}

		return errors.Wrapf(err, "Error running `%s`", cmdStr)
	}

	return nil
}

// GetExitCode extracts an exit code from an error where the platform supports it,
// otherwise returns 0 for no error and 1 for an error
func GetExitCode(err error) int {
	if err == nil {
		return 0
	}
	switch cause := errors.Cause(err).(type) {
	case *exec.ExitError:
		// The program has exited with an exit code != 0
		// There is no platform independent way to retrieve
		// the exit code, but the following will work on Unix/macOS
		if status, ok := cause.Sys().(syscall.WaitStatus); ok {
			return status.ExitStatus()
		}
	}
	return 1
}
<commit_msg>Actually apply the Stdout/Stderr flags<commit_after>package shell

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/buildkite/agent/env"
	"github.com/buildkite/agent/process"
	"github.com/nightlyone/lockfile"
	"github.com/pkg/errors"
)

var (
	lockRetryDuration = time.Second
)

// Shell represents a virtual shell, handles logging, executing commands and
// provides hooks for capturing output and exit conditions.
//
// Provides a lowest-common denominator abstraction over macOS, Linux and Windows
type Shell struct {
	Logger

	// The running environment for the shell
	Env *env.Environment

	// Whether the shell is a PTY
	PTY bool

	// Where stdout is written, defaults to os.Stdout
	Writer io.Writer

	// Whether to run the shell in debug mode
	Debug bool

	// Current working directory that shell commands get executed in
	wd string

	// The context for the shell
	ctx context.Context
}

// New returns a new Shell
func New() (*Shell, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to find current working directory")
	}

	return &Shell{
		Logger: StderrLogger,
		Env:    env.FromSlice(os.Environ()),
		Writer: os.Stdout,
		wd:     wd,
		ctx:    context.Background(),
	}, nil
}

// New returns a new Shell with provided context.Context
func NewWithContext(ctx context.Context) (*Shell, error) {
	sh, err := New()
	if err != nil {
		return nil, err
	}

	sh.ctx = ctx
	return sh, nil
}

// Getwd returns the current working directory of the shell
func (s *Shell) Getwd() string {
	return s.wd
}

// Chdir changes the working directory of the shell
func (s *Shell) Chdir(path string) error {
	// If the path isn't absolute, prefix it with the current working directory.
	if !filepath.IsAbs(path) {
		path = filepath.Join(s.wd, path)
	}

	s.Commentf("Changing working directory to \"%s\"", path)

	if _, err := os.Stat(path); err != nil {
		return fmt.Errorf("Failed to change working: directory does not exist")
	}

	s.wd = path
	return nil
}

// AbsolutePath returns the absolute path to an executable based on the PATH and
// PATHEXT of the Shell
func (s *Shell) AbsolutePath(executable string) (string, error) {
	// Is the path already absolute?
	if path.IsAbs(executable) {
		return executable, nil
	}

	envPath, _ := s.Env.Get("PATH")
	fileExtensions, _ := s.Env.Get("PATHEXT") // For searching .exe, .bat, etc on Windows

	// Use our custom lookPath that takes a specific path
	absolutePath, err := lookPath(executable, envPath, fileExtensions)
	if err != nil {
		return "", err
	}

	// Since the path returned by LookPath is relative to the current working
	// directory, we need to get the absolute version of that.
	return filepath.Abs(absolutePath)
}

// LockFile is a pid-based lock for cross-process locking
type LockFile interface {
	Unlock() error
}

// Create a cross-process file-based lock based on pid files
func (s *Shell) LockFile(path string, timeout time.Duration) (LockFile, error) {
	absolutePathToLock, err := filepath.Abs(path)
	if err != nil {
		return nil, fmt.Errorf("Failed to find absolute path to lock \"%s\" (%v)", path, err)
	}

	lock, err := lockfile.New(absolutePathToLock)
	if err != nil {
		return nil, fmt.Errorf("Failed to create lock \"%s\" (%s)", absolutePathToLock, err)
	}

	ctx, cancel := context.WithTimeout(s.ctx, timeout)
	defer cancel()

	for {
		// Keep trying the lock until we get it
		if err := lock.TryLock(); err != nil {
			s.Commentf("Could not acquire lock on \"%s\" (%s)", absolutePathToLock, err)
			s.Commentf("Trying again in %s...", lockRetryDuration)
			time.Sleep(lockRetryDuration)
		} else {
			break
		}

		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
			// No value ready, moving on
		}
	}

	return &lock, err
}

// Run runs a command, write stdout and stderr to the logger and return an error if it fails
func (s *Shell) Run(command string, arg ...string) error {
	s.Promptf("%s", process.FormatCommand(command, arg))

	cmd, err := s.buildCommand(command, arg...)
	if err != nil {
		s.Errorf("Error building command: %v", err)
		return err
	}

	return s.executeCommand(cmd, s.Writer, executeFlags{
		Stdout: true,
		Stderr: true,
		PTY:    s.PTY,
	})
}

// RunAndCapture runs a command and captures the output for processing. Stdout is captured, but
// stderr isn't. If the shell is in debug mode then the command will be eched and both stderr
// and stdout will be written to the logger. A PTY is never used for RunAndCapture.
func (s *Shell) RunAndCapture(command string, arg ...string) (string, error) {
	if s.Debug {
		s.Promptf("%s", process.FormatCommand(command, arg))
	}

	cmd, err := s.buildCommand(command, arg...)
	if err != nil {
		return "", err
	}

	var b bytes.Buffer

	err = s.executeCommand(cmd, &b, executeFlags{
		Stdout: true,
		Stderr: false,
		PTY:    false,
	})
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(b.String()), nil
}

// RunScript is like Run, but the target is an interpreted script which has
// some extra checks to ensure it gets to the correct interpreter. It also supports
// passing in extra environment just for that script
func (s *Shell) RunScript(path string, extra *env.Environment) error {
	// If you run a script on Linux that doesn't have the
	// #!/bin/bash shebang at the top, it will fail to run with a
	// "exec format error" error.

	// You can solve it by adding the
	// #!/bin/bash line to the top of the file, but that's
	// annoying, and people generally forget it, so we'll make it
	// easy on them and add it for them here.

	// We also need to make sure the script we pass has quotes
	// around it, otherwise `/bin/bash -c run script with space.sh` fails.

	var command string
	var args []string

	if runtime.GOOS == "windows" {
		command = path
		args = []string{}
	} else {
		command = "/bin/bash"
		args = []string{"-c", path}
	}

	s.Promptf("%s", process.FormatCommand(command, args))

	cmd, err := s.buildCommand(command, args...)
	if err != nil {
		s.Errorf("Error building command: %v", err)
		return err
	}

	// Combine the two slices of env, let the latter overwrite the former
	currentEnv := env.FromSlice(cmd.Env)
	customEnv := currentEnv.Merge(extra)
	cmd.Env = customEnv.ToSlice()

	return s.executeCommand(cmd, s.Writer, executeFlags{
		Stdout: true,
		Stderr: true,
		PTY:    s.PTY,
	})
}

// buildCommand returns an exec.Cmd that runs in the context of the shell
func (s *Shell) buildCommand(name string, arg ...string) (*exec.Cmd, error) {
	// Always use absolute path as Windows has a hard time finding executables in it's path
	absPath, err := s.AbsolutePath(name)
	if err != nil {
		return nil, err
	}

	cmd := exec.Command(absPath, arg...)
	cmd.Env = s.Env.ToSlice()
	cmd.Dir = s.wd

	return cmd, nil
}

type executeFlags struct {
	// Whether to capture stdout
	Stdout bool

	// Whether to capture stderr
	Stderr bool

	// Run the command in a PTY
	PTY bool
}

func (s *Shell) executeCommand(cmd *exec.Cmd, w io.Writer, flags executeFlags) error {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt,
		syscall.SIGHUP,
		syscall.SIGTERM,
		syscall.SIGINT,
		syscall.SIGQUIT)
	defer signal.Stop(signals)

	go func() {
		// forward signals to the process
		for sig := range signals {
			if err := signalProcess(cmd, sig); err != nil {
				s.Errorf("Error passing signal to child process: %v", err)
			}
		}
	}()

	cmdStr := process.FormatCommand(cmd.Path, cmd.Args[1:])

	if flags.PTY {
		pty, err := process.StartPTY(cmd)
		if err != nil {
			return fmt.Errorf("Error starting PTY: %v", err)
		}

		// Copy the pty to our buffer. This will block until it EOF's
		// or something breaks.
		_, err = io.Copy(w, pty)
		if e, ok := err.(*os.PathError); ok && e.Err == syscall.EIO {
			// We can safely ignore this error, because it's just the PTY telling us
			// that it closed successfully.
			// See https://github.com/buildkite/agent/pull/34#issuecomment-46080419
		}
	} else {
		cmd.Stdout = nil
		cmd.Stderr = nil
		cmd.Stdin = nil

		if flags.Stdout {
			cmd.Stdout = w
		}
		if flags.Stderr {
			cmd.Stderr = w
		}

		if s.Debug {
			stdOutStreamer := NewLoggerStreamer(s.Logger)
			defer stdOutStreamer.Close()

			stdErrStreamer := NewLoggerStreamer(s.Logger)
			defer stdErrStreamer.Close()

			// write the stdout to the writer and stream both stdout and stderr to the logger
			cmd.Stdout = io.MultiWriter(stdOutStreamer, w)
			cmd.Stderr = stdErrStreamer
		}

		if err := cmd.Start(); err != nil {
			return errors.Wrapf(err, "Error starting `%s`", cmdStr)
		}
	}

	if err := cmd.Wait(); err != nil {
		if s.Debug {
			s.Printf("Exited with error: %v", err)
		}

		return errors.Wrapf(err, "Error running `%s`", cmdStr)
	}

	return nil
}

// GetExitCode extracts an exit code from an error where the platform supports it,
// otherwise returns 0 for no error and 1 for an error
func GetExitCode(err error) int {
	if err == nil {
		return 0
	}
	switch cause := errors.Cause(err).(type) {
	case *exec.ExitError:
		// The program has exited with an exit code != 0
		// There is no platform independent way to retrieve
		// the exit code, but the following will work on Unix/macOS
		if status, ok := cause.Sys().(syscall.WaitStatus); ok {
			return status.ExitStatus()
		}
	}
	return 1
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
)

const (
	userAgent   = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"
	timeLayout  = "02/01/2006"
	storagePath = ".local/share"
)

var (
	ErrDisabled = fmt.Errorf("disabled on weekends")
)

func main() {
	flag.Parse()

	funds, err := GetFunds(flag.Args()...)
	if err == ErrDisabled {
		fmt.Println(err)
		os.Exit(0)
	}

	fmt.Print(prettyPrint(funds...))
}

func GetFunds(codes ...string) ([]Fund, error) {
	const baseurl = "https://www.tefas.gov.tr/FonAnaliz.aspx?FonKod=%v"

	c := &http.Client{Timeout: time.Minute}

	today := time.Now()

	switch today.Weekday() {
	case 6, 0: // saturday and sunday
		return nil, ErrDisabled
	}

	var funds []Fund
	for _, code := range codes {
		code = strings.ToUpper(code)

		u := fmt.Sprintf(baseurl, code)

		req, _ := http.NewRequest("GET", u, nil)
		req.Header.Set("User-Agent", userAgent)

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

		if resp.StatusCode != 200 {
			log.Printf("unexpected status code: %v", resp.StatusCode)
			continue
		}

		doc, err := goquery.NewDocumentFromReader(resp.Body)
		if err != nil {
			return nil, err
		}

		fundName := strings.TrimSpace(doc.Find(".main-indicators h2").Text())
		fund := Fund{
			Code: code,
			Name: fundName,
		}

		fund.Price = atof(doc.Find(".top-list > li:nth-child(1) span").Text())
		fund.Daily = atof(doc.Find(".top-list > li:nth-child(2) span").Text())

		doc.Find(".price-indicators li span").Each(func(i int, sel *goquery.Selection) {
			changePercent := atof(sel.Text())

			switch i {
			case 0:
				fund.Monthly = changePercent
			case 1:
				fund.Quarterly = changePercent
			case 2:
				fund.Biannual = changePercent
			case 3:
				fund.Annual = changePercent
			}

		})
		funds = append(funds, fund)
	}

	return funds, nil
}

type Fund struct {
	Type      FundType
	Code      string
	Name      string
	Price     float64
	Daily     float64
	Monthly   float64
	Quarterly float64
	Biannual  float64
	Annual    float64
}

type FundType uint8

const (
	CommodityFunds     FundType = 3
	FixedIncomeFunds   FundType = 6
	ForeignEquityFunds FundType = 111
)

func prettyPrint(funds ...Fund) string {
	color := func(f float64) string {
		switch {
		case f == 0:
			return "grey"
		case f < 0:
			return "red"
		case f > 0:
			return "green"
		}
		return "black"
	}

	var buf bytes.Buffer

	header := "%v (%v) \x1b[31;1;8m%v\x1b[0m | size=13 href=https://www.tefas.gov.tr/FonAnaliz.aspx?FonKod=%v\n"

	fmt.Fprintln(&buf, "Refresh | refresh=true")
	fmt.Fprintf(&buf, "---\n")

	for _, f := range funds {
		sethop(f.Code, time.Now(), f.Daily)

		// calculate the fall
		hop := gethop(f.Code)
		var freefall string
		for _, h := range hop {
			if h.DailyChange < 0 {
				freefall += "◉ "
			} else {
				freefall = ""
			}
		}

		fmt.Fprintf(&buf, header, f.Code, f.Name, freefall, f.Code)
		fmt.Fprintf(&buf, "• Fiyat:  %v | size=11\n", f.Price)
		fmt.Fprintf(&buf, "• Günlük:  %v%% | color=%v size=11\n", f.Daily, color(f.Daily))
		fmt.Fprintf(&buf, "• Aylık: %v%% | color=%v size=11\n", f.Monthly, color(f.Monthly))
		fmt.Fprintf(&buf, "• 3 Aylık: %v%% | color=%v size=11\n", f.Quarterly, color(f.Quarterly))
		fmt.Fprintf(&buf, "• 6 Aylık: %v%% | color=%v size=11\n", f.Biannual, color(f.Biannual))
		fmt.Fprintf(&buf, "• Yıllık:  %v%% | color=%v size=11\n", f.Annual, color(f.Annual))
		fmt.Fprintf(&buf, "---\n")
	}
	return buf.String()
}

func sethop(code string, date time.Time, change float64) {
	// change is not reflected on the site yet, hence the zero value.
	if change == 0 {
		return
	}
	home := os.Getenv("HOME")
	path := filepath.Join(home, storagePath)
	os.MkdirAll(path, 0755)
	fpath := filepath.Join(path, "funds.json")

	_, err := os.Stat(fpath)
	if os.IsNotExist(err) {
		ioutil.WriteFile(fpath, []byte("{}"), 0644)
	}

	allhops := gethops()

	datestr := date.Format(timeLayout)
	hop := Hop{Date: datestr, DailyChange: change}

	hops, ok := allhops[code]
	if !ok {
		hops = []Hop{hop}
	} else {
		var found bool
		for _, h := range hops {
			if h.Date == datestr {
				found = true
				break
			}
		}
		if !found {
			hops = append(hops, hop)
		}
	}

	// limit the size of the slice to 3
	if len(hops) >= 3 {
		hops = hops[len(hops)-3:]
	}

	allhops[code] = hops

	b, _ := json.Marshal(allhops)
	_ = ioutil.WriteFile(fpath, b, 0644)
}

func gethops() map[string][]Hop {
	home := os.Getenv("HOME")
	path := filepath.Join(home, storagePath)
	os.MkdirAll(path, 0755)
	fpath := filepath.Join(path, "funds.json")

	m := make(map[string][]Hop)

	content, _ := ioutil.ReadFile(fpath)
	_ = json.Unmarshal(content, &m)

	return m
}

func gethop(code string) []Hop {
	return gethops()[code]
}

type Hop struct {
	Date        string
	DailyChange float64
}

func atof(s string) float64 {
	s = strings.TrimPrefix(s, "%")
	s = strings.ReplaceAll(s, ",", ".")
	f, _ := strconv.ParseFloat(s, 64)
	return f
}
<commit_msg>fund: dont reflect -100 value<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
)

const (
	userAgent   = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"
	timeLayout  = "02/01/2006"
	storagePath = ".local/share"
)

var (
	ErrDisabled = fmt.Errorf("disabled on weekends")
)

func main() {
	flag.Parse()

	funds, err := GetFunds(flag.Args()...)
	if err == ErrDisabled {
		fmt.Println(err)
		os.Exit(0)
	}

	fmt.Print(prettyPrint(funds...))
}

func GetFunds(codes ...string) ([]Fund, error) {
	const baseurl = "https://www.tefas.gov.tr/FonAnaliz.aspx?FonKod=%v"

	c := &http.Client{Timeout: time.Minute}

	today := time.Now()

	switch today.Weekday() {
	case 6, 0: // saturday and sunday
		return nil, ErrDisabled
	}

	var funds []Fund
	for _, code := range codes {
		code = strings.ToUpper(code)

		u := fmt.Sprintf(baseurl, code)

		req, _ := http.NewRequest("GET", u, nil)
		req.Header.Set("User-Agent", userAgent)

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

		if resp.StatusCode != 200 {
			log.Printf("unexpected status code: %v", resp.StatusCode)
			continue
		}

		doc, err := goquery.NewDocumentFromReader(resp.Body)
		if err != nil {
			return nil, err
		}

		fundName := strings.TrimSpace(doc.Find(".main-indicators h2").Text())
		fund := Fund{
			Code: code,
			Name: fundName,
		}

		fund.Price = atof(doc.Find(".top-list > li:nth-child(1) span").Text())
		fund.Daily = atof(doc.Find(".top-list > li:nth-child(2) span").Text())

		doc.Find(".price-indicators li span").Each(func(i int, sel *goquery.Selection) {
			changePercent := atof(sel.Text())

			switch i {
			case 0:
				fund.Monthly = changePercent
			case 1:
				fund.Quarterly = changePercent
			case 2:
				fund.Biannual = changePercent
			case 3:
				fund.Annual = changePercent
			}

		})
		funds = append(funds, fund)
	}

	return funds, nil
}

type Fund struct {
	Type      FundType
	Code      string
	Name      string
	Price     float64
	Daily     float64
	Monthly   float64
	Quarterly float64
	Biannual  float64
	Annual    float64
}

type FundType uint8

const (
	CommodityFunds     FundType = 3
	FixedIncomeFunds   FundType = 6
	ForeignEquityFunds FundType = 111
)

func prettyPrint(funds ...Fund) string {
	color := func(f float64) string {
		switch {
		case f == 0:
			return "grey"
		case f < 0:
			return "red"
		case f > 0:
			return "green"
		}
		return "black"
	}

	var buf bytes.Buffer

	header := "%v (%v) \x1b[31;1;8m%v\x1b[0m | size=13 href=https://www.tefas.gov.tr/FonAnaliz.aspx?FonKod=%v\n"

	fmt.Fprintln(&buf, "Refresh | refresh=true")
	fmt.Fprintf(&buf, "---\n")

	for _, f := range funds {
		sethop(f.Code, time.Now(), f.Daily)

		// calculate the fall
		hop := gethop(f.Code)
		var freefall string
		for _, h := range hop {
			if h.DailyChange < 0 {
				freefall += "◉ "
			} else {
				freefall = ""
			}
		}

		fmt.Fprintf(&buf, header, f.Code, f.Name, freefall, f.Code)
		fmt.Fprintf(&buf, "• Fiyat:  %v | size=11\n", f.Price)
		fmt.Fprintf(&buf, "• Günlük:  %v%% | color=%v size=11\n", f.Daily, color(f.Daily))
		fmt.Fprintf(&buf, "• Aylık: %v%% | color=%v size=11\n", f.Monthly, color(f.Monthly))
		fmt.Fprintf(&buf, "• 3 Aylık: %v%% | color=%v size=11\n", f.Quarterly, color(f.Quarterly))
		fmt.Fprintf(&buf, "• 6 Aylık: %v%% | color=%v size=11\n", f.Biannual, color(f.Biannual))
		fmt.Fprintf(&buf, "• Yıllık:  %v%% | color=%v size=11\n", f.Annual, color(f.Annual))
		fmt.Fprintf(&buf, "---\n")
	}
	return buf.String()
}

func sethop(code string, date time.Time, change float64) {
	// change is not reflected on the site yet, hence the zero value.
	if change == 0 || change == -100 {
		return
	}
	home := os.Getenv("HOME")
	path := filepath.Join(home, storagePath)
	os.MkdirAll(path, 0755)
	fpath := filepath.Join(path, "funds.json")

	_, err := os.Stat(fpath)
	if os.IsNotExist(err) {
		ioutil.WriteFile(fpath, []byte("{}"), 0644)
	}

	allhops := gethops()

	datestr := date.Format(timeLayout)
	hop := Hop{Date: datestr, DailyChange: change}

	hops, ok := allhops[code]
	if !ok {
		hops = []Hop{hop}
	} else {
		var found bool
		for _, h := range hops {
			if h.Date == datestr {
				found = true
				break
			}
		}
		if !found {
			hops = append(hops, hop)
		}
	}

	// limit the size of the slice to 3
	if len(hops) >= 3 {
		hops = hops[len(hops)-3:]
	}

	allhops[code] = hops

	b, _ := json.Marshal(allhops)
	_ = ioutil.WriteFile(fpath, b, 0644)
}

func gethops() map[string][]Hop {
	home := os.Getenv("HOME")
	path := filepath.Join(home, storagePath)
	os.MkdirAll(path, 0755)
	fpath := filepath.Join(path, "funds.json")

	m := make(map[string][]Hop)

	content, _ := ioutil.ReadFile(fpath)
	_ = json.Unmarshal(content, &m)

	return m
}

func gethop(code string) []Hop {
	return gethops()[code]
}

type Hop struct {
	Date        string
	DailyChange float64
}

func atof(s string) float64 {
	s = strings.TrimPrefix(s, "%")
	s = strings.ReplaceAll(s, ",", ".")
	f, _ := strconv.ParseFloat(s, 64)
	return f
}
<|endoftext|>


<commit_before>package shell

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/buildkite/agent/env"
	"github.com/buildkite/agent/process"
	"github.com/nightlyone/lockfile"
)

var DefaultOutput io.Writer = os.Stdout

// Shell represents a virtual shell, handles logging, executing commands and
// provides hooks for capturing output and exit conditions.
//
// Provides a lowest-common denominator abstraction over macOS, Linux and Windows
type Shell struct {
	// The running environment for the bootstrap file as each task runs
	Env *env.Environment

	// Whether the shell is a PTY
	PTY bool

	// The output of the shell
	output io.Writer

	// Current working directory that shell commands get executed in
	wd string

	// Funcs to call when the shell exits
	exitHandlers []func(err error)
}

// New returns a new Shell
func New() (*Shell, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Failed to find current working directory: %v", err)
	}

	return &Shell{
		Env:    env.FromSlice(os.Environ()),
		wd:     wd,
		output: DefaultOutput,
	}, nil
}

// Printf prints a line of output
func (s *Shell) Printf(format string, v ...interface{}) {
	fmt.Fprintf(s.output, "%s\n", fmt.Sprintf(format, v...))
}

// Headerf prints a Buildkite formatted header
func (s *Shell) Headerf(format string, v ...interface{}) {
	fmt.Fprintf(s.output, "~~~ %s\n", fmt.Sprintf(format, v...))
}

// Commentf prints a comment line, e.g `# my comment goes here`
func (s *Shell) Commentf(format string, v ...interface{}) {
	fmt.Fprintf(s.output, "\033[90m# %s\033[0m\n", fmt.Sprintf(format, v...))
}

// Errorf shows a Buildkite formatted error expands the previous group
func (s *Shell) Errorf(format string, v ...interface{}) {
	s.Printf("\033[31m🚨 Error: %s\033[0m", fmt.Sprintf(format, v...))
	s.Printf("^^^ +++")
}

// Warningf shows a buildkite boostrap warning
func (s *Shell) Warningf(format string, v ...interface{}) {
	s.Printf("\033[33m⚠ Warning: %s\033[0m", fmt.Sprintf(format, v...))
	s.Printf("^^^ +++")
}

// Promptf prints a shell prompt
func (s *Shell) Promptf(format string, v ...interface{}) {
	if runtime.GOOS == "windows" {
		fmt.Fprintf(s.output, "\033[90m>\033[0m %s\n", fmt.Sprintf(format, v...))
	} else {
		fmt.Fprintf(s.output, "\033[90m$\033[0m %s\n", fmt.Sprintf(format, v...))
	}
}

// Fatalf shows the error text and terminates the shell
func (s *Shell) Fatalf(format string, v ...interface{}) {
	s.Errorf(format, v...)
	s.Exit(nil)
}

func (s *Shell) AddExitHandler(f func(err error)) {
	s.exitHandlers = append(s.exitHandlers, f)
}

func (s *Shell) Exit(err error) {
	for _, f := range s.exitHandlers {
		f(err)
	}
}

// Getwd returns the current working directory of the shell
func (s *Shell) Getwd() string {
	return s.wd
}

// Chdir changes the working directory of the shell
func (s *Shell) Chdir(path string) error {
	// If the path isn't absolute, prefix it with the current working directory.
	if !filepath.IsAbs(path) {
		path = filepath.Join(s.wd, path)
	}

	s.Commentf("Changing working directory to \"%s\"", path)

	if _, err := os.Stat(path); err != nil {
		return fmt.Errorf("Failed to change working: directory does not exist")
	}

	s.wd = path
	return nil
}

// AbsolutePath returns the absolute path to an executable based on the PATH and
// PATHEXT of the Shell
func (s *Shell) AbsolutePath(executable string) (string, error) {
	// Is the path already absolute?
	if path.IsAbs(executable) {
		return executable, nil
	}

	var envPath = s.Env.Get("PATH")
	var fileExtensions = s.Env.Get("PATHEXT") // For searching .exe, .bat, etc on Windows

	// Use our custom lookPath that takes a specific path
	absolutePath, err := lookPath(executable, envPath, fileExtensions)
	if err != nil {
		return "", err
	}

	// Since the path returned by LookPath is relative to the current working
	// directory, we need to get the absolute version of that.
	return filepath.Abs(absolutePath)
}

// RunCommandSilentlyAndCaptureOutput runs a command without showing a prompt or the output to the user
// and returns the output as a string
func (s *Shell) RunCommandSilentlyAndCaptureOutput(command string, args ...string) (string, error) {
	var b bytes.Buffer

	cmd, err := s.buildCommand(command, args...)
	if err != nil {
		return "", err
	}

	execErr := s.executeCommand(cmd, &b, s.PTY, true)
	output := strings.TrimSpace(b.String())

	return output, execErr
}

// RunCommand runs a command, write to the logger and return an error if it fails
func (s *Shell) RunCommand(command string, args ...string) error {
	cmd, err := s.buildCommand(command, args...)
	if err != nil {
		return err
	}

	cmdStr := process.FormatCommand(command, args)
	s.Promptf("%s", cmdStr)

	return s.executeCommand(cmd, s.output, s.PTY, false)
}

// RunScript executes a script, like RunCommand, but the target is an interpreted script
// so it has extra checks applied to make sure it's executable. It also doesn't take arguments
func (s *Shell) RunScript(path string) error {
	var cmd *exec.Cmd
	var err error

	if runtime.GOOS == "windows" {
		cmd, err = s.buildCommand(path)
	} else {
		// If you run a script on Linux that doesn't have the
		// #!/bin/bash thingy at the top, it will fail to run with a
		// "exec format error" error. You can solve it by adding the
		// #!/bin/bash line to the top of the file, but that's
		// annoying, and people generally forget it, so we'll make it
		// easy on them and add it for them here.
		//
		// We also need to make sure the script we pass has quotes
		// around it, otherwise `/bin/bash -c run script with space.sh`
		// fails.
		cmd, err = s.buildCommand("/bin/bash", "-c", `"`+strings.Replace(path, `"`, `\"`, -1)+`"`)
	}
	if err != nil {
		s.Errorf("Error building command: %v", err)
		return err
	}

	return s.executeCommand(cmd, s.output, s.PTY, false)
}

// buildCommand returns an exec.Cmd that runs in the context of the shell
func (s *Shell) buildCommand(command string, args ...string) (*exec.Cmd, error) {
	// Always use absolute path as Windows has a hard time finding executables in it's path
	absPath, err := s.AbsolutePath(command)
	if err != nil {
		return nil, err
	}

	cmd := exec.Command(absPath, args...)
	cmd.Env = s.Env.ToSlice()
	cmd.Dir = s.wd

	return cmd, nil
}

func (s *Shell) executeCommand(cmd *exec.Cmd, w io.Writer, pty bool, silent bool) error {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt,
		syscall.SIGHUP,
		syscall.SIGTERM,
		syscall.SIGINT,
		syscall.SIGQUIT)
	defer signal.Stop(signals)

	var handleError = func(prefix string, err error) error {
		if !silent {
			s.Errorf("%s%v", prefix, err)
		}
		return err
	}

	go func() {
		// forward signals to the process
		for sig := range signals {
			if err := signalProcess(cmd, sig); err != nil {
				_ = handleError("Error passing signal to child process: ", err)
			}
		}
	}()

	cmdStr := process.FormatCommand(cmd.Path, cmd.Args)
	if !silent {
		s.Promptf("%s", cmdStr)
	}

	if pty {
		pty, err := process.StartPTY(cmd)
		if err != nil {
			return handleError("Error starting PTY: ", err)
		}

		// Copy the pty to our buffer. This will block until it EOF's
		// or something breaks.
		_, err = io.Copy(w, pty)
		if e, ok := err.(*os.PathError); ok && e.Err == syscall.EIO {
			// We can safely ignore this error, because it's just the PTY telling us
			// that it closed successfully.
			// See https://github.com/buildkite/agent/pull/34#issuecomment-46080419
		}
	} else {
		cmd.Stdout = w
		cmd.Stderr = w
		cmd.Stdin = nil

		if err := cmd.Start(); err != nil {
			return handleError(fmt.Sprintf("Error starting `%s`: ", cmdStr), err)
		}
	}

	if err := cmd.Wait(); err != nil {
		return handleError(fmt.Sprintf("Error running `%s`: ", cmdStr), err)
	}

	return nil
}

// GetExitCode extracts an exit code from an error where the platform supports it,
// otherwise returns 0 for no error and 1 for an error
func GetExitCode(err error) int {
	if err == nil {
		return 0
	}
	if exiterr, ok := err.(*exec.ExitError); ok {
		// The program has exited with an exit code != 0
		// There is no platform independent way to retrieve
		// the exit code, but the following will work on Unix/macOS
		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
			return status.ExitStatus()
		}
	}
	return 1
}

var (
	lockRetryDuration = time.Second
)

func (s *Shell) LockFile(ctx context.Context, path string) (*lockfile.Lockfile, error) {
	absolutePathToLock, err := filepath.Abs(path)
	if err != nil {
		return nil, fmt.Errorf("Failed to find absolute path to lock \"%s\" (%v)", path, err)
	}

	lock, err := lockfile.New(absolutePathToLock)
	if err != nil {
		return nil, fmt.Errorf("Failed to create lock \"%s\" (%s)", absolutePathToLock, err)
	}

	for {
		// Keep trying the lock until we get it
		if err := lock.TryLock(); err != nil {
			if te, ok := err.(interface {
				Temporary() bool
			}); ok && te.Temporary() {
				s.Commentf("Could not aquire lock on \"%s\" (%s)", absolutePathToLock, err)
				s.Commentf("Trying again in %s...", lockRetryDuration)
				time.Sleep(lockRetryDuration)
			} else {
				return nil, err
			}
		} else {
			break
		}

		// Check if we've timed out
		for {
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			default:
			}
		}
	}

	return &lock, err
}

func (s *Shell) LockFileWithTimeout(path string, timeout time.Duration) (*lockfile.Lockfile, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	return s.LockFile(ctx, path)
}
<commit_msg>Cleanup RunScript<commit_after>package shell

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/buildkite/agent/env"
	"github.com/buildkite/agent/process"
	"github.com/nightlyone/lockfile"
)

var DefaultOutput io.Writer = os.Stdout

// Shell represents a virtual shell, handles logging, executing commands and
// provides hooks for capturing output and exit conditions.
//
// Provides a lowest-common denominator abstraction over macOS, Linux and Windows
type Shell struct {
	// The running environment for the bootstrap file as each task runs
	Env *env.Environment

	// Whether the shell is a PTY
	PTY bool

	// The output of the shell
	output io.Writer

	// Current working directory that shell commands get executed in
	wd string

	// Funcs to call when the shell exits
	exitHandlers []func(err error)
}

// New returns a new Shell
func New() (*Shell, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Failed to find current working directory: %v", err)
	}

	return &Shell{
		Env:    env.FromSlice(os.Environ()),
		wd:     wd,
		output: DefaultOutput,
	}, nil
}

// Printf prints a line of output
func (s *Shell) Printf(format string, v ...interface{}) {
	fmt.Fprintf(s.output, "%s\n", fmt.Sprintf(format, v...))
}

// Headerf prints a Buildkite formatted header
func (s *Shell) Headerf(format string, v ...interface{}) {
	fmt.Fprintf(s.output, "~~~ %s\n", fmt.Sprintf(format, v...))
}

// Commentf prints a comment line, e.g `# my comment goes here`
func (s *Shell) Commentf(format string, v ...interface{}) {
	fmt.Fprintf(s.output, "\033[90m# %s\033[0m\n", fmt.Sprintf(format, v...))
}

// Errorf shows a Buildkite formatted error expands the previous group
func (s *Shell) Errorf(format string, v ...interface{}) {
	s.Printf("\033[31m🚨 Error: %s\033[0m", fmt.Sprintf(format, v...))
	s.Printf("^^^ +++")
}

// Warningf shows a buildkite boostrap warning
func (s *Shell) Warningf(format string, v ...interface{}) {
	s.Printf("\033[33m⚠ Warning: %s\033[0m", fmt.Sprintf(format, v...))
	s.Printf("^^^ +++")
}

// Promptf prints a shell prompt
func (s *Shell) Promptf(format string, v ...interface{}) {
	if runtime.GOOS == "windows" {
		fmt.Fprintf(s.output, "\033[90m>\033[0m %s\n", fmt.Sprintf(format, v...))
	} else {
		fmt.Fprintf(s.output, "\033[90m$\033[0m %s\n", fmt.Sprintf(format, v...))
	}
}

// Fatalf shows the error text and terminates the shell
func (s *Shell) Fatalf(format string, v ...interface{}) {
	s.Errorf(format, v...)
	s.Exit(nil)
}

func (s *Shell) AddExitHandler(f func(err error)) {
	s.exitHandlers = append(s.exitHandlers, f)
}

func (s *Shell) Exit(err error) {
	for _, f := range s.exitHandlers {
		f(err)
	}
}

// Getwd returns the current working directory of the shell
func (s *Shell) Getwd() string {
	return s.wd
}

// Chdir changes the working directory of the shell
func (s *Shell) Chdir(path string) error {
	// If the path isn't absolute, prefix it with the current working directory.
	if !filepath.IsAbs(path) {
		path = filepath.Join(s.wd, path)
	}

	s.Commentf("Changing working directory to \"%s\"", path)

	if _, err := os.Stat(path); err != nil {
		return fmt.Errorf("Failed to change working: directory does not exist")
	}

	s.wd = path
	return nil
}

// AbsolutePath returns the absolute path to an executable based on the PATH and
// PATHEXT of the Shell
func (s *Shell) AbsolutePath(executable string) (string, error) {
	// Is the path already absolute?
	if path.IsAbs(executable) {
		return executable, nil
	}

	var envPath = s.Env.Get("PATH")
	var fileExtensions = s.Env.Get("PATHEXT") // For searching .exe, .bat, etc on Windows

	// Use our custom lookPath that takes a specific path
	absolutePath, err := lookPath(executable, envPath, fileExtensions)
	if err != nil {
		return "", err
	}

	// Since the path returned by LookPath is relative to the current working
	// directory, we need to get the absolute version of that.
	return filepath.Abs(absolutePath)
}

// RunCommandSilentlyAndCaptureOuput runs a command without showing a prompt or the output to the user
// and returns the output as a string
func (s *Shell) RunCommandSilentlyAndCaptureOutput(command string, args ...string) (string, error) {
	var b bytes.Buffer

	cmd, err := s.buildCommand(command, args...)
	if err != nil {
		return "", err
	}

	execErr := s.executeCommand(cmd, &b, s.PTY, true)
	output := strings.TrimSpace(b.String())

	return output, execErr
}

// RunCommand runs a command, write to the logger and return an error if it fails
func (s *Shell) RunCommand(command string, args ...string) error {
	cmd, err := s.buildCommand(command, args...)
	if err != nil {
		return err
	}

	return s.executeCommand(cmd, s.output, s.PTY, false)
}

// RunScript executes a script in a Shell, but the target is an interpreted script
// so it has extra checks applied to make sure it's executable. It also doesn't take arguments
func (s *Shell) RunScript(path string) error {
	if runtime.GOOS == "windows" {
		return s.RunCommand(path)
	}

	// If you run a script on Linux that doesn't have the
	// #!/bin/bash thingy at the top, it will fail to run with a
	// "exec format error" error. You can solve it by adding the
	// #!/bin/bash line to the top of the file, but that's
	// annoying, and people generally forget it, so we'll make it
	// easy on them and add it for them here.
	//
	// We also need to make sure the script we pass has quotes
	// around it, otherwise `/bin/bash -c run script with space.sh`
	// fails.
	return s.RunCommand("/bin/bash", "-c", fmt.Sprintf("%q", path))
}

// buildCommand returns an exec.Cmd that runs in the context of the shell
func (s *Shell) buildCommand(command string, args ...string) (*exec.Cmd, error) {
	// Always use absolute path as Windows has a hard time finding executables in it's path
	absPath, err := s.AbsolutePath(command)
	if err != nil {
		return nil, err
	}

	cmd := exec.Command(absPath, args...)
	cmd.Env = s.Env.ToSlice()
	cmd.Dir = s.wd

	return cmd, nil
}

func (s *Shell) executeCommand(cmd *exec.Cmd, w io.Writer, pty bool, silent bool) error {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt,
		syscall.SIGHUP,
		syscall.SIGTERM,
		syscall.SIGINT,
		syscall.SIGQUIT)
	defer signal.Stop(signals)

	var handleError = func(prefix string, err error) error {
		if !silent {
			s.Errorf("%s%v", prefix, err)
		}
		return err
	}

	go func() {
		// forward signals to the process
		for sig := range signals {
			if err := signalProcess(cmd, sig); err != nil {
				_ = handleError("Error passing signal to child process: ", err)
			}
		}
	}()

	cmdStr := process.FormatCommand(cmd.Path, cmd.Args)
	if !silent {
		s.Promptf("%s", cmdStr)
	}

	if pty {
		pty, err := process.StartPTY(cmd)
		if err != nil {
			return handleError("Error starting PTY: ", err)
		}

		// Copy the pty to our buffer. This will block until it EOF's
		// or something breaks.
		_, err = io.Copy(w, pty)
		if e, ok := err.(*os.PathError); ok && e.Err == syscall.EIO {
			// We can safely ignore this error, because it's just the PTY telling us
			// that it closed successfully.
			// See https://github.com/buildkite/agent/pull/34#issuecomment-46080419
		}
	} else {
		cmd.Stdout = w
		cmd.Stderr = w
		cmd.Stdin = nil

		if err := cmd.Start(); err != nil {
			return handleError(fmt.Sprintf("Error starting `%s`: ", cmdStr), err)
		}
	}

	if err := cmd.Wait(); err != nil {
		return handleError(fmt.Sprintf("Error running `%s`: ", cmdStr), err)
	}

	return nil
}

// GetExitCode extracts an exit code from an error where the platform supports it,
// otherwise returns 0 for no error and 1 for an error
func GetExitCode(err error) int {
	if err == nil {
		return 0
	}
	if exiterr, ok := err.(*exec.ExitError); ok {
		// The program has exited with an exit code != 0
		// There is no platform independent way to retrieve
		// the exit code, but the following will work on Unix/macOS
		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
			return status.ExitStatus()
		}
	}
	return 1
}

var (
	lockRetryDuration = time.Second
)

func (s *Shell) LockFile(ctx context.Context, path string) (*lockfile.Lockfile, error) {
	absolutePathToLock, err := filepath.Abs(path)
	if err != nil {
		return nil, fmt.Errorf("Failed to find absolute path to lock \"%s\" (%v)", path, err)
	}

	lock, err := lockfile.New(absolutePathToLock)
	if err != nil {
		return nil, fmt.Errorf("Failed to create lock \"%s\" (%s)", absolutePathToLock, err)
	}

	for {
		// Keep trying the lock until we get it
		if err := lock.TryLock(); err != nil {
			if te, ok := err.(interface {
				Temporary() bool
			}); ok && te.Temporary() {
				s.Commentf("Could not aquire lock on \"%s\" (%s)", absolutePathToLock, err)
				s.Commentf("Trying again in %s...", lockRetryDuration)
				time.Sleep(lockRetryDuration)
			} else {
				return nil, err
			}
		} else {
			break
		}

		// Check if we've timed out
		for {
			select {
			case <-ctx.Done():
				return nil, ctx.Err()
			default:
			}
		}
	}

	return &lock, err
}

func (s *Shell) LockFileWithTimeout(path string, timeout time.Duration) (*lockfile.Lockfile, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	return s.LockFile(ctx, path)
}
<|endoftext|>